mirror of
				https://github.com/fatedier/frp.git
				synced 2025-10-30 22:27:37 +00:00 
			
		
		
		
	Compare commits
	
		
			575 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 65d8fe37c5 | ||
|   | 1723d7b651 | ||
|   | 2481dfab64 | ||
|   | 95a881a7d3 | ||
|   | fe403ab328 | ||
|   | 66555dbb00 | ||
|   | 7f9ea48405 | ||
|   | 96d7e2da6f | ||
|   | d879b8208b | ||
|   | 3585e456d4 | ||
|   | 1de8c3fc87 | ||
|   | bbab3fe9ca | ||
|   | 48990da22e | ||
|   | 5543fc2a9a | ||
|   | c41de6fd28 | ||
|   | 8c8fd9790e | ||
|   | 5a7ef3be74 | ||
|   | d9b5e0bde0 | ||
|   | 05ca72dbf0 | ||
|   | ef6f8bbf6c | ||
|   | 70ac7d3d11 | ||
|   | 385c4d3dd5 | ||
|   | 5e1983f7ed | ||
|   | 516cdbddb0 | ||
|   | 3954ceb93b | ||
|   | 2061ef11c8 | ||
|   | 71cbe5decc | ||
|   | a2ccb6c190 | ||
|   | 5bdf530b7e | ||
|   | 5177570da4 | ||
|   | 0bd8f9cd9b | ||
|   | 649a2f2457 | ||
|   | 54916793f9 | ||
|   | 0b06c1c821 | ||
|   | bbc6f1687d | ||
|   | b250342e27 | ||
|   | f76deb8898 | ||
|   | 611d063e1f | ||
|   | 0c7d778896 | ||
|   | 7c21906884 | ||
|   | a4106ec4b7 | ||
|   | 655c52f9ce | ||
|   | 25cfda5768 | ||
|   | b61cb14c8f | ||
|   | d5ce4d4916 | ||
|   | 4f0ee5980d | ||
|   | 146956ac6e | ||
|   | 35278ad17f | ||
|   | aea9f9fbcc | ||
|   | 08c17c3247 | ||
|   | 6934a18f95 | ||
|   | 5165b0821f | ||
|   | 0aec869513 | ||
|   | 826b9db5f2 | ||
|   | 89d1a1fb2b | ||
|   | 450e0b7148 | ||
|   | a1ac002694 | ||
|   | 951d33d47c | ||
|   | b33ea9274c | ||
|   | 3b3f3dc2b5 | ||
|   | ec0b59732c | ||
|   | bae1ecdc69 | ||
|   | 5c2ab5a749 | ||
|   | 1a8ac148ca | ||
|   | 698219b621 | ||
|   | 229740524e | ||
|   | cbeeac06a5 | ||
|   | 66a69f873f | ||
|   | fb13774457 | ||
|   | f14ed87b29 | ||
|   | 07623027bc | ||
|   | 941ac25648 | ||
|   | f645082d72 | ||
|   | 7793f55545 | ||
|   | ca88b07ecf | ||
|   | 6e305db4be | ||
|   | 9bb08396c7 | ||
|   | 64136a3b3e | ||
|   | b8037475ed | ||
|   | 082447f517 | ||
|   | cc6486addb | ||
|   | 57417c83ae | ||
|   | d74b45be5d | ||
|   | 0d02f291e3 | ||
|   | 42ee536dae | ||
|   | c33b5152e7 | ||
|   | b6c219aa97 | ||
|   | bbc36be052 | ||
|   | f5778349d5 | ||
|   | 71603c6d0b | ||
|   | e64fcce417 | ||
|   | 629f2856b1 | ||
|   | aeb9f2b64d | ||
|   | 85dd41c17b | ||
|   | 102408d37f | ||
|   | 495b577819 | ||
|   | f56b49ad3b | ||
|   | cb1bf71bef | ||
|   | b9f062bef2 | ||
|   | 490019fb51 | ||
|   | 2e497274ba | ||
|   | cf4136fe99 | ||
|   | b1e9cff622 | ||
|   | db2d1fce76 | ||
|   | 8579de9d3f | ||
|   | 0c35273759 | ||
|   | 6eb8146334 | ||
|   | da78e3f52e | ||
|   | e1918f6396 | ||
|   | ad1e32fd2d | ||
|   | 3726f99b04 | ||
|   | c8a7405992 | ||
|   | 040d198e36 | ||
|   | 1a6cbbb2d2 | ||
|   | ea79e03bd0 | ||
|   | 3e349455a0 | ||
|   | c7a457a045 | ||
|   | 0b0d5c982e | ||
|   | c4f873c07a | ||
|   | 01b1df2b91 | ||
|   | f1bea49314 | ||
|   | 2ffae3489b | ||
|   | 96b94d9164 | ||
|   | 76b04f52d1 | ||
|   | 97db0d187a | ||
|   | d9aadab4cb | ||
|   | a0fe2fc2c2 | ||
|   | 1464836f05 | ||
|   | b2a2037032 | ||
|   | 071cbf4b15 | ||
|   | 20fcb58437 | ||
|   | a27e3dda88 | ||
|   | 1dd7317c06 | ||
|   | 58a54bd0fb | ||
|   | caec4982cc | ||
|   | dd8f788ca4 | ||
|   | 8a6d6c534a | ||
|   | 39089cf262 | ||
|   | 55800dc29f | ||
|   | 04560c1896 | ||
|   | 178efd67f1 | ||
|   | 6ef5fb6391 | ||
|   | 1ae43e4d41 | ||
|   | 5db605ca02 | ||
|   | e087301425 | ||
|   | f45283dbdb | ||
|   | b0959b3caa | ||
|   | c5c89a2519 | ||
|   | bebd1db22a | ||
|   | cd37d22f3b | ||
|   | 30af32728a | ||
|   | 60ecd1d58c | ||
|   | a60be8f562 | ||
|   | c008b14d0f | ||
|   | 853892f3cd | ||
|   | e43f9f5850 | ||
|   | d5f30ccd6b | ||
|   | b87df569e7 | ||
|   | 976cf3e9f8 | ||
|   | 371c401f5b | ||
|   | 69919e8ef9 | ||
|   | 9abbe33790 | ||
|   | 4a5c00286e | ||
|   | dfb892c8f6 | ||
|   | 461c4c18fd | ||
|   | 00b9ba95ae | ||
|   | c47aad348d | ||
|   | 4cb4da3afc | ||
|   | c1f57da00d | ||
|   | fe187eb8ec | ||
|   | 0f6f674a64 | ||
|   | 814afbe1f6 | ||
|   | 3fde9176c9 | ||
|   | af7cca1a93 | ||
|   | 7dd28a14aa | ||
|   | 1325c59a4c | ||
|   | 82dc1e924f | ||
|   | 3166bdf3f0 | ||
|   | 8af70c8822 | ||
|   | 87763e8251 | ||
|   | e9241aeb94 | ||
|   | 2eaf134042 | ||
|   | 1739e012d6 | ||
|   | 9e8980429f | ||
|   | 1d0865ca49 | ||
|   | 5c9909aeef | ||
|   | 456ce09061 | ||
|   | ffc13b704a | ||
|   | 5d239127bb | ||
|   | 9b990adf96 | ||
|   | 44e8108910 | ||
|   | 1c35e9a0c6 | ||
|   | 8e719ff0ff | ||
|   | 637ddbce1f | ||
|   | ce8fde793c | ||
|   | eede31c064 | ||
|   | 41c41789b6 | ||
|   | 68dfc89bce | ||
|   | 8690075c0c | ||
|   | 33d8816ced | ||
|   | 90cd25ac21 | ||
|   | ff28668cf2 | ||
|   | a6f2736b80 | ||
|   | 902f6f84a5 | ||
|   | cf9193a429 | ||
|   | 3f64d73ea9 | ||
|   | a77c7e8625 | ||
|   | 14733dd109 | ||
|   | 74b75e8c57 | ||
|   | 63e6e0dc92 | ||
|   | 4d4a738aa9 | ||
|   | 1ed130e704 | ||
|   | 2e773d550b | ||
|   | e155ff056e | ||
|   | 37210d9983 | ||
|   | 338d5bae37 | ||
|   | 3e62198612 | ||
|   | 4f7dfcdb31 | ||
|   | 5b08201e5d | ||
|   | b2c846664d | ||
|   | 3f6799c06a | ||
|   | 9a5f0c23c4 | ||
|   | afde0c515c | ||
|   | 584e098e8e | ||
|   | 37395b3ef5 | ||
|   | 43fb3f3ff7 | ||
|   | 82b127494c | ||
|   | 4d79648657 | ||
|   | 3bb404dfb5 | ||
|   | ff4bdec3f7 | ||
|   | 69f8b08ac0 | ||
|   | d873df5ca8 | ||
|   | a384bf5580 | ||
|   | 92046a7ca2 | ||
|   | 4cc5ddc012 | ||
|   | 46358d466d | ||
|   | 7da61f004b | ||
|   | 63037f1c65 | ||
|   | cc160995da | ||
|   | de48d97cb2 | ||
|   | 1a6a179b68 | ||
|   | 3a2946a2ff | ||
|   | ae9a4623d9 | ||
|   | bd1e9a3010 | ||
|   | 92fff5c191 | ||
|   | 8c65b337ca | ||
|   | 0f1005ff61 | ||
|   | ad858a0d32 | ||
|   | 1e905839f0 | ||
|   | bf50f932d9 | ||
|   | 673047be2c | ||
|   | fa2b9a836c | ||
|   | 9e0fd0c4ef | ||
|   | 0559865fe5 | ||
|   | 4fc85a36c2 | ||
|   | 3f1174a519 | ||
|   | bcbdfcb99b | ||
|   | df046bdeeb | ||
|   | f83447c652 | ||
|   | 9ae69b4aac | ||
|   | c48a89731a | ||
|   | 36b58ab60c | ||
|   | 6320f15a7c | ||
|   | 066172e9c1 | ||
|   | d5931758b6 | ||
|   | c75c3acd21 | ||
|   | 0208ecd1d9 | ||
|   | 23e9845e65 | ||
|   | 2b1ba3a946 | ||
|   | ee9ddf52cd | ||
|   | d246400a71 | ||
|   | f63a4f0cdd | ||
|   | b743b5aaed | ||
|   | 9d9416ab94 | ||
|   | c081df40e1 | ||
|   | fe32a7c4bb | ||
|   | 7bb8c10647 | ||
|   | 0752508469 | ||
|   | 4cc1663a5f | ||
|   | b55a24a27e | ||
|   | aede4e54f8 | ||
|   | b811a620c3 | ||
|   | 07fe05a9d5 | ||
|   | 171bc8dd22 | ||
|   | 9c175d4eb5 | ||
|   | 9f736558e2 | ||
|   | 8f071dd2c2 | ||
|   | bcaf51a6ad | ||
|   | ad3cf9a64a | ||
|   | e3fc73dbc5 | ||
|   | f884e894f2 | ||
|   | d57ed7d3d8 | ||
|   | a2c318d24c | ||
|   | 32f8745d61 | ||
|   | 66120fe49d | ||
|   | fca7f42b37 | ||
|   | 5b303f5148 | ||
|   | 2a044c9d6d | ||
|   | 70e2aee46d | ||
|   | 6742fa2ea8 | ||
|   | 511503d34c | ||
|   | 1eaf17fd05 | ||
|   | 04f4fd0a81 | ||
|   | 3a4d769bb3 | ||
|   | 84341b7fcc | ||
|   | 80ba931326 | ||
|   | 7ebcc7503a | ||
|   | 74cf57feb3 | ||
|   | 712afed0ab | ||
|   | e29a1330ed | ||
|   | 44971c7918 | ||
|   | 7bc6c72844 | ||
|   | 93461e0094 | ||
|   | 03d55201b2 | ||
|   | e6d82f3162 | ||
|   | 1af6276be9 | ||
|   | d1f5ec083a | ||
|   | 716ec281f6 | ||
|   | 67bfae5d23 | ||
|   | f0dc3ed47b | ||
|   | 08b0885564 | ||
|   | 49b503c17b | ||
|   | 150682ec63 | ||
|   | 4dc96f41c9 | ||
|   | 6c13b6d37a | ||
|   | 1c04de380d | ||
|   | 738e5dad22 | ||
|   | 6d81e4c8c6 | ||
|   | faf584e1dd | ||
|   | ba6afd5789 | ||
|   | 11260389a1 | ||
|   | b8082e6e08 | ||
|   | 7957572ced | ||
|   | c2ff37d0d8 | ||
|   | c67f9d5e76 | ||
|   | 1cc61b60f9 | ||
|   | 9c38baeb9e | ||
|   | 84465a7463 | ||
|   | 3fe50df200 | ||
|   | 93d86ca635 | ||
|   | b600a07ec0 | ||
|   | a5f06489cb | ||
|   | 2883d70ea9 | ||
|   | 3f17837a2c | ||
|   | fd268b5082 | ||
|   | 69b09eb8a2 | ||
|   | a84dd05351 | ||
|   | 71f7caa1ee | ||
|   | 5360febd72 | ||
|   | 1b70f0c4fd | ||
|   | 5c75efa222 | ||
|   | ab4a53965b | ||
|   | a0c83bdb78 | ||
|   | 30aeaf968e | ||
|   | 58d0d41501 | ||
|   | 6a95a63fd4 | ||
|   | aa185eb9f3 | ||
|   | d8683a0079 | ||
|   | 8b2cde3a30 | ||
|   | 634e048d0c | ||
|   | a4fece3f51 | ||
|   | 9e683fe446 | ||
|   | 54bbfe26b0 | ||
|   | a1023fdfc2 | ||
|   | b02e1007fb | ||
|   | f90028cf96 | ||
|   | f83a2a73ab | ||
|   | 307b74cc13 | ||
|   | f00a28598f | ||
|   | 6ee0b25782 | ||
|   | 88083d21e8 | ||
|   | a22440aade | ||
|   | b006540141 | ||
|   | e655f07674 | ||
|   | aafa96db58 | ||
|   | 1325148cd3 | ||
|   | 3f9749488a | ||
|   | f9a0d891a1 | ||
|   | 92daa45b68 | ||
|   | 5f20a22b0d | ||
|   | 63be94c611 | ||
|   | 694ee44af6 | ||
|   | edb97abf50 | ||
|   | 0c10279deb | ||
|   | 1f49510e3e | ||
|   | 1868b3bafb | ||
|   | a23521885c | ||
|   | c80dcd050d | ||
|   | 043ab62587 | ||
|   | a8969b1901 | ||
|   | e26285eefc | ||
|   | 299bd7b5cb | ||
|   | 90d1384bf7 | ||
|   | a5434e31b7 | ||
|   | 044bb692dc | ||
|   | 34b98dde52 | ||
|   | 020f786bf5 | ||
|   | cdcc1240ec | ||
|   | c2c9f68a00 | ||
|   | 37470c26f0 | ||
|   | 04a4591caa | ||
|   | 8bf61d5e39 | ||
|   | 659f84bab2 | ||
|   | 9faf4acd62 | ||
|   | 4c3fb22295 | ||
|   | d243f70125 | ||
|   | a56f068f8c | ||
|   | 6a6ccc5302 | ||
|   | 6f90c3400c | ||
|   | eb4f779384 | ||
|   | 59a34b81e0 | ||
|   | b1d1a7a20a | ||
|   | 6b34ed4644 | ||
|   | dde734c953 | ||
|   | 5532881b09 | ||
|   | 94ddeebc21 | ||
|   | ddbb56ee8f | ||
|   | 10fc6c67e0 | ||
|   | 0573ddcd84 | ||
|   | 5eb5fec761 | ||
|   | 52fe721202 | ||
|   | d7d2b72431 | ||
|   | d04d31b39a | ||
|   | d9304d8166 | ||
|   | a44be1e2ed | ||
|   | 2bf1d3e922 | ||
|   | 19f349a65e | ||
|   | b0e56945cd | ||
|   | f2999e3317 | ||
|   | a4c05e6ff9 | ||
|   | d93dd82ed9 | ||
|   | edf4bc431d | ||
|   | 47db75e921 | ||
|   | c702355669 | ||
|   | 7cc5d03f35 | ||
|   | 54beb19435 | ||
|   | 396e148f80 | ||
|   | 4c69a4810e | ||
|   | 40e023f5f4 | ||
|   | adcb2c1ea5 | ||
|   | 8c497793c5 | ||
|   | 78c6845781 | ||
|   | dc5e130d33 | ||
|   | fbc504dfa3 | ||
|   | 77f207d69a | ||
|   | b65e037b5e | ||
|   | b8a28e945c | ||
|   | 0476a85a7d | ||
|   | 5661537f7c | ||
|   | 19f7950485 | ||
|   | c21f8ad291 | ||
|   | 3d6578b15f | ||
|   | 899d6837df | ||
|   | 0e1752b5ce | ||
|   | da182ecd81 | ||
|   | 94c7f57949 | ||
|   | c8e5096f48 | ||
|   | 5079bf01fd | ||
|   | 603d7df49a | ||
|   | 2b1c39e03d | ||
|   | 46ee2f2bc8 | ||
|   | 3d5c3acee0 | ||
|   | 41fd4bb673 | ||
|   | e1e18ba9d6 | ||
|   | ab9eff97a8 | ||
|   | 6f40b1a70a | ||
|   | 87c9b8f548 | ||
|   | 3fcf7efc5a | ||
|   | a655f5699b | ||
|   | 09624b56ca | ||
|   | e262ac6abd | ||
|   | 47c1a3e52c | ||
|   | 4dadaac905 | ||
|   | e1ed6660b0 | ||
|   | b71b2cf46d | ||
|   | a0903d4121 | ||
|   | b403e4142b | ||
|   | 46716acd8e | ||
|   | c7f85bcdd3 | ||
|   | ddd2acfe9f | ||
|   | e3bf7e2b2b | ||
|   | 4914472215 | ||
|   | 5d9300c1e9 | ||
|   | b4a577b0d7 | ||
|   | 32d0ce9ea0 | ||
|   | 2d30a6e8a7 | ||
|   | 740691b080 | ||
|   | 11fe4b1d8b | ||
|   | c64931fce9 | ||
|   | d4ecc2218d | ||
|   | 9c0ca8675d | ||
|   | 5cdb84c666 | ||
|   | 060277308b | ||
|   | 31dfd5101f | ||
|   | 4300169041 | ||
|   | 3ab9850871 | ||
|   | d813b953dd | ||
|   | 1da81ad7d3 | ||
|   | 3b06d771ac | ||
|   | 7f386fc042 | ||
|   | df8edefa56 | ||
|   | ecb6ad4885 | ||
|   | 785dcaad44 | ||
|   | fd3c97a0e9 | ||
|   | 8f5f0b0a9a | ||
|   | 452e02adab | ||
|   | d2e1cfa5bc | ||
|   | 6dd51e0951 | ||
|   | e0f2993b70 | ||
|   | 4067591a4d | ||
|   | 926d0b74a9 | ||
|   | 4f49458af0 | ||
|   | fd6b94908b | ||
|   | dee4cbd48c | ||
|   | 9a3564f29c | ||
|   | ac09ba3982 | ||
|   | a9bf25f255 | ||
|   | 6bc05de58e | ||
|   | 5265b79957 | ||
|   | fefc0a38c3 | ||
|   | c387138006 | ||
|   | 36f8beee3d | ||
|   | 366a0c898d | ||
|   | d747f9207e | ||
|   | 5400366036 | ||
|   | 9dae7ad6fe | ||
|   | a4e051d494 | ||
|   | 28251a8104 | ||
|   | e99357da4e | ||
|   | e580c7b6e6 | ||
|   | ba74934a1f | ||
|   | 1bad5c6561 | ||
|   | f968f3eace | ||
|   | b14441d5cd | ||
|   | 0a50c3bd82 | ||
|   | ef5702213f | ||
|   | c5e4b24f8f | ||
|   | 1987a399c1 | ||
|   | ab6c5c813e | ||
|   | 51eaec14ab | ||
|   | f3876d69bb | ||
|   | 817f4463f4 | ||
|   | 654981019d | ||
|   | 740fb05b21 | ||
|   | e8c830e5c8 | ||
|   | 2640c0b570 | ||
|   | 04014bb78f | ||
|   | 150c4beef8 | ||
|   | 5febee6201 | ||
|   | ee8786a6b3 | ||
|   | d569a60eff | ||
|   | 14607b352d | ||
|   | bc7ad2bb20 | ||
|   | cd59bbdad6 | ||
|   | f404a0a5ee | ||
|   | da7c473288 | ||
|   | ea323084ad | ||
|   | c680d87edc | ||
|   | d3c4401473 | ||
|   | 7a9a675d58 | ||
|   | 040841db48 | ||
|   | f804330dbf | ||
|   | d39d745e43 | ||
|   | c10321ead6 | ||
|   | d7797cbd18 | ||
|   | 0b9d823168 | ||
|   | deb750652f | ||
|   | 14ba38a1b4 | ||
|   | f650d3f330 | ||
|   | 2c39719cc0 | ||
|   | 6874688e07 | ||
|   | fdd7436736 | ||
|   | 0f326449e8 | ||
|   | 7c3e00ed28 | ||
|   | d5913fc77b | 
							
								
								
									
										30
									
								
								.github/ISSUE_TEMPLATE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.github/ISSUE_TEMPLATE
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly. | ||||
| (为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。) | ||||
|  | ||||
| Use the commands below to provide key information from your environment: | ||||
| You do NOT have to include this information if this is a FEATURE REQUEST | ||||
|  | ||||
| **What version of frp are you using (./frpc -v or ./frps -v)?** | ||||
|  | ||||
|  | ||||
| **What operating system and processor architecture are you using (`go env`)?** | ||||
|  | ||||
|  | ||||
| **Configures you used:** | ||||
|  | ||||
|  | ||||
| **Steps to reproduce the issue:** | ||||
| 1. | ||||
| 2. | ||||
| 3. | ||||
|  | ||||
| **Describe the results you received:** | ||||
|  | ||||
|  | ||||
| **Describe the results you expected:** | ||||
|  | ||||
|  | ||||
| **Additional information you deem important (e.g. issue happens only occasionally):** | ||||
|  | ||||
|  | ||||
| **Can you point out what caused this issue (optional)** | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -25,6 +25,8 @@ _testmain.go | ||||
|  | ||||
| # Self | ||||
| bin/ | ||||
| packages/ | ||||
| test/bin/ | ||||
|  | ||||
| # Cache | ||||
| *.swp | ||||
|   | ||||
| @@ -2,11 +2,11 @@ sudo: false | ||||
| language: go | ||||
|  | ||||
| go: | ||||
|     - 1.4.2 | ||||
|     - 1.5.3 | ||||
|     - 1.10.x | ||||
|     - 1.11.x | ||||
|  | ||||
| install: | ||||
|     - make | ||||
|  | ||||
| script: | ||||
|     - make test | ||||
|     - make alltest | ||||
|   | ||||
							
								
								
									
										14
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,14 +0,0 @@ | ||||
| FROM golang:1.5 | ||||
|  | ||||
| MAINTAINER fatedier | ||||
|  | ||||
| RUN echo "[common]\nbind_addr = 0.0.0.0\nbind_port = 7000\n[test]\npasswd = 123\nbind_addr = 0.0.0.0\nlisten_port = 80" > /usr/share/frps.ini | ||||
|  | ||||
| ADD ./ /usr/share/frp/ | ||||
|  | ||||
| RUN cd /usr/share/frp && make | ||||
|  | ||||
| EXPOSE 80 | ||||
| EXPOSE 7000 | ||||
|  | ||||
| CMD ["/usr/share/frp/bin/frps", "-c", "/usr/share/frps.ini"] | ||||
							
								
								
									
										23
									
								
								Godeps/Godeps.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										23
									
								
								Godeps/Godeps.json
									
									
									
										generated
									
									
									
								
							| @@ -1,23 +0,0 @@ | ||||
| { | ||||
| 	"ImportPath": "frp", | ||||
| 	"GoVersion": "go1.4", | ||||
| 	"Packages": [ | ||||
| 		"./..." | ||||
| 	], | ||||
| 	"Deps": [ | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/astaxie/beego/logs", | ||||
| 			"Comment": "v1.5.0-9-gfb7314f", | ||||
| 			"Rev": "fb7314f8ac86b83ccd34386518d97cf2363e2ae5" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/docopt/docopt-go", | ||||
| 			"Comment": "0.6.2", | ||||
| 			"Rev": "784ddc588536785e7299f7272f39101f7faccc3f" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"ImportPath": "github.com/vaughan0/go-ini", | ||||
| 			"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1" | ||||
| 		} | ||||
| 	] | ||||
| } | ||||
							
								
								
									
										5
									
								
								Godeps/Readme
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								Godeps/Readme
									
									
									
										generated
									
									
									
								
							| @@ -1,5 +0,0 @@ | ||||
| This directory tree is generated automatically by godep. | ||||
|  | ||||
| Please do not edit. | ||||
|  | ||||
| See https://github.com/tools/godep for more information. | ||||
							
								
								
									
										2
									
								
								Godeps/_workspace/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								Godeps/_workspace/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,2 +0,0 @@ | ||||
| /pkg | ||||
| /bin | ||||
							
								
								
									
										95
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/console.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										95
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/console.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,95 +0,0 @@ | ||||
| // Copyright 2014 beego Author. All Rights Reserved. | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package logs | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| ) | ||||
|  | ||||
| type Brush func(string) string | ||||
|  | ||||
| func NewBrush(color string) Brush { | ||||
| 	pre := "\033[" | ||||
| 	reset := "\033[0m" | ||||
| 	return func(text string) string { | ||||
| 		return pre + color + "m" + text + reset | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var colors = []Brush{ | ||||
| 	NewBrush("1;37"), // Emergency	white | ||||
| 	NewBrush("1;36"), // Alert			cyan | ||||
| 	NewBrush("1;35"), // Critical   magenta | ||||
| 	NewBrush("1;31"), // Error      red | ||||
| 	NewBrush("1;33"), // Warning    yellow | ||||
| 	NewBrush("1;32"), // Notice			green | ||||
| 	NewBrush("1;34"), // Informational	blue | ||||
| 	NewBrush("1;34"), // Debug      blue | ||||
| } | ||||
|  | ||||
| // ConsoleWriter implements LoggerInterface and writes messages to terminal. | ||||
| type ConsoleWriter struct { | ||||
| 	lg    *log.Logger | ||||
| 	Level int `json:"level"` | ||||
| } | ||||
|  | ||||
| // create ConsoleWriter returning as LoggerInterface. | ||||
| func NewConsole() LoggerInterface { | ||||
| 	cw := &ConsoleWriter{ | ||||
| 		lg:    log.New(os.Stdout, "", log.Ldate|log.Ltime), | ||||
| 		Level: LevelDebug, | ||||
| 	} | ||||
| 	return cw | ||||
| } | ||||
|  | ||||
| // init console logger. | ||||
| // jsonconfig like '{"level":LevelTrace}'. | ||||
| func (c *ConsoleWriter) Init(jsonconfig string) error { | ||||
| 	if len(jsonconfig) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return json.Unmarshal([]byte(jsonconfig), c) | ||||
| } | ||||
|  | ||||
| // write message in console. | ||||
| func (c *ConsoleWriter) WriteMsg(msg string, level int) error { | ||||
| 	if level > c.Level { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if goos := runtime.GOOS; goos == "windows" { | ||||
| 		c.lg.Println(msg) | ||||
| 		return nil | ||||
| 	} | ||||
| 	c.lg.Println(colors[level](msg)) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // implementing method. empty. | ||||
| func (c *ConsoleWriter) Destroy() { | ||||
|  | ||||
| } | ||||
|  | ||||
| // implementing method. empty. | ||||
| func (c *ConsoleWriter) Flush() { | ||||
|  | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	Register("console", NewConsole) | ||||
| } | ||||
							
								
								
									
										76
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/es/es.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										76
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/es/es.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,76 +0,0 @@ | ||||
| package es | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/astaxie/beego/logs" | ||||
| 	"github.com/belogik/goes" | ||||
| ) | ||||
|  | ||||
| func NewES() logs.LoggerInterface { | ||||
| 	cw := &esLogger{ | ||||
| 		Level: logs.LevelDebug, | ||||
| 	} | ||||
| 	return cw | ||||
| } | ||||
|  | ||||
| type esLogger struct { | ||||
| 	*goes.Connection | ||||
| 	DSN   string `json:"dsn"` | ||||
| 	Level int    `json:"level"` | ||||
| } | ||||
|  | ||||
| // {"dsn":"http://localhost:9200/","level":1} | ||||
| func (el *esLogger) Init(jsonconfig string) error { | ||||
| 	err := json.Unmarshal([]byte(jsonconfig), el) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if el.DSN == "" { | ||||
| 		return errors.New("empty dsn") | ||||
| 	} else if u, err := url.Parse(el.DSN); err != nil { | ||||
| 		return err | ||||
| 	} else if u.Path == "" { | ||||
| 		return errors.New("missing prefix") | ||||
| 	} else if host, port, err := net.SplitHostPort(u.Host); err != nil { | ||||
| 		return err | ||||
| 	} else { | ||||
| 		conn := goes.NewConnection(host, port) | ||||
| 		el.Connection = conn | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (el *esLogger) WriteMsg(msg string, level int) error { | ||||
| 	if level > el.Level { | ||||
| 		return nil | ||||
| 	} | ||||
| 	t := time.Now() | ||||
| 	vals := make(map[string]interface{}) | ||||
| 	vals["@timestamp"] = t.Format(time.RFC3339) | ||||
| 	vals["@msg"] = msg | ||||
| 	d := goes.Document{ | ||||
| 		Index:  fmt.Sprintf("%04d.%02d.%02d", t.Year(), t.Month(), t.Day()), | ||||
| 		Type:   "logs", | ||||
| 		Fields: vals, | ||||
| 	} | ||||
| 	_, err := el.Index(d, nil) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (el *esLogger) Destroy() { | ||||
|  | ||||
| } | ||||
|  | ||||
| func (el *esLogger) Flush() { | ||||
|  | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	logs.Register("es", NewES) | ||||
| } | ||||
							
								
								
									
										283
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/file.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										283
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/file.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,283 +0,0 @@ | ||||
| // Copyright 2014 beego Author. All Rights Reserved. | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package logs | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // FileLogWriter implements LoggerInterface. | ||||
| // It writes messages by lines limit, file size limit, or time frequency. | ||||
| type FileLogWriter struct { | ||||
| 	*log.Logger | ||||
| 	mw *MuxWriter | ||||
| 	// The opened file | ||||
| 	Filename string `json:"filename"` | ||||
|  | ||||
| 	Maxlines          int `json:"maxlines"` | ||||
| 	maxlines_curlines int | ||||
|  | ||||
| 	// Rotate at size | ||||
| 	Maxsize         int `json:"maxsize"` | ||||
| 	maxsize_cursize int | ||||
|  | ||||
| 	// Rotate daily | ||||
| 	Daily          bool  `json:"daily"` | ||||
| 	Maxdays        int64 `json:"maxdays"` | ||||
| 	daily_opendate int | ||||
|  | ||||
| 	Rotate bool `json:"rotate"` | ||||
|  | ||||
| 	startLock sync.Mutex // Only one log can write to the file | ||||
|  | ||||
| 	Level int `json:"level"` | ||||
| } | ||||
|  | ||||
| // an *os.File writer with locker. | ||||
| type MuxWriter struct { | ||||
| 	sync.Mutex | ||||
| 	fd *os.File | ||||
| } | ||||
|  | ||||
| // write to os.File. | ||||
| func (l *MuxWriter) Write(b []byte) (int, error) { | ||||
| 	l.Lock() | ||||
| 	defer l.Unlock() | ||||
| 	return l.fd.Write(b) | ||||
| } | ||||
|  | ||||
| // set os.File in writer. | ||||
| func (l *MuxWriter) SetFd(fd *os.File) { | ||||
| 	if l.fd != nil { | ||||
| 		l.fd.Close() | ||||
| 	} | ||||
| 	l.fd = fd | ||||
| } | ||||
|  | ||||
| // create a FileLogWriter returning as LoggerInterface. | ||||
| func NewFileWriter() LoggerInterface { | ||||
| 	w := &FileLogWriter{ | ||||
| 		Filename: "", | ||||
| 		Maxlines: 1000000, | ||||
| 		Maxsize:  1 << 28, //256 MB | ||||
| 		Daily:    true, | ||||
| 		Maxdays:  7, | ||||
| 		Rotate:   true, | ||||
| 		Level:    LevelTrace, | ||||
| 	} | ||||
| 	// use MuxWriter instead direct use os.File for lock write when rotate | ||||
| 	w.mw = new(MuxWriter) | ||||
| 	// set MuxWriter as Logger's io.Writer | ||||
| 	w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime) | ||||
| 	return w | ||||
| } | ||||
|  | ||||
| // Init file logger with json config. | ||||
| // jsonconfig like: | ||||
| //	{ | ||||
| //	"filename":"logs/beego.log", | ||||
| //	"maxlines":10000, | ||||
| //	"maxsize":1<<30, | ||||
| //	"daily":true, | ||||
| //	"maxdays":15, | ||||
| //	"rotate":true | ||||
| //	} | ||||
| func (w *FileLogWriter) Init(jsonconfig string) error { | ||||
| 	err := json.Unmarshal([]byte(jsonconfig), w) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if len(w.Filename) == 0 { | ||||
| 		return errors.New("jsonconfig must have filename") | ||||
| 	} | ||||
| 	err = w.startLogger() | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // start file logger. create log file and set to locker-inside file writer. | ||||
| func (w *FileLogWriter) startLogger() error { | ||||
| 	fd, err := w.createLogFile() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	w.mw.SetFd(fd) | ||||
| 	return w.initFd() | ||||
| } | ||||
|  | ||||
| func (w *FileLogWriter) docheck(size int) { | ||||
| 	w.startLock.Lock() | ||||
| 	defer w.startLock.Unlock() | ||||
| 	if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) || | ||||
| 		(w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) || | ||||
| 		(w.Daily && time.Now().Day() != w.daily_opendate)) { | ||||
| 		if err := w.DoRotate(); err != nil { | ||||
| 			fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	w.maxlines_curlines++ | ||||
| 	w.maxsize_cursize += size | ||||
| } | ||||
|  | ||||
| // write logger message into file. | ||||
| func (w *FileLogWriter) WriteMsg(msg string, level int) error { | ||||
| 	if level > w.Level { | ||||
| 		return nil | ||||
| 	} | ||||
| 	n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] " | ||||
| 	w.docheck(n) | ||||
| 	w.Logger.Println(msg) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (w *FileLogWriter) createLogFile() (*os.File, error) { | ||||
| 	// Open the log file | ||||
| 	fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) | ||||
| 	return fd, err | ||||
| } | ||||
|  | ||||
| func (w *FileLogWriter) initFd() error { | ||||
| 	fd := w.mw.fd | ||||
| 	finfo, err := fd.Stat() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("get stat err: %s\n", err) | ||||
| 	} | ||||
| 	w.maxsize_cursize = int(finfo.Size()) | ||||
| 	w.daily_opendate = time.Now().Day() | ||||
| 	w.maxlines_curlines = 0 | ||||
| 	if finfo.Size() > 0 { | ||||
| 		count, err := w.lines() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		w.maxlines_curlines = count | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (w *FileLogWriter) lines() (int, error) { | ||||
| 	fd, err := os.Open(w.Filename) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	defer fd.Close() | ||||
|  | ||||
| 	buf := make([]byte, 32768) // 32k | ||||
| 	count := 0 | ||||
| 	lineSep := []byte{'\n'} | ||||
|  | ||||
| 	for { | ||||
| 		c, err := fd.Read(buf) | ||||
| 		if err != nil && err != io.EOF { | ||||
| 			return count, err | ||||
| 		} | ||||
|  | ||||
| 		count += bytes.Count(buf[:c], lineSep) | ||||
|  | ||||
| 		if err == io.EOF { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return count, nil | ||||
| } | ||||
|  | ||||
| // DoRotate means it need to write file in new file. | ||||
| // new file name like xx.log.2013-01-01.2 | ||||
| func (w *FileLogWriter) DoRotate() error { | ||||
| 	_, err := os.Lstat(w.Filename) | ||||
| 	if err == nil { // file exists | ||||
| 		// Find the next available number | ||||
| 		num := 1 | ||||
| 		fname := "" | ||||
| 		for ; err == nil && num <= 999; num++ { | ||||
| 			fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num) | ||||
| 			_, err = os.Lstat(fname) | ||||
| 		} | ||||
| 		// return error if the last file checked still existed | ||||
| 		if err == nil { | ||||
| 			return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename) | ||||
| 		} | ||||
|  | ||||
| 		// block Logger's io.Writer | ||||
| 		w.mw.Lock() | ||||
| 		defer w.mw.Unlock() | ||||
|  | ||||
| 		fd := w.mw.fd | ||||
| 		fd.Close() | ||||
|  | ||||
| 		// close fd before rename | ||||
| 		// Rename the file to its newfound home | ||||
| 		err = os.Rename(w.Filename, fname) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("Rotate: %s\n", err) | ||||
| 		} | ||||
|  | ||||
| 		// re-start logger | ||||
| 		err = w.startLogger() | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("Rotate StartLogger: %s\n", err) | ||||
| 		} | ||||
|  | ||||
| 		go w.deleteOldLog() | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (w *FileLogWriter) deleteOldLog() { | ||||
| 	dir := filepath.Dir(w.Filename) | ||||
| 	filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { | ||||
| 		defer func() { | ||||
| 			if r := recover(); r != nil { | ||||
| 				returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r) | ||||
| 				fmt.Println(returnErr) | ||||
| 			} | ||||
| 		}() | ||||
|  | ||||
| 		if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) { | ||||
| 			if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) { | ||||
| 				os.Remove(path) | ||||
| 			} | ||||
| 		} | ||||
| 		return | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // destroy file logger, close file writer. | ||||
| func (w *FileLogWriter) Destroy() { | ||||
| 	w.mw.fd.Close() | ||||
| } | ||||
|  | ||||
| // flush file logger. | ||||
| // there are no buffering messages in file logger in memory. | ||||
| // flush file means sync file from disk. | ||||
| func (w *FileLogWriter) Flush() { | ||||
| 	w.mw.fd.Sync() | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	Register("file", NewFileWriter) | ||||
| } | ||||
							
								
								
									
										350
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/log.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										350
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/log.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,350 +0,0 @@ | ||||
| // Copyright 2014 beego Author. All Rights Reserved. | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| // Usage: | ||||
| // | ||||
| // import "github.com/astaxie/beego/logs" | ||||
| // | ||||
| //	log := NewLogger(10000) | ||||
| //	log.SetLogger("console", "") | ||||
| // | ||||
| //	> the first params stand for how many channel | ||||
| // | ||||
| // Use it like this: | ||||
| // | ||||
| //	log.Trace("trace") | ||||
| //	log.Info("info") | ||||
| //	log.Warn("warning") | ||||
| //	log.Debug("debug") | ||||
| //	log.Critical("critical") | ||||
| // | ||||
| //  more docs http://beego.me/docs/module/logs.md | ||||
| package logs | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"path" | ||||
| 	"runtime" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| // RFC5424 log message levels. | ||||
| const ( | ||||
| 	LevelEmergency = iota | ||||
| 	LevelAlert | ||||
| 	LevelCritical | ||||
| 	LevelError | ||||
| 	LevelWarning | ||||
| 	LevelNotice | ||||
| 	LevelInformational | ||||
| 	LevelDebug | ||||
| ) | ||||
|  | ||||
| // Legacy loglevel constants to ensure backwards compatibility. | ||||
| // | ||||
| // Deprecated: will be removed in 1.5.0. | ||||
| const ( | ||||
| 	LevelInfo  = LevelInformational | ||||
| 	LevelTrace = LevelDebug | ||||
| 	LevelWarn  = LevelWarning | ||||
| ) | ||||
|  | ||||
| type loggerType func() LoggerInterface | ||||
|  | ||||
| // LoggerInterface defines the behavior of a log provider. | ||||
| type LoggerInterface interface { | ||||
| 	Init(config string) error | ||||
| 	WriteMsg(msg string, level int) error | ||||
| 	Destroy() | ||||
| 	Flush() | ||||
| } | ||||
|  | ||||
| var adapters = make(map[string]loggerType) | ||||
|  | ||||
| // Register makes a log provide available by the provided name. | ||||
| // If Register is called twice with the same name or if driver is nil, | ||||
| // it panics. | ||||
| func Register(name string, log loggerType) { | ||||
| 	if log == nil { | ||||
| 		panic("logs: Register provide is nil") | ||||
| 	} | ||||
| 	if _, dup := adapters[name]; dup { | ||||
| 		panic("logs: Register called twice for provider " + name) | ||||
| 	} | ||||
| 	adapters[name] = log | ||||
| } | ||||
|  | ||||
| // BeeLogger is default logger in beego application. | ||||
| // it can contain several providers and log message into all providers. | ||||
| type BeeLogger struct { | ||||
| 	lock                sync.Mutex | ||||
| 	level               int | ||||
| 	enableFuncCallDepth bool | ||||
| 	loggerFuncCallDepth int | ||||
| 	asynchronous        bool | ||||
| 	msg                 chan *logMsg | ||||
| 	outputs             map[string]LoggerInterface | ||||
| } | ||||
|  | ||||
| type logMsg struct { | ||||
| 	level int | ||||
| 	msg   string | ||||
| } | ||||
|  | ||||
| // NewLogger returns a new BeeLogger. | ||||
| // channellen means the number of messages in chan. | ||||
| // if the buffering chan is full, logger adapters write to file or other way. | ||||
| func NewLogger(channellen int64) *BeeLogger { | ||||
| 	bl := new(BeeLogger) | ||||
| 	bl.level = LevelDebug | ||||
| 	bl.loggerFuncCallDepth = 2 | ||||
| 	bl.msg = make(chan *logMsg, channellen) | ||||
| 	bl.outputs = make(map[string]LoggerInterface) | ||||
| 	return bl | ||||
| } | ||||
|  | ||||
| func (bl *BeeLogger) Async() *BeeLogger { | ||||
| 	bl.asynchronous = true | ||||
| 	go bl.startLogger() | ||||
| 	return bl | ||||
| } | ||||
|  | ||||
| // SetLogger provides a given logger adapter into BeeLogger with config string. | ||||
| // config need to be correct JSON as string: {"interval":360}. | ||||
| func (bl *BeeLogger) SetLogger(adaptername string, config string) error { | ||||
| 	bl.lock.Lock() | ||||
| 	defer bl.lock.Unlock() | ||||
| 	if log, ok := adapters[adaptername]; ok { | ||||
| 		lg := log() | ||||
| 		err := lg.Init(config) | ||||
| 		bl.outputs[adaptername] = lg | ||||
| 		if err != nil { | ||||
| 			fmt.Println("logs.BeeLogger.SetLogger: " + err.Error()) | ||||
| 			return err | ||||
| 		} | ||||
| 	} else { | ||||
| 		return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // remove a logger adapter in BeeLogger. | ||||
| func (bl *BeeLogger) DelLogger(adaptername string) error { | ||||
| 	bl.lock.Lock() | ||||
| 	defer bl.lock.Unlock() | ||||
| 	if lg, ok := bl.outputs[adaptername]; ok { | ||||
| 		lg.Destroy() | ||||
| 		delete(bl.outputs, adaptername) | ||||
| 		return nil | ||||
| 	} else { | ||||
| 		return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (bl *BeeLogger) writerMsg(loglevel int, msg string) error { | ||||
| 	lm := new(logMsg) | ||||
| 	lm.level = loglevel | ||||
| 	if bl.enableFuncCallDepth { | ||||
| 		_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) | ||||
| 		if !ok { | ||||
| 			file = "???" | ||||
| 			line = 0 | ||||
| 		} | ||||
| 		_, filename := path.Split(file) | ||||
| 		lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg) | ||||
| 	} else { | ||||
| 		lm.msg = msg | ||||
| 	} | ||||
| 	if bl.asynchronous { | ||||
| 		bl.msg <- lm | ||||
| 	} else { | ||||
| 		for name, l := range bl.outputs { | ||||
| 			err := l.WriteMsg(lm.msg, lm.level) | ||||
| 			if err != nil { | ||||
| 				fmt.Println("unable to WriteMsg to adapter:", name, err) | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Set log message level. | ||||
| // | ||||
| // If message level (such as LevelDebug) is higher than logger level (such as LevelWarning), | ||||
| // log providers will not even be sent the message. | ||||
| func (bl *BeeLogger) SetLevel(l int) { | ||||
| 	bl.level = l | ||||
| } | ||||
|  | ||||
| // set log funcCallDepth | ||||
| func (bl *BeeLogger) SetLogFuncCallDepth(d int) { | ||||
| 	bl.loggerFuncCallDepth = d | ||||
| } | ||||
|  | ||||
| // get log funcCallDepth for wrapper | ||||
| func (bl *BeeLogger) GetLogFuncCallDepth() int { | ||||
| 	return bl.loggerFuncCallDepth | ||||
| } | ||||
|  | ||||
| // enable log funcCallDepth | ||||
| func (bl *BeeLogger) EnableFuncCallDepth(b bool) { | ||||
| 	bl.enableFuncCallDepth = b | ||||
| } | ||||
|  | ||||
| // start logger chan reading. | ||||
| // when chan is not empty, write logs. | ||||
| func (bl *BeeLogger) startLogger() { | ||||
| 	for { | ||||
| 		select { | ||||
| 		case bm := <-bl.msg: | ||||
| 			for _, l := range bl.outputs { | ||||
| 				err := l.WriteMsg(bm.msg, bm.level) | ||||
| 				if err != nil { | ||||
| 					fmt.Println("ERROR, unable to WriteMsg:", err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Log EMERGENCY level message. | ||||
| func (bl *BeeLogger) Emergency(format string, v ...interface{}) { | ||||
| 	if LevelEmergency > bl.level { | ||||
| 		return | ||||
| 	} | ||||
| 	msg := fmt.Sprintf("[M] "+format, v...) | ||||
| 	bl.writerMsg(LevelEmergency, msg) | ||||
| } | ||||
|  | ||||
| // Log ALERT level message. | ||||
| func (bl *BeeLogger) Alert(format string, v ...interface{}) { | ||||
| 	if LevelAlert > bl.level { | ||||
| 		return | ||||
| 	} | ||||
| 	msg := fmt.Sprintf("[A] "+format, v...) | ||||
| 	bl.writerMsg(LevelAlert, msg) | ||||
| } | ||||
|  | ||||
| // Log CRITICAL level message. | ||||
| func (bl *BeeLogger) Critical(format string, v ...interface{}) { | ||||
| 	if LevelCritical > bl.level { | ||||
| 		return | ||||
| 	} | ||||
| 	msg := fmt.Sprintf("[C] "+format, v...) | ||||
| 	bl.writerMsg(LevelCritical, msg) | ||||
| } | ||||
|  | ||||
| // Log ERROR level message. | ||||
| func (bl *BeeLogger) Error(format string, v ...interface{}) { | ||||
| 	if LevelError > bl.level { | ||||
| 		return | ||||
| 	} | ||||
| 	msg := fmt.Sprintf("[E] "+format, v...) | ||||
| 	bl.writerMsg(LevelError, msg) | ||||
| } | ||||
|  | ||||
| // Log WARNING level message. | ||||
| func (bl *BeeLogger) Warning(format string, v ...interface{}) { | ||||
| 	if LevelWarning > bl.level { | ||||
| 		return | ||||
| 	} | ||||
| 	msg := fmt.Sprintf("[W] "+format, v...) | ||||
| 	bl.writerMsg(LevelWarning, msg) | ||||
| } | ||||
|  | ||||
| // Log NOTICE level message. | ||||
| func (bl *BeeLogger) Notice(format string, v ...interface{}) { | ||||
| 	if LevelNotice > bl.level { | ||||
| 		return | ||||
| 	} | ||||
| 	msg := fmt.Sprintf("[N] "+format, v...) | ||||
| 	bl.writerMsg(LevelNotice, msg) | ||||
| } | ||||
|  | ||||
| // Log INFORMATIONAL level message. | ||||
| func (bl *BeeLogger) Informational(format string, v ...interface{}) { | ||||
| 	if LevelInformational > bl.level { | ||||
| 		return | ||||
| 	} | ||||
| 	msg := fmt.Sprintf("[I] "+format, v...) | ||||
| 	bl.writerMsg(LevelInformational, msg) | ||||
| } | ||||
|  | ||||
| // Log DEBUG level message. | ||||
| func (bl *BeeLogger) Debug(format string, v ...interface{}) { | ||||
| 	if LevelDebug > bl.level { | ||||
| 		return | ||||
| 	} | ||||
| 	msg := fmt.Sprintf("[D] "+format, v...) | ||||
| 	bl.writerMsg(LevelDebug, msg) | ||||
| } | ||||
|  | ||||
| // Log WARN level message. | ||||
| // compatibility alias for Warning() | ||||
| func (bl *BeeLogger) Warn(format string, v ...interface{}) { | ||||
| 	if LevelWarning > bl.level { | ||||
| 		return | ||||
| 	} | ||||
| 	msg := fmt.Sprintf("[W] "+format, v...) | ||||
| 	bl.writerMsg(LevelWarning, msg) | ||||
| } | ||||
|  | ||||
| // Log INFO level message. | ||||
| // compatibility alias for Informational() | ||||
| func (bl *BeeLogger) Info(format string, v ...interface{}) { | ||||
| 	if LevelInformational > bl.level { | ||||
| 		return | ||||
| 	} | ||||
| 	msg := fmt.Sprintf("[I] "+format, v...) | ||||
| 	bl.writerMsg(LevelInformational, msg) | ||||
| } | ||||
|  | ||||
| // Log TRACE level message. | ||||
| // compatibility alias for Debug() | ||||
| func (bl *BeeLogger) Trace(format string, v ...interface{}) { | ||||
| 	if LevelDebug > bl.level { | ||||
| 		return | ||||
| 	} | ||||
| 	msg := fmt.Sprintf("[D] "+format, v...) | ||||
| 	bl.writerMsg(LevelDebug, msg) | ||||
| } | ||||
|  | ||||
| // flush all chan data. | ||||
| func (bl *BeeLogger) Flush() { | ||||
| 	for _, l := range bl.outputs { | ||||
| 		l.Flush() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // close logger, flush all chan data and destroy all adapters in BeeLogger. | ||||
| func (bl *BeeLogger) Close() { | ||||
| 	for { | ||||
| 		if len(bl.msg) > 0 { | ||||
| 			bm := <-bl.msg | ||||
| 			for _, l := range bl.outputs { | ||||
| 				err := l.WriteMsg(bm.msg, bm.level) | ||||
| 				if err != nil { | ||||
| 					fmt.Println("ERROR, unable to WriteMsg (while closing logger):", err) | ||||
| 				} | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
| 		break | ||||
| 	} | ||||
| 	for _, l := range bl.outputs { | ||||
| 		l.Flush() | ||||
| 		l.Destroy() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										31
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,31 +0,0 @@ | ||||
| # Travis CI (http://travis-ci.org/) is a continuous integration | ||||
| # service for open source projects. This file configures it | ||||
| # to run unit tests for docopt-go. | ||||
|  | ||||
| language: go | ||||
|  | ||||
| go: | ||||
|     - 1.4 | ||||
|     - 1.5 | ||||
|     - tip | ||||
|  | ||||
| matrix: | ||||
|     fast_finish: true | ||||
|  | ||||
| before_install: | ||||
|     - go get golang.org/x/tools/cmd/vet | ||||
|     - go get golang.org/x/tools/cmd/cover | ||||
|     - go get github.com/golang/lint/golint | ||||
|     - go get github.com/mattn/goveralls | ||||
|  | ||||
| install: | ||||
|     - go get -d -v ./... && go build -v ./... | ||||
|  | ||||
| script: | ||||
|     - go vet -x ./... | ||||
|     - $HOME/gopath/bin/golint ./... | ||||
|     - go test -v ./... | ||||
|     - go test -covermode=count -coverprofile=profile.cov . | ||||
|  | ||||
| after_script: | ||||
|     - $HOME/gopath/bin/goveralls -coverprofile=profile.cov -service=travis-ci | ||||
							
								
								
									
										88
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										88
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,88 +0,0 @@ | ||||
| docopt-go | ||||
| ========= | ||||
|  | ||||
| [](https://travis-ci.org/docopt/docopt.go) | ||||
| [](https://coveralls.io/r/docopt/docopt.go) | ||||
| [](https://godoc.org/github.com/docopt/docopt.go) | ||||
|  | ||||
| An implementation of [docopt](http://docopt.org/) in the | ||||
| [Go](http://golang.org/) programming language. | ||||
|  | ||||
| **docopt** helps you create *beautiful* command-line interfaces easily: | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/docopt/docopt-go" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	  usage := `Naval Fate. | ||||
|  | ||||
| Usage: | ||||
|   naval_fate ship new <name>... | ||||
|   naval_fate ship <name> move <x> <y> [--speed=<kn>] | ||||
|   naval_fate ship shoot <x> <y> | ||||
|   naval_fate mine (set|remove) <x> <y> [--moored|--drifting] | ||||
|   naval_fate -h | --help | ||||
|   naval_fate --version | ||||
|  | ||||
| Options: | ||||
|   -h --help     Show this screen. | ||||
|   --version     Show version. | ||||
|   --speed=<kn>  Speed in knots [default: 10]. | ||||
|   --moored      Moored (anchored) mine. | ||||
|   --drifting    Drifting mine.` | ||||
|  | ||||
| 	  arguments, _ := docopt.Parse(usage, nil, true, "Naval Fate 2.0", false) | ||||
| 	  fmt.Println(arguments) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **docopt** parses command-line arguments based on a help message. Don't | ||||
| write parser code: a good help message already has all the necessary | ||||
| information in it. | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| ⚠ Use the alias “docopt-go”. To use docopt in your Go code: | ||||
|  | ||||
| ```go | ||||
| import "github.com/docopt/docopt-go" | ||||
| ``` | ||||
|  | ||||
| To install docopt according to your `$GOPATH`: | ||||
|  | ||||
| ```console | ||||
| $ go get github.com/docopt/docopt-go | ||||
| ``` | ||||
|  | ||||
| ## API | ||||
|  | ||||
| ```go | ||||
| func Parse(doc string, argv []string, help bool, version string, | ||||
|     optionsFirst bool, exit ...bool) (map[string]interface{}, error) | ||||
| ``` | ||||
| Parse `argv` based on the command-line interface described in `doc`. | ||||
|  | ||||
| Given a conventional command-line help message, docopt creates a parser and | ||||
| processes the arguments. See | ||||
| https://github.com/docopt/docopt#help-message-format for a description of the | ||||
| help message format. If `argv` is `nil`, `os.Args[1:]` is used. | ||||
|  | ||||
| docopt returns a map of option names to the values parsed from `argv`, and an | ||||
| error or `nil`. | ||||
|  | ||||
| More documentation for docopt is available at | ||||
| [GoDoc.org](https://godoc.org/github.com/docopt/docopt.go). | ||||
|  | ||||
| ## Testing | ||||
|  | ||||
| All tests from the Python version are implemented and passing | ||||
| at [Travis CI](https://travis-ci.org/docopt/docopt.go). New | ||||
| language-agnostic tests have been added | ||||
| to [test_golang.docopt](test_golang.docopt). | ||||
|  | ||||
| To run tests for docopt-go, use `go test`. | ||||
							
								
								
									
										1239
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/docopt.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1239
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/docopt.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										9
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/test_golang.docopt
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/test_golang.docopt
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,9 +0,0 @@ | ||||
| r"""usage: prog [NAME_-2]...""" | ||||
| $ prog 10 20 | ||||
| {"NAME_-2": ["10", "20"]} | ||||
|  | ||||
| $ prog 10 | ||||
| {"NAME_-2": ["10"]} | ||||
|  | ||||
| $ prog | ||||
| {"NAME_-2": []} | ||||
							
								
								
									
										957
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/testcases.docopt
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										957
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/testcases.docopt
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,957 +0,0 @@ | ||||
| r"""Usage: prog | ||||
|  | ||||
| """ | ||||
| $ prog | ||||
| {} | ||||
|  | ||||
| $ prog --xxx | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""Usage: prog [options] | ||||
|  | ||||
| Options: -a  All. | ||||
|  | ||||
| """ | ||||
| $ prog | ||||
| {"-a": false} | ||||
|  | ||||
| $ prog -a | ||||
| {"-a": true} | ||||
|  | ||||
| $ prog -x | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""Usage: prog [options] | ||||
|  | ||||
| Options: --all  All. | ||||
|  | ||||
| """ | ||||
| $ prog | ||||
| {"--all": false} | ||||
|  | ||||
| $ prog --all | ||||
| {"--all": true} | ||||
|  | ||||
| $ prog --xxx | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""Usage: prog [options] | ||||
|  | ||||
| Options: -v, --verbose  Verbose. | ||||
|  | ||||
| """ | ||||
| $ prog --verbose | ||||
| {"--verbose": true} | ||||
|  | ||||
| $ prog --ver | ||||
| {"--verbose": true} | ||||
|  | ||||
| $ prog -v | ||||
| {"--verbose": true} | ||||
|  | ||||
|  | ||||
| r"""Usage: prog [options] | ||||
|  | ||||
| Options: -p PATH | ||||
|  | ||||
| """ | ||||
| $ prog -p home/ | ||||
| {"-p": "home/"} | ||||
|  | ||||
| $ prog -phome/ | ||||
| {"-p": "home/"} | ||||
|  | ||||
| $ prog -p | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""Usage: prog [options] | ||||
|  | ||||
| Options: --path <path> | ||||
|  | ||||
| """ | ||||
| $ prog --path home/ | ||||
| {"--path": "home/"} | ||||
|  | ||||
| $ prog --path=home/ | ||||
| {"--path": "home/"} | ||||
|  | ||||
| $ prog --pa home/ | ||||
| {"--path": "home/"} | ||||
|  | ||||
| $ prog --pa=home/ | ||||
| {"--path": "home/"} | ||||
|  | ||||
| $ prog --path | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""Usage: prog [options] | ||||
|  | ||||
| Options: -p PATH, --path=<path>  Path to files. | ||||
|  | ||||
| """ | ||||
| $ prog -proot | ||||
| {"--path": "root"} | ||||
|  | ||||
|  | ||||
| r"""Usage: prog [options] | ||||
|  | ||||
| Options:    -p --path PATH  Path to files. | ||||
|  | ||||
| """ | ||||
| $ prog -p root | ||||
| {"--path": "root"} | ||||
|  | ||||
| $ prog --path root | ||||
| {"--path": "root"} | ||||
|  | ||||
|  | ||||
| r"""Usage: prog [options] | ||||
|  | ||||
| Options: | ||||
|  -p PATH  Path to files [default: ./] | ||||
|  | ||||
| """ | ||||
| $ prog | ||||
| {"-p": "./"} | ||||
|  | ||||
| $ prog -phome | ||||
| {"-p": "home"} | ||||
|  | ||||
|  | ||||
| r"""UsAgE: prog [options] | ||||
|  | ||||
| OpTiOnS: --path=<files>  Path to files | ||||
|                 [dEfAuLt: /root] | ||||
|  | ||||
| """ | ||||
| $ prog | ||||
| {"--path": "/root"} | ||||
|  | ||||
| $ prog --path=home | ||||
| {"--path": "home"} | ||||
|  | ||||
|  | ||||
| r"""usage: prog [options] | ||||
|  | ||||
| options: | ||||
|     -a        Add | ||||
|     -r        Remote | ||||
|     -m <msg>  Message | ||||
|  | ||||
| """ | ||||
| $ prog -a -r -m Hello | ||||
| {"-a": true, | ||||
|  "-r": true, | ||||
|  "-m": "Hello"} | ||||
|  | ||||
| $ prog -armyourass | ||||
| {"-a": true, | ||||
|  "-r": true, | ||||
|  "-m": "yourass"} | ||||
|  | ||||
| $ prog -a -r | ||||
| {"-a": true, | ||||
|  "-r": true, | ||||
|  "-m": null} | ||||
|  | ||||
|  | ||||
| r"""Usage: prog [options] | ||||
|  | ||||
| Options: --version | ||||
|          --verbose | ||||
|  | ||||
| """ | ||||
| $ prog --version | ||||
| {"--version": true, | ||||
|  "--verbose": false} | ||||
|  | ||||
| $ prog --verbose | ||||
| {"--version": false, | ||||
|  "--verbose": true} | ||||
|  | ||||
| $ prog --ver | ||||
| "user-error" | ||||
|  | ||||
| $ prog --verb | ||||
| {"--version": false, | ||||
|  "--verbose": true} | ||||
|  | ||||
|  | ||||
| r"""usage: prog [-a -r -m <msg>] | ||||
|  | ||||
| options: | ||||
|  -a        Add | ||||
|  -r        Remote | ||||
|  -m <msg>  Message | ||||
|  | ||||
| """ | ||||
| $ prog -armyourass | ||||
| {"-a": true, | ||||
|  "-r": true, | ||||
|  "-m": "yourass"} | ||||
|  | ||||
|  | ||||
| r"""usage: prog [-armmsg] | ||||
|  | ||||
| options: -a        Add | ||||
|          -r        Remote | ||||
|          -m <msg>  Message | ||||
|  | ||||
| """ | ||||
| $ prog -a -r -m Hello | ||||
| {"-a": true, | ||||
|  "-r": true, | ||||
|  "-m": "Hello"} | ||||
|  | ||||
|  | ||||
| r"""usage: prog -a -b | ||||
|  | ||||
| options: | ||||
|  -a | ||||
|  -b | ||||
|  | ||||
| """ | ||||
| $ prog -a -b | ||||
| {"-a": true, "-b": true} | ||||
|  | ||||
| $ prog -b -a | ||||
| {"-a": true, "-b": true} | ||||
|  | ||||
| $ prog -a | ||||
| "user-error" | ||||
|  | ||||
| $ prog | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""usage: prog (-a -b) | ||||
|  | ||||
| options: -a | ||||
|          -b | ||||
|  | ||||
| """ | ||||
| $ prog -a -b | ||||
| {"-a": true, "-b": true} | ||||
|  | ||||
| $ prog -b -a | ||||
| {"-a": true, "-b": true} | ||||
|  | ||||
| $ prog -a | ||||
| "user-error" | ||||
|  | ||||
| $ prog | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""usage: prog [-a] -b | ||||
|  | ||||
| options: -a | ||||
|  -b | ||||
|  | ||||
| """ | ||||
| $ prog -a -b | ||||
| {"-a": true, "-b": true} | ||||
|  | ||||
| $ prog -b -a | ||||
| {"-a": true, "-b": true} | ||||
|  | ||||
| $ prog -a | ||||
| "user-error" | ||||
|  | ||||
| $ prog -b | ||||
| {"-a": false, "-b": true} | ||||
|  | ||||
| $ prog | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""usage: prog [(-a -b)] | ||||
|  | ||||
| options: -a | ||||
|          -b | ||||
|  | ||||
| """ | ||||
| $ prog -a -b | ||||
| {"-a": true, "-b": true} | ||||
|  | ||||
| $ prog -b -a | ||||
| {"-a": true, "-b": true} | ||||
|  | ||||
| $ prog -a | ||||
| "user-error" | ||||
|  | ||||
| $ prog -b | ||||
| "user-error" | ||||
|  | ||||
| $ prog | ||||
| {"-a": false, "-b": false} | ||||
|  | ||||
|  | ||||
| r"""usage: prog (-a|-b) | ||||
|  | ||||
| options: -a | ||||
|          -b | ||||
|  | ||||
| """ | ||||
| $ prog -a -b | ||||
| "user-error" | ||||
|  | ||||
| $ prog | ||||
| "user-error" | ||||
|  | ||||
| $ prog -a | ||||
| {"-a": true, "-b": false} | ||||
|  | ||||
| $ prog -b | ||||
| {"-a": false, "-b": true} | ||||
|  | ||||
|  | ||||
| r"""usage: prog [ -a | -b ] | ||||
|  | ||||
| options: -a | ||||
|          -b | ||||
|  | ||||
| """ | ||||
| $ prog -a -b | ||||
| "user-error" | ||||
|  | ||||
| $ prog | ||||
| {"-a": false, "-b": false} | ||||
|  | ||||
| $ prog -a | ||||
| {"-a": true, "-b": false} | ||||
|  | ||||
| $ prog -b | ||||
| {"-a": false, "-b": true} | ||||
|  | ||||
|  | ||||
| r"""usage: prog <arg>""" | ||||
| $ prog 10 | ||||
| {"<arg>": "10"} | ||||
|  | ||||
| $ prog 10 20 | ||||
| "user-error" | ||||
|  | ||||
| $ prog | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""usage: prog [<arg>]""" | ||||
| $ prog 10 | ||||
| {"<arg>": "10"} | ||||
|  | ||||
| $ prog 10 20 | ||||
| "user-error" | ||||
|  | ||||
| $ prog | ||||
| {"<arg>": null} | ||||
|  | ||||
|  | ||||
| r"""usage: prog <kind> <name> <type>""" | ||||
| $ prog 10 20 40 | ||||
| {"<kind>": "10", "<name>": "20", "<type>": "40"} | ||||
|  | ||||
| $ prog 10 20 | ||||
| "user-error" | ||||
|  | ||||
| $ prog | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""usage: prog <kind> [<name> <type>]""" | ||||
| $ prog 10 20 40 | ||||
| {"<kind>": "10", "<name>": "20", "<type>": "40"} | ||||
|  | ||||
| $ prog 10 20 | ||||
| {"<kind>": "10", "<name>": "20", "<type>": null} | ||||
|  | ||||
| $ prog | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""usage: prog [<kind> | <name> <type>]""" | ||||
| $ prog 10 20 40 | ||||
| "user-error" | ||||
|  | ||||
| $ prog 20 40 | ||||
| {"<kind>": null, "<name>": "20", "<type>": "40"} | ||||
|  | ||||
| $ prog | ||||
| {"<kind>": null, "<name>": null, "<type>": null} | ||||
|  | ||||
|  | ||||
| r"""usage: prog (<kind> --all | <name>) | ||||
|  | ||||
| options: | ||||
|  --all | ||||
|  | ||||
| """ | ||||
| $ prog 10 --all | ||||
| {"<kind>": "10", "--all": true, "<name>": null} | ||||
|  | ||||
| $ prog 10 | ||||
| {"<kind>": null, "--all": false, "<name>": "10"} | ||||
|  | ||||
| $ prog | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""usage: prog [<name> <name>]""" | ||||
| $ prog 10 20 | ||||
| {"<name>": ["10", "20"]} | ||||
|  | ||||
| $ prog 10 | ||||
| {"<name>": ["10"]} | ||||
|  | ||||
| $ prog | ||||
| {"<name>": []} | ||||
|  | ||||
|  | ||||
| r"""usage: prog [(<name> <name>)]""" | ||||
| $ prog 10 20 | ||||
| {"<name>": ["10", "20"]} | ||||
|  | ||||
| $ prog 10 | ||||
| "user-error" | ||||
|  | ||||
| $ prog | ||||
| {"<name>": []} | ||||
|  | ||||
|  | ||||
| r"""usage: prog NAME...""" | ||||
| $ prog 10 20 | ||||
| {"NAME": ["10", "20"]} | ||||
|  | ||||
| $ prog 10 | ||||
| {"NAME": ["10"]} | ||||
|  | ||||
| $ prog | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""usage: prog [NAME]...""" | ||||
| $ prog 10 20 | ||||
| {"NAME": ["10", "20"]} | ||||
|  | ||||
| $ prog 10 | ||||
| {"NAME": ["10"]} | ||||
|  | ||||
| $ prog | ||||
| {"NAME": []} | ||||
|  | ||||
|  | ||||
| r"""usage: prog [NAME...]""" | ||||
| $ prog 10 20 | ||||
| {"NAME": ["10", "20"]} | ||||
|  | ||||
| $ prog 10 | ||||
| {"NAME": ["10"]} | ||||
|  | ||||
| $ prog | ||||
| {"NAME": []} | ||||
|  | ||||
|  | ||||
| r"""usage: prog [NAME [NAME ...]]""" | ||||
| $ prog 10 20 | ||||
| {"NAME": ["10", "20"]} | ||||
|  | ||||
| $ prog 10 | ||||
| {"NAME": ["10"]} | ||||
|  | ||||
| $ prog | ||||
| {"NAME": []} | ||||
|  | ||||
|  | ||||
| r"""usage: prog (NAME | --foo NAME) | ||||
|  | ||||
| options: --foo | ||||
|  | ||||
| """ | ||||
| $ prog 10 | ||||
| {"NAME": "10", "--foo": false} | ||||
|  | ||||
| $ prog --foo 10 | ||||
| {"NAME": "10", "--foo": true} | ||||
|  | ||||
| $ prog --foo=10 | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""usage: prog (NAME | --foo) [--bar | NAME] | ||||
|  | ||||
| options: --foo | ||||
| options: --bar | ||||
|  | ||||
| """ | ||||
| $ prog 10 | ||||
| {"NAME": ["10"], "--foo": false, "--bar": false} | ||||
|  | ||||
| $ prog 10 20 | ||||
| {"NAME": ["10", "20"], "--foo": false, "--bar": false} | ||||
|  | ||||
| $ prog --foo --bar | ||||
| {"NAME": [], "--foo": true, "--bar": true} | ||||
|  | ||||
|  | ||||
| r"""Naval Fate. | ||||
|  | ||||
| Usage: | ||||
|   prog ship new <name>... | ||||
|   prog ship [<name>] move <x> <y> [--speed=<kn>] | ||||
|   prog ship shoot <x> <y> | ||||
|   prog mine (set|remove) <x> <y> [--moored|--drifting] | ||||
|   prog -h | --help | ||||
|   prog --version | ||||
|  | ||||
| Options: | ||||
|   -h --help     Show this screen. | ||||
|   --version     Show version. | ||||
|   --speed=<kn>  Speed in knots [default: 10]. | ||||
|   --moored      Mored (anchored) mine. | ||||
|   --drifting    Drifting mine. | ||||
|  | ||||
| """ | ||||
| $ prog ship Guardian move 150 300 --speed=20 | ||||
| {"--drifting": false, | ||||
|  "--help": false, | ||||
|  "--moored": false, | ||||
|  "--speed": "20", | ||||
|  "--version": false, | ||||
|  "<name>": ["Guardian"], | ||||
|  "<x>": "150", | ||||
|  "<y>": "300", | ||||
|  "mine": false, | ||||
|  "move": true, | ||||
|  "new": false, | ||||
|  "remove": false, | ||||
|  "set": false, | ||||
|  "ship": true, | ||||
|  "shoot": false} | ||||
|  | ||||
|  | ||||
| r"""usage: prog --hello""" | ||||
| $ prog --hello | ||||
| {"--hello": true} | ||||
|  | ||||
|  | ||||
| r"""usage: prog [--hello=<world>]""" | ||||
| $ prog | ||||
| {"--hello": null} | ||||
|  | ||||
| $ prog --hello wrld | ||||
| {"--hello": "wrld"} | ||||
|  | ||||
|  | ||||
| r"""usage: prog [-o]""" | ||||
| $ prog | ||||
| {"-o": false} | ||||
|  | ||||
| $ prog -o | ||||
| {"-o": true} | ||||
|  | ||||
|  | ||||
| r"""usage: prog [-opr]""" | ||||
| $ prog -op | ||||
| {"-o": true, "-p": true, "-r": false} | ||||
|  | ||||
|  | ||||
| r"""usage: prog --aabb | --aa""" | ||||
| $ prog --aa | ||||
| {"--aabb": false, "--aa": true} | ||||
|  | ||||
| $ prog --a | ||||
| "user-error"  # not a unique prefix | ||||
|  | ||||
| # | ||||
| # Counting number of flags | ||||
| # | ||||
|  | ||||
| r"""Usage: prog -v""" | ||||
| $ prog -v | ||||
| {"-v": true} | ||||
|  | ||||
|  | ||||
| r"""Usage: prog [-v -v]""" | ||||
| $ prog | ||||
| {"-v": 0} | ||||
|  | ||||
| $ prog -v | ||||
| {"-v": 1} | ||||
|  | ||||
| $ prog -vv | ||||
| {"-v": 2} | ||||
|  | ||||
|  | ||||
| r"""Usage: prog -v ...""" | ||||
| $ prog | ||||
| "user-error" | ||||
|  | ||||
| $ prog -v | ||||
| {"-v": 1} | ||||
|  | ||||
| $ prog -vv | ||||
| {"-v": 2} | ||||
|  | ||||
| $ prog -vvvvvv | ||||
| {"-v": 6} | ||||
|  | ||||
|  | ||||
| r"""Usage: prog [-v | -vv | -vvv] | ||||
|  | ||||
| This one is probably most readable user-friednly variant. | ||||
|  | ||||
| """ | ||||
| $ prog | ||||
| {"-v": 0} | ||||
|  | ||||
| $ prog -v | ||||
| {"-v": 1} | ||||
|  | ||||
| $ prog -vv | ||||
| {"-v": 2} | ||||
|  | ||||
| $ prog -vvvv | ||||
| "user-error" | ||||
|  | ||||
|  | ||||
| r"""usage: prog [--ver --ver]""" | ||||
| $ prog --ver --ver | ||||
| {"--ver": 2} | ||||
|  | ||||
|  | ||||
| # | ||||
| # Counting commands | ||||
| # | ||||
|  | ||||
| r"""usage: prog [go]""" | ||||
| $ prog go | ||||
| {"go": true} | ||||
|  | ||||
|  | ||||
| r"""usage: prog [go go]""" | ||||
| $ prog | ||||
| {"go": 0} | ||||
|  | ||||
| $ prog go | ||||
| {"go": 1} | ||||
|  | ||||
| $ prog go go | ||||
| {"go": 2} | ||||
|  | ||||
| $ prog go go go | ||||
| "user-error" | ||||
|  | ||||
| r"""usage: prog go...""" | ||||
| $ prog go go go go go | ||||
| {"go": 5} | ||||
|  | ||||
| # | ||||
| # [options] does not include options from usage-pattern | ||||
| # | ||||
| r"""usage: prog [options] [-a] | ||||
|  | ||||
| options: -a | ||||
|          -b | ||||
| """ | ||||
| $ prog -a | ||||
| {"-a": true, "-b": false} | ||||
|  | ||||
| $ prog -aa | ||||
| "user-error" | ||||
|  | ||||
| # | ||||
| # Test [options] shourtcut | ||||
| # | ||||
|  | ||||
| r"""Usage: prog [options] A | ||||
| Options: | ||||
|     -q  Be quiet | ||||
|     -v  Be verbose. | ||||
|  | ||||
| """ | ||||
| $ prog arg | ||||
| {"A": "arg", "-v": false, "-q": false} | ||||
|  | ||||
| $ prog -v arg | ||||
| {"A": "arg", "-v": true, "-q": false} | ||||
|  | ||||
| $ prog -q arg | ||||
| {"A": "arg", "-v": false, "-q": true} | ||||
|  | ||||
| # | ||||
| # Test single dash | ||||
| # | ||||
|  | ||||
| r"""usage: prog [-]""" | ||||
|  | ||||
| $ prog - | ||||
| {"-": true} | ||||
|  | ||||
| $ prog | ||||
| {"-": false} | ||||
|  | ||||
| # | ||||
| # If argument is repeated, its value should always be a list | ||||
| # | ||||
|  | ||||
| r"""usage: prog [NAME [NAME ...]]""" | ||||
|  | ||||
| $ prog a b | ||||
| {"NAME": ["a", "b"]} | ||||
|  | ||||
| $ prog | ||||
| {"NAME": []} | ||||
|  | ||||
| # | ||||
| # Option's argument defaults to null/None | ||||
| # | ||||
|  | ||||
| r"""usage: prog [options] | ||||
| options: | ||||
|  -a        Add | ||||
|  -m <msg>  Message | ||||
|  | ||||
| """ | ||||
| $ prog -a | ||||
| {"-m": null, "-a": true} | ||||
|  | ||||
| # | ||||
| # Test options without description | ||||
| # | ||||
|  | ||||
| r"""usage: prog --hello""" | ||||
| $ prog --hello | ||||
| {"--hello": true} | ||||
|  | ||||
| r"""usage: prog [--hello=<world>]""" | ||||
| $ prog | ||||
| {"--hello": null} | ||||
|  | ||||
| $ prog --hello wrld | ||||
| {"--hello": "wrld"} | ||||
|  | ||||
| r"""usage: prog [-o]""" | ||||
| $ prog | ||||
| {"-o": false} | ||||
|  | ||||
| $ prog -o | ||||
| {"-o": true} | ||||
|  | ||||
| r"""usage: prog [-opr]""" | ||||
| $ prog -op | ||||
| {"-o": true, "-p": true, "-r": false} | ||||
|  | ||||
| r"""usage: git [-v | --verbose]""" | ||||
| $ prog -v | ||||
| {"-v": true, "--verbose": false} | ||||
|  | ||||
| r"""usage: git remote [-v | --verbose]""" | ||||
| $ prog remote -v | ||||
| {"remote": true, "-v": true, "--verbose": false} | ||||
|  | ||||
| # | ||||
| # Test empty usage pattern | ||||
| # | ||||
|  | ||||
| r"""usage: prog""" | ||||
| $ prog | ||||
| {} | ||||
|  | ||||
| r"""usage: prog | ||||
|            prog <a> <b> | ||||
| """ | ||||
| $ prog 1 2 | ||||
| {"<a>": "1", "<b>": "2"} | ||||
|  | ||||
| $ prog | ||||
| {"<a>": null, "<b>": null} | ||||
|  | ||||
| r"""usage: prog <a> <b> | ||||
|            prog | ||||
| """ | ||||
| $ prog | ||||
| {"<a>": null, "<b>": null} | ||||
|  | ||||
| # | ||||
| # Option's argument should not capture default value from usage pattern | ||||
| # | ||||
|  | ||||
| r"""usage: prog [--file=<f>]""" | ||||
| $ prog | ||||
| {"--file": null} | ||||
|  | ||||
| r"""usage: prog [--file=<f>] | ||||
|  | ||||
| options: --file <a> | ||||
|  | ||||
| """ | ||||
| $ prog | ||||
| {"--file": null} | ||||
|  | ||||
| r"""Usage: prog [-a <host:port>] | ||||
|  | ||||
| Options: -a, --address <host:port>  TCP address [default: localhost:6283]. | ||||
|  | ||||
| """ | ||||
| $ prog | ||||
| {"--address": "localhost:6283"} | ||||
|  | ||||
| # | ||||
| # If option with argument could be repeated, | ||||
| # its arguments should be accumulated into a list | ||||
| # | ||||
|  | ||||
| r"""usage: prog --long=<arg> ...""" | ||||
|  | ||||
| $ prog --long one | ||||
| {"--long": ["one"]} | ||||
|  | ||||
| $ prog --long one --long two | ||||
| {"--long": ["one", "two"]} | ||||
|  | ||||
| # | ||||
| # Test multiple elements repeated at once | ||||
| # | ||||
|  | ||||
| r"""usage: prog (go <direction> --speed=<km/h>)...""" | ||||
| $ prog  go left --speed=5  go right --speed=9 | ||||
| {"go": 2, "<direction>": ["left", "right"], "--speed": ["5", "9"]} | ||||
|  | ||||
| # | ||||
| # Required options should work with option shortcut | ||||
| # | ||||
|  | ||||
| r"""usage: prog [options] -a | ||||
|  | ||||
| options: -a | ||||
|  | ||||
| """ | ||||
| $ prog -a | ||||
| {"-a": true} | ||||
|  | ||||
| # | ||||
| # If option could be repeated its defaults should be split into a list | ||||
| # | ||||
|  | ||||
| r"""usage: prog [-o <o>]... | ||||
|  | ||||
| options: -o <o>  [default: x] | ||||
|  | ||||
| """ | ||||
| $ prog -o this -o that | ||||
| {"-o": ["this", "that"]} | ||||
|  | ||||
| $ prog | ||||
| {"-o": ["x"]} | ||||
|  | ||||
| r"""usage: prog [-o <o>]... | ||||
|  | ||||
| options: -o <o>  [default: x y] | ||||
|  | ||||
| """ | ||||
| $ prog -o this | ||||
| {"-o": ["this"]} | ||||
|  | ||||
| $ prog | ||||
| {"-o": ["x", "y"]} | ||||
|  | ||||
| # | ||||
| # Test stacked option's argument | ||||
| # | ||||
|  | ||||
| r"""usage: prog -pPATH | ||||
|  | ||||
| options: -p PATH | ||||
|  | ||||
| """ | ||||
| $ prog -pHOME | ||||
| {"-p": "HOME"} | ||||
|  | ||||
| # | ||||
| # Issue 56: Repeated mutually exclusive args give nested lists sometimes | ||||
| # | ||||
|  | ||||
| r"""Usage: foo (--xx=x|--yy=y)...""" | ||||
| $ prog --xx=1 --yy=2 | ||||
| {"--xx": ["1"], "--yy": ["2"]} | ||||
|  | ||||
| # | ||||
| # POSIXly correct tokenization | ||||
| # | ||||
|  | ||||
| r"""usage: prog [<input file>]""" | ||||
| $ prog f.txt | ||||
| {"<input file>": "f.txt"} | ||||
|  | ||||
| r"""usage: prog [--input=<file name>]...""" | ||||
| $ prog --input a.txt --input=b.txt | ||||
| {"--input": ["a.txt", "b.txt"]} | ||||
|  | ||||
| # | ||||
| # Issue 85: `[options]` shourtcut with multiple subcommands | ||||
| # | ||||
|  | ||||
| r"""usage: prog good [options] | ||||
|            prog fail [options] | ||||
|  | ||||
| options: --loglevel=N | ||||
|  | ||||
| """ | ||||
| $ prog fail --loglevel 5 | ||||
| {"--loglevel": "5", "fail": true, "good": false} | ||||
|  | ||||
| # | ||||
| # Usage-section syntax | ||||
| # | ||||
|  | ||||
| r"""usage:prog --foo""" | ||||
| $ prog --foo | ||||
| {"--foo": true} | ||||
|  | ||||
| r"""PROGRAM USAGE: prog --foo""" | ||||
| $ prog --foo | ||||
| {"--foo": true} | ||||
|  | ||||
| r"""Usage: prog --foo | ||||
|            prog --bar | ||||
| NOT PART OF SECTION""" | ||||
| $ prog --foo | ||||
| {"--foo": true, "--bar": false} | ||||
|  | ||||
| r"""Usage: | ||||
|  prog --foo | ||||
|  prog --bar | ||||
|  | ||||
| NOT PART OF SECTION""" | ||||
| $ prog --foo | ||||
| {"--foo": true, "--bar": false} | ||||
|  | ||||
| r"""Usage: | ||||
|  prog --foo | ||||
|  prog --bar | ||||
| NOT PART OF SECTION""" | ||||
| $ prog --foo | ||||
| {"--foo": true, "--bar": false} | ||||
|  | ||||
| # | ||||
| # Options-section syntax | ||||
| # | ||||
|  | ||||
| r"""Usage: prog [options] | ||||
|  | ||||
| global options: --foo | ||||
| local options: --baz | ||||
|                --bar | ||||
| other options: | ||||
|  --egg | ||||
|  --spam | ||||
| -not-an-option- | ||||
|  | ||||
| """ | ||||
| $ prog --baz --egg | ||||
| {"--foo": false, "--baz": true, "--bar": false, "--egg": true, "--spam": false} | ||||
							
								
								
									
										44
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,21 +1,43 @@ | ||||
| export PATH := $(GOPATH)/bin:$(PATH) | ||||
| export NEW_GOPATH := $(shell pwd) | ||||
|  | ||||
| all: build | ||||
| all: fmt build | ||||
|  | ||||
| build: godep fmt frps frpc | ||||
| build: frps frpc | ||||
|  | ||||
| godep: | ||||
| 	@go get github.com/tools/godep | ||||
| # compile assets into binary file | ||||
| file: | ||||
| 	rm -rf ./assets/frps/static/* | ||||
| 	rm -rf ./assets/frpc/static/* | ||||
| 	cp -rf ./web/frps/dist/* ./assets/frps/static | ||||
| 	cp -rf ./web/frpc/dist/* ./assets/frpc/static | ||||
| 	rm -rf ./assets/frps/statik | ||||
| 	rm -rf ./assets/frpc/statik | ||||
| 	go generate ./assets/... | ||||
|  | ||||
| fmt: | ||||
| 	GOPATH=$(NEW_GOPATH) godep go fmt ./... | ||||
|  | ||||
| 	go fmt ./... | ||||
| 	 | ||||
| frps: | ||||
| 	GOPATH=$(NEW_GOPATH) godep go build -o bin/frps ./src/frp/cmd/frps | ||||
| 	go build -o bin/frps ./cmd/frps | ||||
|  | ||||
| frpc: | ||||
| 	GOPATH=$(NEW_GOPATH) godep go build -o bin/frpc ./src/frp/cmd/frpc | ||||
| 	go build -o bin/frpc ./cmd/frpc | ||||
|  | ||||
| test: | ||||
| 	@GOPATH=$(NEW_GOPATH) godep go test -v ./... | ||||
| test: gotest | ||||
|  | ||||
| gotest: | ||||
| 	go test -v --cover ./assets/... | ||||
| 	go test -v --cover ./client/... | ||||
| 	go test -v --cover ./cmd/... | ||||
| 	go test -v --cover ./models/... | ||||
| 	go test -v --cover ./server/... | ||||
| 	go test -v --cover ./utils/... | ||||
|  | ||||
| ci: | ||||
| 	go test -count=1 -p=1 -v ./tests/... | ||||
|  | ||||
| alltest: gotest ci | ||||
| 	 | ||||
| clean: | ||||
| 	rm -f ./bin/frpc | ||||
| 	rm -f ./bin/frps | ||||
|   | ||||
							
								
								
									
										37
									
								
								Makefile.cross-compiles
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								Makefile.cross-compiles
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| export PATH := $(GOPATH)/bin:$(PATH) | ||||
| LDFLAGS := -s -w | ||||
|  | ||||
| all: build | ||||
|  | ||||
| build: app | ||||
|  | ||||
| app: | ||||
| 	env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_darwin_amd64 ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_darwin_amd64 ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_386 ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_386 ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_freebsd_amd64 ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_freebsd_amd64 ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_386 ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_386 ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_amd64 ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_arm64 ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_arm64 ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_386.exe ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_386.exe ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frpc_windows_amd64.exe ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_windows_amd64.exe ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64 ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64 ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips64le ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips64le ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mips ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mips ./cmd/frps | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frpc_linux_mipsle ./cmd/frpc | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./frps_linux_mipsle ./cmd/frps | ||||
|  | ||||
| temp: | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps | ||||
							
								
								
									
										767
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										767
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,42 +1,769 @@ | ||||
| # frp | ||||
|  | ||||
| [](https://travis-ci.org/fatedier/frp) | ||||
| [](https://travis-ci.org/fatedier/frp) | ||||
|  | ||||
| [README](README.md) | [中文文档](README_zh.md) | ||||
|  | ||||
| ## What is frp? | ||||
|  | ||||
| frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. | ||||
| frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. As of now, it supports tcp & udp, as well as http and https protocols, where requests can be forwarded to internal services by domain name. | ||||
|  | ||||
| Now it also try to support p2p connect. | ||||
|  | ||||
| ## Table of Contents | ||||
|  | ||||
| <!-- vim-markdown-toc GFM --> | ||||
|  | ||||
| * [Status](#status) | ||||
| * [Architecture](#architecture) | ||||
| * [Example Usage](#example-usage) | ||||
|     * [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh) | ||||
|     * [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains) | ||||
|     * [Forward DNS query request](#forward-dns-query-request) | ||||
|     * [Forward unix domain socket](#forward-unix-domain-socket) | ||||
|     * [Expose a simple http file server](#expose-a-simple-http-file-server) | ||||
|     * [Expose your service in security](#expose-your-service-in-security) | ||||
|     * [P2P Mode](#p2p-mode) | ||||
| * [Features](#features) | ||||
|     * [Configuration File](#configuration-file) | ||||
|     * [Configuration file template](#configuration-file-template) | ||||
|     * [Dashboard](#dashboard) | ||||
|     * [Authentication](#authentication) | ||||
|     * [Encryption and Compression](#encryption-and-compression) | ||||
|     * [Hot-Reload frpc configuration](#hot-reload-frpc-configuration) | ||||
|     * [Get proxy status from client](#get-proxy-status-from-client) | ||||
|     * [Port White List](#port-white-list) | ||||
|     * [Port Reuse](#port-reuse) | ||||
|     * [TCP Stream Multiplexing](#tcp-stream-multiplexing) | ||||
|     * [Support KCP Protocol](#support-kcp-protocol) | ||||
|     * [Connection Pool](#connection-pool) | ||||
|     * [Load balancing](#load-balancing) | ||||
|     * [Health Check](#health-check) | ||||
|     * [Rewriting the Host Header](#rewriting-the-host-header) | ||||
|     * [Set Headers In HTTP Request](#set-headers-in-http-request) | ||||
|     * [Get Real IP](#get-real-ip) | ||||
|     * [Password protecting your web service](#password-protecting-your-web-service) | ||||
|     * [Custom subdomain names](#custom-subdomain-names) | ||||
|     * [URL routing](#url-routing) | ||||
|     * [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy) | ||||
|     * [Range ports mapping](#range-ports-mapping) | ||||
|     * [Plugin](#plugin) | ||||
| * [Development Plan](#development-plan) | ||||
| * [Contributing](#contributing) | ||||
| * [Donation](#donation) | ||||
|     * [AliPay](#alipay) | ||||
|     * [Wechat Pay](#wechat-pay) | ||||
|     * [Paypal](#paypal) | ||||
|  | ||||
| <!-- vim-markdown-toc --> | ||||
|  | ||||
| ## Status | ||||
|  | ||||
| frp is under development and you can try it with latest release version.Master branch for releasing stable version when dev branch for developing. | ||||
| frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing. | ||||
|  | ||||
| **We may change any protocol and can't promise backward compatible before version 1.x.** | ||||
|  | ||||
| ## Quick Start | ||||
|  | ||||
| Read the [QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh.md) | ||||
| **We may change any protocol and can't promise backward compatible. Please check the release log when upgrading.** | ||||
|  | ||||
| ## Architecture | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## What can I do with frp? | ||||
| ## Example Usage | ||||
|  | ||||
| * Expose any http service behind a NAT or firewall to the internet by a server with public IP address. | ||||
| * Expose any tcp service behind a NAT or firewall to the internet by a server with public IP address. | ||||
| * Inspect all http requests/responses that are transmitted over the tunnel(future). | ||||
| Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your os and arch. | ||||
|  | ||||
| Put **frps** and **frps.ini** to your server with public IP. | ||||
|  | ||||
| Put **frpc** and **frpc.ini** to your server in LAN. | ||||
|  | ||||
| ### Access your computer in LAN by SSH | ||||
|  | ||||
| 1. Modify frps.ini: | ||||
|  | ||||
|   ```ini | ||||
|   # frps.ini | ||||
|   [common] | ||||
|   bind_port = 7000 | ||||
|   ``` | ||||
|  | ||||
| 2. Start frps: | ||||
|  | ||||
|   `./frps -c ./frps.ini` | ||||
|  | ||||
| 3. Modify frpc.ini, `server_addr` is your frps's server IP: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [ssh] | ||||
|   type = tcp | ||||
|   local_ip = 127.0.0.1 | ||||
|   local_port = 22 | ||||
|   remote_port = 6000 | ||||
|   ``` | ||||
|  | ||||
| 4. Start frpc: | ||||
|  | ||||
|   `./frpc -c ./frpc.ini` | ||||
|  | ||||
| 5. Connect to server in LAN by ssh assuming that username is test: | ||||
|  | ||||
|   `ssh -oPort=6000 test@x.x.x.x` | ||||
|  | ||||
| ### Visit your web service in LAN by custom domains | ||||
|  | ||||
| Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local ip. | ||||
|  | ||||
| However, we can expose a http or https service using frp. | ||||
|  | ||||
| 1. Modify frps.ini, configure http port 8080: | ||||
|  | ||||
|   ```ini | ||||
|   # frps.ini | ||||
|   [common] | ||||
|   bind_port = 7000 | ||||
|   vhost_http_port = 8080 | ||||
|   ``` | ||||
|  | ||||
| 2. Start frps: | ||||
|  | ||||
|   `./frps -c ./frps.ini` | ||||
|  | ||||
| 3. Modify frpc.ini and set remote frps server's IP as x.x.x.x. The `local_port` is the port of your web service: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [web] | ||||
|   type = http | ||||
|   local_port = 80 | ||||
|   custom_domains = www.yourdomain.com | ||||
|   ``` | ||||
|  | ||||
| 4. Start frpc: | ||||
|  | ||||
|   `./frpc -c ./frpc.ini` | ||||
|  | ||||
| 5. Resolve A record of `www.yourdomain.com` to IP `x.x.x.x` or CNAME record to your origin domain. | ||||
|  | ||||
| 6. Now visit your local web service using url `http://www.yourdomain.com:8080`. | ||||
|  | ||||
| ### Forward DNS query request | ||||
|  | ||||
| 1. Modify frps.ini: | ||||
|  | ||||
|   ```ini | ||||
|   # frps.ini | ||||
|   [common] | ||||
|   bind_port = 7000 | ||||
|   ``` | ||||
|  | ||||
| 2. Start frps: | ||||
|  | ||||
|   `./frps -c ./frps.ini` | ||||
|  | ||||
| 3. Modify frpc.ini, set remote frps's server IP as x.x.x.x, forward dns query request to google dns server `8.8.8.8:53`: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [dns] | ||||
|   type = udp | ||||
|   local_ip = 8.8.8.8 | ||||
|   local_port = 53 | ||||
|   remote_port = 6000 | ||||
|   ``` | ||||
|  | ||||
| 4. Start frpc: | ||||
|  | ||||
|   `./frpc -c ./frpc.ini` | ||||
|  | ||||
| 5. Send dns query request by dig: | ||||
|  | ||||
|   `dig @x.x.x.x -p 6000 www.google.com` | ||||
|  | ||||
| ### Forward unix domain socket | ||||
|  | ||||
| Using tcp port to connect unix domain socket like docker daemon. | ||||
|  | ||||
| Configure frps same as above. | ||||
|  | ||||
| 1. Start frpc with configurations: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [unix_domain_socket] | ||||
|   type = tcp | ||||
|   remote_port = 6000 | ||||
|   plugin = unix_domain_socket | ||||
|   plugin_unix_path = /var/run/docker.sock | ||||
|   ``` | ||||
|  | ||||
| 2. Get docker version by curl command: | ||||
|  | ||||
|   `curl http://x.x.x.x:6000/version` | ||||
|  | ||||
| ### Expose a simple http file server | ||||
|  | ||||
| A simple way to visit files in the LAN. | ||||
|  | ||||
| Configure frps same as above. | ||||
|  | ||||
| 1. Start frpc with configurations: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [test_static_file] | ||||
|   type = tcp | ||||
|   remote_port = 6000 | ||||
|   plugin = static_file | ||||
|   plugin_local_path = /tmp/file | ||||
|   plugin_strip_prefix = static | ||||
|   plugin_http_user = abc | ||||
|   plugin_http_passwd = abc | ||||
|   ``` | ||||
|  | ||||
| 2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`. | ||||
|  | ||||
| ### Expose your service in security | ||||
|  | ||||
| For some services, if expose them to the public network directly will be a security risk. | ||||
|  | ||||
| **stcp(secret tcp)** help you create a proxy avoiding any one can access it. | ||||
|  | ||||
| Configure frps same as above. | ||||
|  | ||||
| 1. Start frpc, forward ssh port and `remote_port` is useless: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [secret_ssh] | ||||
|   type = stcp | ||||
|   sk = abcdefg | ||||
|   local_ip = 127.0.0.1 | ||||
|   local_port = 22 | ||||
|   ``` | ||||
|  | ||||
| 2. Start another frpc in which you want to connect this ssh server: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [secret_ssh_visitor] | ||||
|   type = stcp | ||||
|   role = visitor | ||||
|   server_name = secret_ssh | ||||
|   sk = abcdefg | ||||
|   bind_addr = 127.0.0.1 | ||||
|   bind_port = 6000 | ||||
|   ``` | ||||
|  | ||||
| 3. Connect to server in LAN by ssh assuming that username is test: | ||||
|  | ||||
|   `ssh -oPort=6000 test@127.0.0.1` | ||||
|  | ||||
| ### P2P Mode | ||||
|  | ||||
| **xtcp** is designed for transmitting a large amount of data directly between two client. | ||||
|  | ||||
| Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work. | ||||
|  | ||||
| 1. Configure a udp port for xtcp: | ||||
|  | ||||
|   ```ini | ||||
|   bind_udp_port = 7001 | ||||
|   ``` | ||||
|  | ||||
| 2. Start frpc, forward ssh port and `remote_port` is useless: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [p2p_ssh] | ||||
|   type = xtcp | ||||
|   sk = abcdefg | ||||
|   local_ip = 127.0.0.1 | ||||
|   local_port = 22 | ||||
|   ``` | ||||
|  | ||||
| 3. Start another frpc in which you want to connect this ssh server: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [p2p_ssh_visitor] | ||||
|   type = xtcp | ||||
|   role = visitor | ||||
|   server_name = p2p_ssh | ||||
|   sk = abcdefg | ||||
|   bind_addr = 127.0.0.1 | ||||
|   bind_port = 6000 | ||||
|   ``` | ||||
|  | ||||
| 4. Connect to server in LAN by ssh assuming that username is test: | ||||
|  | ||||
|   `ssh -oPort=6000 test@127.0.0.1` | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| ### Configuration File | ||||
|  | ||||
| You can find features which this document not metioned from full example configuration files. | ||||
|  | ||||
| [frps full configuration file](./conf/frps_full.ini) | ||||
|  | ||||
| [frpc full configuration file](./conf/frpc_full.ini) | ||||
|  | ||||
| ### Configuration file template | ||||
|  | ||||
| Configuration file tempalte can be rendered using os environments. Template uses Go's standard format. | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [common] | ||||
| server_addr = {{ .Envs.FRP_SERVER_ADDR }} | ||||
| server_port = 7000 | ||||
|  | ||||
| [ssh] | ||||
| type = tcp | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 22 | ||||
| remote_port = {{ .Envs.FRP_SSH_REMOTE_PORT }} | ||||
| ``` | ||||
|  | ||||
| Start frpc program: | ||||
|  | ||||
| ``` | ||||
| export FRP_SERVER_ADDR="x.x.x.x" | ||||
| export FRP_SSH_REMOTE_PORT="6000" | ||||
| ./frpc -c ./frpc.ini | ||||
| ``` | ||||
|  | ||||
| frpc will auto render configuration file template using os environments. | ||||
| All environments has prefix `.Envs`. | ||||
|  | ||||
| ### Dashboard | ||||
|  | ||||
| Check frp's status and proxies's statistics information by Dashboard. | ||||
|  | ||||
| Configure a port for dashboard to enable this feature: | ||||
|  | ||||
| ```ini | ||||
| [common] | ||||
| dashboard_port = 7500 | ||||
| # dashboard's username and password are both optional,if not set, default is admin. | ||||
| dashboard_user = admin | ||||
| dashboard_pwd = admin | ||||
| ``` | ||||
|  | ||||
| Then visit `http://[server_addr]:7500` to see dashboard, default username and password are both `admin`. | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Authentication | ||||
|  | ||||
| `token` in frps.ini and frpc.ini should be same. | ||||
|  | ||||
| ### Encryption and Compression | ||||
|  | ||||
| Defalut value is false, you could decide if the proxy will use encryption or compression: | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [ssh] | ||||
| type = tcp | ||||
| local_port = 22 | ||||
| remote_port = 6000 | ||||
| use_encryption = true | ||||
| use_compression = true | ||||
| ``` | ||||
|  | ||||
| ### Hot-Reload frpc configuration | ||||
|  | ||||
| First you need to set admin port in frpc's configure file to let it provide HTTP API for more features. | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [common] | ||||
| admin_addr = 127.0.0.1 | ||||
| admin_port = 7400 | ||||
| ``` | ||||
|  | ||||
| Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let frpc create or update or delete proxies. | ||||
|  | ||||
| **Note that parameters in [common] section won't be modified except 'start' now.** | ||||
|  | ||||
| ### Get proxy status from client | ||||
|  | ||||
| Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configure file. | ||||
|  | ||||
| ### Port White List | ||||
|  | ||||
| `allow_ports` in frps.ini is used for preventing abuse of ports: | ||||
|  | ||||
| ```ini | ||||
| # frps.ini | ||||
| [common] | ||||
| allow_ports = 2000-3000,3001,3003,4000-50000 | ||||
| ``` | ||||
|  | ||||
| `allow_ports` consists of a specific port or a range of ports divided by `,`. | ||||
|  | ||||
| ### Port Reuse | ||||
|  | ||||
| Now `vhost_http_port` and `vhost_https_port` in frps can use same port with `bind_port`. frps will detect connection's protocol and handle it correspondingly. | ||||
|  | ||||
| We would like to try to allow multiple proxies bind a same remote port with different protocols in the future. | ||||
|  | ||||
| ### TCP Stream Multiplexing | ||||
|  | ||||
| frp support tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing. All user requests to same frpc can use only one tcp connection. | ||||
|  | ||||
| You can disable this feature by modify frps.ini and frpc.ini: | ||||
|  | ||||
| ```ini | ||||
| # frps.ini and frpc.ini, must be same | ||||
| [common] | ||||
| tcp_mux = false | ||||
| ``` | ||||
|  | ||||
| ### Support KCP Protocol | ||||
|  | ||||
| frp support kcp protocol since v0.12.0. | ||||
|  | ||||
| KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP. | ||||
|  | ||||
| Using kcp in frp: | ||||
|  | ||||
| 1. Enable kcp protocol in frps: | ||||
|  | ||||
|   ```ini | ||||
|   # frps.ini | ||||
|   [common] | ||||
|   bind_port = 7000 | ||||
|   # kcp needs to bind a udp port, it can be same with 'bind_port' | ||||
|   kcp_bind_port = 7000 | ||||
|   ``` | ||||
|  | ||||
| 2. Configure the protocol used in frpc to connect frps: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   # specify the 'kcp_bind_port' in frps | ||||
|   server_port = 7000 | ||||
|   protocol = kcp | ||||
|   ``` | ||||
|  | ||||
| ### Connection Pool | ||||
|  | ||||
| By default, frps send message to frpc for create a new connection to backward service when getting an user request.If a proxy's connection pool is enabled, there will be a specified number of connections pre-established. | ||||
|  | ||||
| This feature is fit for a large number of short connections. | ||||
|  | ||||
| 1. Configure the limit of pool count each proxy can use in frps.ini: | ||||
|  | ||||
|   ```ini | ||||
|   # frps.ini | ||||
|   [common] | ||||
|   max_pool_count = 5 | ||||
|   ``` | ||||
|  | ||||
| 2. Enable and specify the number of connection pool: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   pool_count = 1 | ||||
|   ``` | ||||
|  | ||||
| ### Load balancing | ||||
|  | ||||
| Load balancing is supported by `group`. | ||||
| This feature is available only for type `tcp` now. | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [test1] | ||||
| type = tcp | ||||
| local_port = 8080 | ||||
| remote_port = 80 | ||||
| group = web | ||||
| group_key = 123 | ||||
|  | ||||
| [test2] | ||||
| type = tcp | ||||
| local_port = 8081 | ||||
| remote_port = 80 | ||||
| group = web | ||||
| group_key = 123 | ||||
| ``` | ||||
|  | ||||
| `group_key` is used for authentication. | ||||
|  | ||||
| Proxies in same group will accept connections from port 80 randomly. | ||||
|  | ||||
| ### Health Check | ||||
|  | ||||
| Health check feature can help you achieve high availability with load balancing. | ||||
|  | ||||
| Add `health_check_type = {type}` to enable health check. | ||||
|  | ||||
| **type** can be tcp or http. | ||||
|  | ||||
| Type tcp will dial the service port and type http will send a http rquest to service and require a 200 response. | ||||
|  | ||||
| Type tcp configuration: | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [test1] | ||||
| type = tcp | ||||
| local_port = 22 | ||||
| remote_port = 6000 | ||||
| # enable tcp health check | ||||
| health_check_type = tcp | ||||
| # dial timeout seconds | ||||
| health_check_timeout_s = 3 | ||||
| # if continuous failed in 3 times, the proxy will be removed from frps | ||||
| health_check_max_failed = 3 | ||||
| # every 10 seconds will do a health check | ||||
| health_check_interval_s = 10 | ||||
| ``` | ||||
|  | ||||
| Type http configuration: | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web] | ||||
| type = http | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 80 | ||||
| custom_domains = test.yourdomain.com | ||||
| # enable http health check | ||||
| health_check_type = http | ||||
| # frpc will send a GET http request '/status' to local http service | ||||
| # http service is alive when it return 2xx http response code | ||||
| health_check_url = /status | ||||
| health_check_interval_s = 10 | ||||
| health_check_max_failed = 3 | ||||
| health_check_timeout_s = 3 | ||||
| ``` | ||||
|  | ||||
| ### Rewriting the Host Header | ||||
|  | ||||
| When forwarding to a local port, frp does not modify the tunneled HTTP requests at all, they are copied to your server byte-for-byte as they are received. Some application servers use the Host header for determining which development site to display. For this reason, frp can rewrite your requests with a modified host header. Use the `host_header_rewrite` switch to rewrite incoming HTTP requests. | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web] | ||||
| type = http | ||||
| local_port = 80 | ||||
| custom_domains = test.yourdomain.com | ||||
| host_header_rewrite = dev.yourdomain.com | ||||
| ``` | ||||
|  | ||||
| If `host_header_rewrite` is specified, the host header will be rewritten to match the hostname portion of the forwarding address. | ||||
|  | ||||
| ### Set Headers In HTTP Request | ||||
|  | ||||
| You can set headers for proxy which type is `http`. | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web] | ||||
| type = http | ||||
| local_port = 80 | ||||
| custom_domains = test.yourdomain.com | ||||
| host_header_rewrite = dev.yourdomain.com | ||||
| header_X-From-Where = frp | ||||
| ``` | ||||
|  | ||||
| Note that params which have prefix `header_` will be added to http request headers. | ||||
| In this example, it will set header `X-From-Where: frp` to http request. | ||||
|  | ||||
| ### Get Real IP | ||||
|  | ||||
| Features for http proxy only. | ||||
|  | ||||
| You can get user's real IP from http request header `X-Forwarded-For` and `X-Real-IP`. | ||||
|  | ||||
| ### Password protecting your web service | ||||
|  | ||||
| Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password. | ||||
|  | ||||
| This enforces HTTP Basic Auth on all requests with the username and password you specify in frpc's configure file. | ||||
|  | ||||
| It can only be enabled when proxy type is http. | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web] | ||||
| type = http | ||||
| local_port = 80 | ||||
| custom_domains = test.yourdomain.com | ||||
| http_user = abc | ||||
| http_pwd = abc | ||||
| ``` | ||||
|  | ||||
| Visit `http://test.yourdomain.com` and now you need to input username and password. | ||||
|  | ||||
| ### Custom subdomain names | ||||
|  | ||||
| It is convenient to use `subdomain` configure for http、https type when many people use one frps server together. | ||||
|  | ||||
| ```ini | ||||
| # frps.ini | ||||
| subdomain_host = frps.com | ||||
| ``` | ||||
|  | ||||
| Resolve `*.frps.com` to the frps server's IP. | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web] | ||||
| type = http | ||||
| local_port = 80 | ||||
| subdomain = test | ||||
| ``` | ||||
|  | ||||
| Now you can visit your web service by host `test.frps.com`. | ||||
|  | ||||
| Note that if `subdomain_host` is not empty, `custom_domains` should not be the subdomain of `subdomain_host`. | ||||
|  | ||||
| ### URL routing | ||||
|  | ||||
| frp support forward http requests to different backward web services by url routing. | ||||
|  | ||||
| `locations` specify the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order. | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web01] | ||||
| type = http | ||||
| local_port = 80 | ||||
| custom_domains = web.yourdomain.com | ||||
| locations = / | ||||
|  | ||||
| [web02] | ||||
| type = http | ||||
| local_port = 81 | ||||
| custom_domains = web.yourdomain.com | ||||
| locations = /news,/about | ||||
| ``` | ||||
| Http requests with url prefix `/news` and `/about` will be forwarded to **web02** and others to **web01**. | ||||
|  | ||||
| ### Connect frps by HTTP PROXY | ||||
|  | ||||
| frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file. | ||||
|  | ||||
| It only works when protocol is tcp. | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [common] | ||||
| server_addr = x.x.x.x | ||||
| server_port = 7000 | ||||
| http_proxy = http://user:pwd@192.168.1.128:8080 | ||||
| ``` | ||||
|  | ||||
| ### Range ports mapping | ||||
|  | ||||
| Proxy name has prefix `range:` will support mapping range ports. | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [range:test_tcp] | ||||
| type = tcp | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 6000-6006,6007 | ||||
| remote_port = 6000-6006,6007 | ||||
| ``` | ||||
|  | ||||
| frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`. | ||||
|  | ||||
| ### Plugin | ||||
|  | ||||
| frpc only forward request to local tcp or udp port by default. | ||||
|  | ||||
| Plugin is used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage). | ||||
|  | ||||
| Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin. | ||||
|  | ||||
| Using plugin **http_proxy**: | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [http_proxy] | ||||
| type = tcp | ||||
| remote_port = 6000 | ||||
| plugin = http_proxy | ||||
| plugin_http_user = abc | ||||
| plugin_http_passwd = abc | ||||
| ``` | ||||
|  | ||||
| `plugin_http_user` and `plugin_http_passwd` are configuration parameters used in `http_proxy` plugin. | ||||
|  | ||||
| ## Development Plan | ||||
|  | ||||
| * Log http request information in frps. | ||||
| * Direct reverse proxy, like haproxy. | ||||
| * kubernetes ingress support. | ||||
|  | ||||
| ## Contributing | ||||
|  | ||||
| Interested in getting involved? We would love to help you! | ||||
| Interested in getting involved? We would like to help you! | ||||
|  | ||||
| * Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider submitting a patch | ||||
| * If you have some wanderful ideas, send email to fatedier@gmail.com. | ||||
| * Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**. | ||||
| * If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request. | ||||
| * Sorry for my poor english and improvement for this document is welcome even some typo fix. | ||||
| * If you have some wonderful ideas, send email to fatedier@gmail.com. | ||||
|  | ||||
| ## Contributors | ||||
| **Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.** | ||||
|  | ||||
| * [fatedier](https://github.com/fatedier) | ||||
| * [Hurricanezwf](https://github.com/Hurricanezwf) | ||||
| * [vashstorm](https://github.com/vashstorm) | ||||
| ## Donation | ||||
|  | ||||
| If frp help you a lot, you can support us by: | ||||
|  | ||||
| frp QQ group: 606194980 | ||||
|  | ||||
| ### AliPay | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Wechat Pay | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Paypal | ||||
|  | ||||
| Donate money by [paypal](https://www.paypal.me/fatedier) to my account **fatedier@gmail.com**. | ||||
|   | ||||
							
								
								
									
										815
									
								
								README_zh.md
									
									
									
									
									
								
							
							
						
						
									
										815
									
								
								README_zh.md
									
									
									
									
									
								
							| @@ -1,40 +1,815 @@ | ||||
| # frp | ||||
|  | ||||
| [](https://travis-ci.org/fatedier/frp) | ||||
| [](https://travis-ci.org/fatedier/frp) | ||||
|  | ||||
| [README](README.md) | [中文文档](README_zh.md) | ||||
|  | ||||
| >frp 是一个高性能的反向代理应用,可以帮助你轻松的进行内网穿透,对外网提供服务。 | ||||
| frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp 协议,为 http 和 https 应用协议提供了额外的能力,且尝试性支持了点对点穿透。 | ||||
|  | ||||
| ## 目录 | ||||
|  | ||||
| <!-- vim-markdown-toc GFM --> | ||||
|  | ||||
| * [开发状态](#开发状态) | ||||
| * [架构](#架构) | ||||
| * [使用示例](#使用示例) | ||||
|     * [通过 ssh 访问公司内网机器](#通过-ssh-访问公司内网机器) | ||||
|     * [通过自定义域名访问部署于内网的 web 服务](#通过自定义域名访问部署于内网的-web-服务) | ||||
|     * [转发 DNS 查询请求](#转发-dns-查询请求) | ||||
|     * [转发 Unix域套接字](#转发-unix域套接字) | ||||
|     * [对外提供简单的文件访问服务](#对外提供简单的文件访问服务) | ||||
|     * [安全地暴露内网服务](#安全地暴露内网服务) | ||||
|     * [点对点内网穿透](#点对点内网穿透) | ||||
| * [功能说明](#功能说明) | ||||
|     * [配置文件](#配置文件) | ||||
|     * [配置文件模版渲染](#配置文件模版渲染) | ||||
|     * [Dashboard](#dashboard) | ||||
|     * [身份验证](#身份验证) | ||||
|     * [加密与压缩](#加密与压缩) | ||||
|     * [客户端热加载配置文件](#客户端热加载配置文件) | ||||
|     * [客户端查看代理状态](#客户端查看代理状态) | ||||
|     * [端口白名单](#端口白名单) | ||||
|     * [端口复用](#端口复用) | ||||
|     * [TCP 多路复用](#tcp-多路复用) | ||||
|     * [底层通信可选 kcp 协议](#底层通信可选-kcp-协议) | ||||
|     * [连接池](#连接池) | ||||
|     * [负载均衡](#负载均衡) | ||||
|     * [健康检查](#健康检查) | ||||
|     * [修改 Host Header](#修改-host-header) | ||||
|     * [设置 HTTP 请求的 header](#设置-http-请求的-header) | ||||
|     * [获取用户真实 IP](#获取用户真实-ip) | ||||
|     * [通过密码保护你的 web 服务](#通过密码保护你的-web-服务) | ||||
|     * [自定义二级域名](#自定义二级域名) | ||||
|     * [URL 路由](#url-路由) | ||||
|     * [通过代理连接 frps](#通过代理连接-frps) | ||||
|     * [范围端口映射](#范围端口映射) | ||||
|     * [插件](#插件) | ||||
| * [开发计划](#开发计划) | ||||
| * [为 frp 做贡献](#为-frp-做贡献) | ||||
| * [捐助](#捐助) | ||||
|     * [支付宝扫码捐赠](#支付宝扫码捐赠) | ||||
|     * [微信支付捐赠](#微信支付捐赠) | ||||
|     * [Paypal 捐赠](#paypal-捐赠) | ||||
|  | ||||
| <!-- vim-markdown-toc --> | ||||
|  | ||||
| ## 开发状态 | ||||
|  | ||||
| frp 目前正在前期开发阶段,master分支用于发布稳定版本,dev分支用于开发,您可以尝试下载最新的 release 版本进行测试。 | ||||
| frp 仍然处于开发阶段,未经充分测试与验证,不推荐用于生产环境。 | ||||
|  | ||||
| **在 1.x 版本以前,交互协议都可能会被改变,不能保证向后兼容。** | ||||
| master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。 | ||||
|  | ||||
| ## 快速开始 | ||||
|  | ||||
| [QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh.md) | ||||
| **目前的交互协议可能随时改变,不保证向后兼容,升级新版本时需要注意公告说明同时升级服务端和客户端。** | ||||
|  | ||||
| ## 架构 | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## frp 的作用? | ||||
| ## 使用示例 | ||||
|  | ||||
| * 利用处于内网或防火墙后的机器,对外网环境提供http服务。(针对http的优化正在开发中) | ||||
| * 利用处于内网或防火墙后的机器,对外网环境提供tcp服务。 | ||||
| * 可查看通过代理的所有http请求和响应信息。(待开发) | ||||
| 根据对应的操作系统及架构,从 [Release](https://github.com/fatedier/frp/releases) 页面下载最新版本的程序。 | ||||
|  | ||||
| ## 贡献代码 | ||||
| 将 **frps** 及 **frps.ini** 放到具有公网 IP 的机器上。 | ||||
|  | ||||
| 如果您对这个项目感兴趣,并且想要参与其中,我们非常欢迎! | ||||
| 将 **frpc** 及 **frpc.ini** 放到处于内网环境的机器上。 | ||||
|  | ||||
| * 如果您需要提交问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来完成。 | ||||
| * 如果您有新的功能需求,可以反馈至 fatedier@gmail.com 共同讨论。 | ||||
| ### 通过 ssh 访问公司内网机器 | ||||
|  | ||||
| ## 贡献者 | ||||
| 1. 修改 frps.ini 文件,这里使用了最简化的配置: | ||||
|  | ||||
| * [fatedier](https://github.com/fatedier) | ||||
| * [Hurricanezwf](https://github.com/Hurricanezwf) | ||||
| * [vashstorm](https://github.com/vashstorm) | ||||
|   ```ini | ||||
|   # frps.ini | ||||
|   [common] | ||||
|   bind_port = 7000 | ||||
|   ``` | ||||
|  | ||||
| 2. 启动 frps: | ||||
|  | ||||
|   `./frps -c ./frps.ini` | ||||
|  | ||||
| 3. 修改 frpc.ini 文件,假设 frps 所在服务器的公网 IP 为 x.x.x.x; | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|    | ||||
|   [ssh] | ||||
|   type = tcp | ||||
|   local_ip = 127.0.0.1 | ||||
|   local_port = 22 | ||||
|   remote_port = 6000 | ||||
|   ``` | ||||
|  | ||||
| 4. 启动 frpc: | ||||
|  | ||||
|   `./frpc -c ./frpc.ini` | ||||
|  | ||||
| 5. 通过 ssh 访问内网机器,假设用户名为 test: | ||||
|  | ||||
|   `ssh -oPort=6000 test@x.x.x.x` | ||||
|  | ||||
| ### 通过自定义域名访问部署于内网的 web 服务 | ||||
|  | ||||
| 有时想要让其他人通过域名访问或者测试我们在本地搭建的 web 服务,但是由于本地机器没有公网 IP,无法将域名解析到本地的机器,通过 frp 就可以实现这一功能,以下示例为 http 服务,https 服务配置方法相同, vhost_http_port 替换为 vhost_https_port, type 设置为 https 即可。 | ||||
|  | ||||
| 1. 修改 frps.ini 文件,设置 http 访问端口为 8080: | ||||
|  | ||||
|   ```ini | ||||
|   # frps.ini | ||||
|   [common] | ||||
|   bind_port = 7000 | ||||
|   vhost_http_port = 8080 | ||||
|   ``` | ||||
|  | ||||
| 2. 启动 frps; | ||||
|  | ||||
|   `./frps -c ./frps.ini` | ||||
|  | ||||
| 3. 修改 frpc.ini 文件,假设 frps 所在的服务器的 IP 为 x.x.x.x,local_port 为本地机器上 web 服务对应的端口, 绑定自定义域名 `www.yourdomain.com`: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [web] | ||||
|   type = http | ||||
|   local_port = 80 | ||||
|   custom_domains = www.yourdomain.com | ||||
|   ``` | ||||
|  | ||||
| 4. 启动 frpc: | ||||
|  | ||||
|   `./frpc -c ./frpc.ini` | ||||
|  | ||||
| 5. 将 `www.yourdomain.com` 的域名 A 记录解析到 IP `x.x.x.x`,如果服务器已经有对应的域名,也可以将 CNAME 记录解析到服务器原先的域名。 | ||||
|  | ||||
| 6. 通过浏览器访问 `http://www.yourdomain.com:8080` 即可访问到处于内网机器上的 web 服务。 | ||||
|  | ||||
| ### 转发 DNS 查询请求 | ||||
|  | ||||
| DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿透,配置方式和 TCP 基本一致。 | ||||
|  | ||||
| 1. 修改 frps.ini 文件: | ||||
|  | ||||
|   ```ini | ||||
|   # frps.ini | ||||
|   [common] | ||||
|   bind_port = 7000 | ||||
|   ``` | ||||
|  | ||||
| 2. 启动 frps: | ||||
|  | ||||
|   `./frps -c ./frps.ini` | ||||
|  | ||||
| 3. 修改 frpc.ini 文件,设置 frps 所在服务器的 IP 为 x.x.x.x,转发到 Google 的 DNS 查询服务器 `8.8.8.8` 的 udp 53 端口: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|    | ||||
|   [dns] | ||||
|   type = udp | ||||
|   local_ip = 8.8.8.8 | ||||
|   local_port = 53 | ||||
|   remote_port = 6000 | ||||
|   ``` | ||||
|  | ||||
| 4. 启动 frpc: | ||||
|  | ||||
|   `./frpc -c ./frpc.ini` | ||||
|  | ||||
| 5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果: | ||||
|  | ||||
|   `dig @x.x.x.x -p 6000 www.google.com` | ||||
|  | ||||
| ### 转发 Unix域套接字 | ||||
|  | ||||
| 通过 tcp 端口访问内网的 unix域套接字(例如和 docker daemon 通信)。 | ||||
|  | ||||
| frps 的部署步骤同上。 | ||||
|  | ||||
| 1. 启动 frpc,启用 `unix_domain_socket` 插件,配置如下: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|    | ||||
|   [unix_domain_socket] | ||||
|   type = tcp | ||||
|   remote_port = 6000 | ||||
|   plugin = unix_domain_socket | ||||
|   plugin_unix_path = /var/run/docker.sock | ||||
|   ``` | ||||
|  | ||||
| 2. 通过 curl 命令查看 docker 版本信息 | ||||
|  | ||||
|   `curl http://x.x.x.x:6000/version` | ||||
|  | ||||
| ### 对外提供简单的文件访问服务 | ||||
|  | ||||
| 通过 `static_file` 插件可以对外提供一个简单的基于 HTTP 的文件访问服务。 | ||||
|  | ||||
| frps 的部署步骤同上。 | ||||
|  | ||||
| 1. 启动 frpc,启用 `static_file` 插件,配置如下: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [test_static_file] | ||||
|   type = tcp | ||||
|   remote_port = 6000 | ||||
|   plugin = static_file | ||||
|   # 要对外暴露的文件目录 | ||||
|   plugin_local_path = /tmp/file | ||||
|   # 访问 url 中会被去除的前缀,保留的内容即为要访问的文件路径 | ||||
|   plugin_strip_prefix = static | ||||
|   plugin_http_user = abc | ||||
|   plugin_http_passwd = abc | ||||
|   ``` | ||||
|  | ||||
| 2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。 | ||||
|  | ||||
| ### 安全地暴露内网服务 | ||||
|  | ||||
| 对于某些服务来说如果直接暴露于公网上将会存在安全隐患。 | ||||
|  | ||||
| 使用 **stcp(secret tcp)** 类型的代理可以避免让任何人都能访问到要穿透的服务,但是访问者也需要运行另外一个 frpc。 | ||||
|  | ||||
| 以下示例将会创建一个只有自己能访问到的 ssh 服务代理。 | ||||
|  | ||||
| frps 的部署步骤同上。 | ||||
|  | ||||
| 1. 启动 frpc,转发内网的 ssh 服务,配置如下,不需要指定远程端口: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [secret_ssh] | ||||
|   type = stcp | ||||
|   # 只有 sk 一致的用户才能访问到此服务 | ||||
|   sk = abcdefg | ||||
|   local_ip = 127.0.0.1 | ||||
|   local_port = 22 | ||||
|   ``` | ||||
|  | ||||
| 2. 在要访问这个服务的机器上启动另外一个 frpc,配置如下: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [secret_ssh_visitor] | ||||
|   type = stcp | ||||
|   # stcp 的访问者 | ||||
|   role = visitor | ||||
|   # 要访问的 stcp 代理的名字 | ||||
|   server_name = secret_ssh | ||||
|   sk = abcdefg | ||||
|   # 绑定本地端口用于访问 ssh 服务 | ||||
|   bind_addr = 127.0.0.1 | ||||
|   bind_port = 6000 | ||||
|   ``` | ||||
|  | ||||
| 3. 通过 ssh 访问内网机器,假设用户名为 test: | ||||
|  | ||||
|   `ssh -oPort=6000 test@127.0.0.1` | ||||
|  | ||||
| ### 点对点内网穿透 | ||||
|  | ||||
| frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量数据且流量不经过服务器的场景。 | ||||
|  | ||||
| 使用方式同 **stcp** 类似,需要在两边都部署上 frpc 用于建立直接的连接。 | ||||
|  | ||||
| 目前处于开发的初级阶段,并不能穿透所有类型的 NAT 设备,所以穿透成功率较低。穿透失败时可以尝试 **stcp** 的方式。 | ||||
|  | ||||
| 1. frps 除正常配置外需要额外配置一个 udp 端口用于支持该类型的客户端: | ||||
|  | ||||
|   ```ini | ||||
|   bind_udp_port = 7001 | ||||
|   ``` | ||||
|  | ||||
| 2. 启动 frpc,转发内网的 ssh 服务,配置如下,不需要指定远程端口: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [p2p_ssh] | ||||
|   type = xtcp | ||||
|   # 只有 sk 一致的用户才能访问到此服务 | ||||
|   sk = abcdefg | ||||
|   local_ip = 127.0.0.1 | ||||
|   local_port = 22 | ||||
|   ``` | ||||
|  | ||||
| 3. 在要访问这个服务的机器上启动另外一个 frpc,配置如下: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   server_port = 7000 | ||||
|  | ||||
|   [p2p_ssh_visitor] | ||||
|   type = xtcp | ||||
|   # xtcp 的访问者 | ||||
|   role = visitor | ||||
|   # 要访问的 xtcp 代理的名字 | ||||
|   server_name = p2p_ssh | ||||
|   sk = abcdefg | ||||
|   # 绑定本地端口用于访问 ssh 服务 | ||||
|   bind_addr = 127.0.0.1 | ||||
|   bind_port = 6000 | ||||
|   ``` | ||||
|  | ||||
| 4. 通过 ssh 访问内网机器,假设用户名为 test: | ||||
|  | ||||
|   `ssh -oPort=6000 test@127.0.0.1` | ||||
|  | ||||
| ## 功能说明 | ||||
|  | ||||
| ### 配置文件 | ||||
|  | ||||
| 由于 frp 目前支持的功能和配置项较多,未在文档中列出的功能可以从完整的示例配置文件中发现。 | ||||
|  | ||||
| [frps 完整配置文件](./conf/frps_full.ini) | ||||
|  | ||||
| [frpc 完整配置文件](./conf/frpc_full.ini) | ||||
|  | ||||
| ### 配置文件模版渲染 | ||||
|  | ||||
| 配置文件支持使用系统环境变量进行模版渲染,模版格式采用 Go 的标准格式。 | ||||
|  | ||||
| 示例配置如下: | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [common] | ||||
| server_addr = {{ .Envs.FRP_SERVER_ADDR }} | ||||
| server_port = 7000 | ||||
|  | ||||
| [ssh] | ||||
| type = tcp | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 22 | ||||
| remote_port = {{ .Envs.FRP_SSH_REMOTE_PORT }} | ||||
| ``` | ||||
|  | ||||
| 启动 frpc 程序: | ||||
|  | ||||
| ``` | ||||
| export FRP_SERVER_ADDR="x.x.x.x" | ||||
| export FRP_SSH_REMOTE_PORT="6000" | ||||
| ./frpc -c ./frpc.ini | ||||
| ``` | ||||
|  | ||||
| frpc 会自动使用环境变量渲染配置文件模版,所有环境变量需要以 `.Envs` 为前缀。 | ||||
|  | ||||
| ### Dashboard | ||||
|  | ||||
| 通过浏览器查看 frp 的状态以及代理统计信息展示。 | ||||
|  | ||||
| **注:Dashboard 尚未针对大量的 proxy 数据展示做优化,如果出现 Dashboard 访问较慢的情况,请不要启用此功能。** | ||||
|  | ||||
| 需要在 frps.ini 中指定 dashboard 服务使用的端口,即可开启此功能: | ||||
|  | ||||
| ```ini | ||||
| [common] | ||||
| dashboard_port = 7500 | ||||
| # dashboard 用户名密码,默认都为 admin | ||||
| dashboard_user = admin | ||||
| dashboard_pwd = admin | ||||
| ``` | ||||
|  | ||||
| 打开浏览器通过 `http://[server_addr]:7500` 访问 dashboard 界面,用户名密码默认为 `admin`。 | ||||
|  | ||||
|  | ||||
|  | ||||
| ### 身份验证 | ||||
|  | ||||
| 服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。 | ||||
|  | ||||
| ### 加密与压缩 | ||||
|  | ||||
| 这两个功能默认是不开启的,需要在 frpc.ini 中通过配置来为指定的代理启用加密与压缩的功能,压缩算法使用 snappy: | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [ssh] | ||||
| type = tcp | ||||
| local_port = 22 | ||||
| remote_port = 6000 | ||||
| use_encryption = true | ||||
| use_compression = true | ||||
| ``` | ||||
|  | ||||
| 如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了 ssh 协议等,通过设置 `use_encryption = true`,将 frpc 与 frps 之间的通信内容加密传输,将会有效防止流量被拦截。 | ||||
|  | ||||
| 如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。 | ||||
|  | ||||
| ### 客户端热加载配置文件 | ||||
|  | ||||
| 当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。 | ||||
|  | ||||
| 启用此功能需要在 frpc 中启用 admin 端口,用于提供 API 服务。配置如下: | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [common] | ||||
| admin_addr = 127.0.0.1 | ||||
| admin_port = 7400 | ||||
| ``` | ||||
|  | ||||
| 之后执行重启命令: | ||||
|  | ||||
| `frpc reload -c ./frpc.ini` | ||||
|  | ||||
| 等待一段时间后客户端会根据新的配置文件创建、更新、删除代理。 | ||||
|  | ||||
| **需要注意的是,[common] 中的参数除了 start 外目前无法被修改。** | ||||
|  | ||||
| ### 客户端查看代理状态 | ||||
|  | ||||
| frpc 支持通过 `frpc status -c ./frpc.ini` 命令查看代理的状态信息,此功能需要在 frpc 中配置 admin 端口。 | ||||
|  | ||||
| ### 端口白名单 | ||||
|  | ||||
| 为了防止端口被滥用,可以手动指定允许哪些端口被使用,在 frps.ini 中通过 `allow_ports` 来指定: | ||||
|  | ||||
| ```ini | ||||
| # frps.ini | ||||
| [common] | ||||
| allow_ports = 2000-3000,3001,3003,4000-50000 | ||||
| ``` | ||||
|  | ||||
| `allow_ports` 可以配置允许使用的某个指定端口或者是一个范围内的所有端口,以 `,` 分隔,指定的范围以 `-` 分隔。 | ||||
|  | ||||
| ### 端口复用 | ||||
|  | ||||
| 目前 frps 中的 `vhost_http_port` 和 `vhost_https_port` 支持配置成和 `bind_port` 为同一个端口,frps 会对连接的协议进行分析,之后进行不同的处理。 | ||||
|  | ||||
| 例如在某些限制较严格的网络环境中,可以将 `bind_port` 和 `vhost_https_port` 都设置为 443。 | ||||
|  | ||||
| 后续会尝试允许多个 proxy 绑定同一个远端端口的不同协议。 | ||||
|  | ||||
| ### TCP 多路复用 | ||||
|  | ||||
| 从 v0.10.0 版本开始,客户端和服务器端之间的连接支持多路复用,不再需要为每一个用户请求创建一个连接,使连接建立的延迟降低,并且避免了大量文件描述符的占用,使 frp 可以承载更高的并发数。 | ||||
|  | ||||
| 该功能默认启用,如需关闭,可以在 frps.ini 和 frpc.ini 中配置,该配置项在服务端和客户端必须一致: | ||||
|  | ||||
| ```ini | ||||
| # frps.ini 和 frpc.ini 中 | ||||
| [common] | ||||
| tcp_mux = false | ||||
| ``` | ||||
|  | ||||
| ### 底层通信可选 kcp 协议 | ||||
|  | ||||
| 从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。 | ||||
|  | ||||
| 开启 kcp 协议支持: | ||||
|  | ||||
| 1. 在 frps.ini 中启用 kcp 协议支持,指定一个 udp 端口用于接收客户端请求: | ||||
|  | ||||
|   ```ini | ||||
|   # frps.ini | ||||
|   [common] | ||||
|   bind_port = 7000 | ||||
|   # kcp 绑定的是 udp 端口,可以和 bind_port 一样 | ||||
|   kcp_bind_port = 7000 | ||||
|   ``` | ||||
|  | ||||
| 2. 在 frpc.ini 指定需要使用的协议类型,目前只支持 tcp 和 kcp。其他代理配置不需要变更: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   server_addr = x.x.x.x | ||||
|   # server_port 指定为 frps 的 kcp_bind_port | ||||
|   server_port = 7000 | ||||
|   protocol = kcp | ||||
|   ``` | ||||
|  | ||||
| 3. 像之前一样使用 frp,需要注意开放相关机器上的 udp 的端口的访问权限。 | ||||
|  | ||||
| ### 连接池 | ||||
|  | ||||
| 默认情况下,当用户请求建立连接后,frps 才会请求 frpc 主动与后端服务建立一个连接。当为指定的代理启用连接池后,frp 会预先和后端服务建立起指定数量的连接,每次接收到用户请求后,会从连接池中取出一个连接和用户连接关联起来,避免了等待与后端服务建立连接以及 frpc 和 frps 之间传递控制信息的时间。 | ||||
|  | ||||
| 这一功能比较适合有大量短连接请求时开启。 | ||||
|  | ||||
| 1. 首先可以在 frps.ini 中设置每个代理可以创建的连接池上限,避免大量资源占用,客户端设置超过此配置后会被调整到当前值: | ||||
|  | ||||
|   ```ini | ||||
|   # frps.ini | ||||
|   [common] | ||||
|   max_pool_count = 5 | ||||
|   ``` | ||||
|  | ||||
| 2. 在 frpc.ini 中为客户端启用连接池,指定预创建连接的数量: | ||||
|  | ||||
|   ```ini | ||||
|   # frpc.ini | ||||
|   [common] | ||||
|   pool_count = 1 | ||||
|   ``` | ||||
|  | ||||
| ### 负载均衡 | ||||
|  | ||||
| 可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。 | ||||
| 目前只支持 tcp 类型的 proxy。 | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [test1] | ||||
| type = tcp | ||||
| local_port = 8080 | ||||
| remote_port = 80 | ||||
| group = web | ||||
| group_key = 123 | ||||
|  | ||||
| [test2] | ||||
| type = tcp | ||||
| local_port = 8081 | ||||
| remote_port = 80 | ||||
| group = web | ||||
| group_key = 123 | ||||
| ``` | ||||
|  | ||||
| 用户连接 frps 服务器的 80 端口,frps 会将接收到的用户连接随机分发给其中一个存活的 proxy。这样可以在一台 frpc 机器挂掉后仍然有其他节点能够提供服务。 | ||||
|  | ||||
| 要求 `group_key` 相同,做权限验证,且 `remote_port` 相同。 | ||||
|  | ||||
| ### 健康检查 | ||||
|  | ||||
| 通过给 proxy 加上健康检查的功能,可以在要反向代理的服务出现故障时,将这个服务从 frps 中摘除,搭配负载均衡的功能,可以用来实现高可用的架构,避免服务单点故障。 | ||||
|  | ||||
| 在每一个 proxy 的配置下加上 `health_check_type = {type}` 来启用健康检查功能。 | ||||
|  | ||||
| **type** 目前可选 tcp 和 http。 | ||||
|  | ||||
| tcp 只要能够建立连接则认为服务正常,http 会发送一个 http 请求,服务需要返回 2xx 的状态码才会被认为正常。 | ||||
|  | ||||
| tcp 示例配置如下: | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [test1] | ||||
| type = tcp | ||||
| local_port = 22 | ||||
| remote_port = 6000 | ||||
| # 启用健康检查,类型为 tcp | ||||
| health_check_type = tcp | ||||
| # 建立连接超时时间为 3 秒 | ||||
| health_check_timeout_s = 3 | ||||
| # 连续 3 次检查失败,此 proxy 会被摘除 | ||||
| health_check_max_failed = 3 | ||||
| # 每隔 10 秒进行一次健康检查 | ||||
| health_check_interval_s = 10 | ||||
| ``` | ||||
|  | ||||
| http 示例配置如下: | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web] | ||||
| type = http | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 80 | ||||
| custom_domains = test.yourdomain.com | ||||
| # 启用健康检查,类型为 http | ||||
| health_check_type = http | ||||
| # 健康检查发送 http 请求的 url,后端服务需要返回 2xx 的 http 状态码 | ||||
| health_check_url = /status | ||||
| health_check_interval_s = 10 | ||||
| health_check_max_failed = 3 | ||||
| health_check_timeout_s = 3 | ||||
| ``` | ||||
|  | ||||
| ### 修改 Host Header | ||||
|  | ||||
| 通常情况下 frp 不会修改转发的任何数据。但有一些后端服务会根据 http 请求 header 中的 host 字段来展现不同的网站,例如 nginx 的虚拟主机服务,启用 host-header 的修改功能可以动态修改 http 请求中的 host 字段。该功能仅限于 http 类型的代理。 | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web] | ||||
| type = http | ||||
| local_port = 80 | ||||
| custom_domains = test.yourdomain.com | ||||
| host_header_rewrite = dev.yourdomain.com | ||||
| ``` | ||||
|  | ||||
| 原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `dev.yourdomain.com`。 | ||||
|  | ||||
| ### 设置 HTTP 请求的 header | ||||
|  | ||||
| 对于 `type = http` 的代理,可以设置在转发中动态添加的 header 参数。 | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web] | ||||
| type = http | ||||
| local_port = 80 | ||||
| custom_domains = test.yourdomain.com | ||||
| host_header_rewrite = dev.yourdomain.com | ||||
| header_X-From-Where = frp | ||||
| ``` | ||||
|  | ||||
| 对于参数配置中所有以 `header_` 开头的参数(支持同时配置多个),都会被添加到 http 请求的 header 中,根据如上的配置,会在请求的 header 中加上 `X-From-Where: frp`。 | ||||
|  | ||||
| ### 获取用户真实 IP | ||||
|  | ||||
| 目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。 | ||||
|  | ||||
| ### 通过密码保护你的 web 服务 | ||||
|  | ||||
| 由于所有客户端共用一个 frps 的 http 服务端口,任何知道你的域名和 url 的人都能访问到你部署在内网的 web 服务,但是在某些场景下需要确保只有限定的用户才能访问。 | ||||
|  | ||||
| frp 支持通过 HTTP Basic Auth 来保护你的 web 服务,使用户需要通过用户名和密码才能访问到你的服务。 | ||||
|  | ||||
| 该功能目前仅限于 http 类型的代理,需要在 frpc 的代理配置中添加用户名和密码的设置。 | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web] | ||||
| type = http | ||||
| local_port = 80 | ||||
| custom_domains = test.yourdomain.com | ||||
| http_user = abc | ||||
| http_pwd = abc | ||||
| ``` | ||||
|  | ||||
| 通过浏览器访问 `http://test.yourdomain.com`,需要输入配置的用户名和密码才能访问。 | ||||
|  | ||||
| ### 自定义二级域名 | ||||
|  | ||||
| 在多人同时使用一个 frps 时,通过自定义二级域名的方式来使用会更加方便。 | ||||
|  | ||||
| 通过在 frps 的配置文件中配置 `subdomain_host`,就可以启用该特性。之后在 frpc 的 http、https 类型的代理中可以不配置 `custom_domains`,而是配置一个 `subdomain` 参数。 | ||||
|  | ||||
| 只需要将 `*.{subdomain_host}` 解析到 frps 所在服务器。之后用户可以通过 `subdomain` 自行指定自己的 web 服务所需要使用的二级域名,通过 `{subdomain}.{subdomain_host}` 来访问自己的 web 服务。 | ||||
|  | ||||
| ```ini | ||||
| # frps.ini | ||||
| [common] | ||||
| subdomain_host = frps.com | ||||
| ``` | ||||
|  | ||||
| 将泛域名 `*.frps.com` 解析到 frps 所在服务器的 IP 地址。 | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web] | ||||
| type = http | ||||
| local_port = 80 | ||||
| subdomain = test | ||||
| ``` | ||||
|  | ||||
| frps 和 frpc 都启动成功后,通过 `test.frps.com` 就可以访问到内网的 web 服务。 | ||||
|  | ||||
| **注:如果 frps 配置了 `subdomain_host`,则 `custom_domains` 中不能是属于 `subdomain_host` 的子域名或者泛域名。** | ||||
|  | ||||
| 同一个 http 或 https 类型的代理中 `custom_domains`  和 `subdomain` 可以同时配置。 | ||||
|  | ||||
| ### URL 路由 | ||||
|  | ||||
| frp 支持根据请求的 URL 路径路由转发到不同的后端服务。 | ||||
|  | ||||
| 通过配置文件中的 `locations` 字段指定一个或多个 proxy 能够匹配的 URL 前缀(目前仅支持最大前缀匹配,之后会考虑正则匹配)。例如指定 `locations = /news`,则所有 URL 以 `/news` 开头的请求都会被转发到这个服务。 | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [web01] | ||||
| type = http | ||||
| local_port = 80 | ||||
| custom_domains = web.yourdomain.com | ||||
| locations = / | ||||
|  | ||||
| [web02] | ||||
| type = http | ||||
| local_port = 81 | ||||
| custom_domains = web.yourdomain.com | ||||
| locations = /news,/about | ||||
| ``` | ||||
|  | ||||
| 按照上述的示例配置后,`web.yourdomain.com` 这个域名下所有以 `/news` 以及 `/about` 作为前缀的 URL 请求都会被转发到 web02,其余的请求会被转发到 web01。 | ||||
|  | ||||
| ### 通过代理连接 frps | ||||
|  | ||||
| 在只能通过代理访问外网的环境内,frpc 支持通过 HTTP PROXY 和 frps 进行通信。 | ||||
|  | ||||
| 可以通过设置 `HTTP_PROXY` 系统环境变量或者通过在 frpc 的配置文件中设置 `http_proxy` 参数来使用此功能。 | ||||
|  | ||||
| 仅在 `protocol = tcp` 时生效。 | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [common] | ||||
| server_addr = x.x.x.x | ||||
| server_port = 7000 | ||||
| http_proxy = http://user:pwd@192.168.1.128:8080 | ||||
| ``` | ||||
|  | ||||
| ### 范围端口映射 | ||||
|  | ||||
| 在 frpc 的配置文件中可以指定映射多个端口,目前只支持 tcp 和 udp 的类型。 | ||||
|  | ||||
| 这一功能通过 `range:` 段落标记来实现,客户端会解析这个标记中的配置,将其拆分成多个 proxy,每一个 proxy 以数字为后缀命名。 | ||||
|  | ||||
| 例如要映射本地 6000-6005, 6007 这6个端口,主要配置如下: | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [range:test_tcp] | ||||
| type = tcp | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 6000-6006,6007 | ||||
| remote_port = 6000-6006,6007 | ||||
| ``` | ||||
|  | ||||
| 实际连接成功后会创建 8 个 proxy,命名为 `test_tcp_0, test_tcp_1 ... test_tcp_7`。 | ||||
|  | ||||
| ### 插件 | ||||
|  | ||||
| 默认情况下,frpc 只会转发请求到本地 tcp 或 udp 端口。 | ||||
|  | ||||
| 插件模式是为了在客户端提供更加丰富的功能,目前内置的插件有 `unix_domain_socket`、`http_proxy`、`socks5`、`static_file`。具体使用方式请查看[使用示例](#使用示例)。 | ||||
|  | ||||
| 通过 `plugin` 指定需要使用的插件,插件的配置参数都以 `plugin_` 开头。使用插件后 `local_ip` 和 `local_port` 不再需要配置。 | ||||
|  | ||||
| 使用 **http_proxy** 插件的示例: | ||||
|  | ||||
| ```ini | ||||
| # frpc.ini | ||||
| [http_proxy] | ||||
| type = tcp | ||||
| remote_port = 6000 | ||||
| plugin = http_proxy | ||||
| plugin_http_user = abc | ||||
| plugin_http_passwd = abc | ||||
| ``` | ||||
|  | ||||
| `plugin_http_user` 和 `plugin_http_passwd` 即为 `http_proxy` 插件可选的配置参数。 | ||||
|  | ||||
| ## 开发计划 | ||||
|  | ||||
| 计划在后续版本中加入的功能与优化,排名不分先后,如果有其他功能建议欢迎在 [issues](https://github.com/fatedier/frp/issues) 中反馈。 | ||||
|  | ||||
| * frps 记录 http 请求日志。 | ||||
|  | ||||
| ## 为 frp 做贡献 | ||||
|  | ||||
| frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进步贡献力量。 | ||||
|  | ||||
| * 在使用过程中出现任何问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来反馈。 | ||||
| * Bug 的修复可以直接提交 Pull Request 到 dev 分支。 | ||||
| * 如果是增加新的功能特性,请先创建一个 issue 并做简单描述以及大致的实现方法,提议被采纳后,就可以创建一个实现新特性的 Pull Request。 | ||||
| * 欢迎对说明文档做出改善,帮助更多的人使用 frp,特别是英文文档。 | ||||
| * 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。 | ||||
| * 如果你有任何其他方面的问题,欢迎反馈至 fatedier@gmail.com 共同交流。 | ||||
|  | ||||
| **提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。** | ||||
|  | ||||
| ## 捐助 | ||||
|  | ||||
| 如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。 | ||||
|  | ||||
| frp 交流群:606194980 (QQ 群号) | ||||
|  | ||||
| ### 知识星球 | ||||
|  | ||||
| 如果您想学习 frp 相关的知识和技术,或者寻求任何帮助,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群: | ||||
|  | ||||
|  | ||||
|  | ||||
| ### 支付宝扫码捐赠 | ||||
|  | ||||
|  | ||||
|  | ||||
| ### 微信支付捐赠 | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Paypal 捐赠 | ||||
|  | ||||
| 海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。 | ||||
|   | ||||
							
								
								
									
										75
									
								
								assets/assets.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								assets/assets.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| // Copyright 2016 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package assets | ||||
|  | ||||
| //go:generate statik -src=./frps/static -dest=./frps | ||||
| //go:generate statik -src=./frpc/static -dest=./frpc | ||||
| //go:generate go fmt ./frps/statik/statik.go | ||||
| //go:generate go fmt ./frpc/statik/statik.go | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"path" | ||||
|  | ||||
| 	"github.com/rakyll/statik/fs" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// store static files in memory by statik | ||||
| 	FileSystem http.FileSystem | ||||
|  | ||||
| 	// if prefix is not empty, we get file content from disk | ||||
| 	prefixPath string | ||||
| ) | ||||
|  | ||||
| // if path is empty, load assets in memory | ||||
| // or set FileSystem using disk files | ||||
| func Load(path string) (err error) { | ||||
| 	prefixPath = path | ||||
| 	if prefixPath != "" { | ||||
| 		FileSystem = http.Dir(prefixPath) | ||||
| 		return nil | ||||
| 	} else { | ||||
| 		FileSystem, err = fs.New() | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func ReadFile(file string) (content string, err error) { | ||||
| 	if prefixPath == "" { | ||||
| 		file, err := FileSystem.Open(path.Join("/", file)) | ||||
| 		if err != nil { | ||||
| 			return content, err | ||||
| 		} | ||||
| 		buf, err := ioutil.ReadAll(file) | ||||
| 		if err != nil { | ||||
| 			return content, err | ||||
| 		} | ||||
| 		content = string(buf) | ||||
| 	} else { | ||||
| 		file, err := os.Open(path.Join(prefixPath, file)) | ||||
| 		if err != nil { | ||||
| 			return content, err | ||||
| 		} | ||||
| 		buf, err := ioutil.ReadAll(file) | ||||
| 		if err != nil { | ||||
| 			return content, err | ||||
| 		} | ||||
| 		content = string(buf) | ||||
| 	} | ||||
| 	return content, err | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								assets/frpc/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/frpc/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/frpc/static/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/frpc/static/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.4 KiB | 
							
								
								
									
										1
									
								
								assets/frpc/static/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/frpc/static/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?d2cd6337d30c7b22e836"></script><script type="text/javascript" src="vendor.js?edb271e1d9c81f857840"></script></body> </html>  | ||||
							
								
								
									
										1
									
								
								assets/frpc/static/manifest.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/frpc/static/manifest.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| !function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"edb271e1d9c81f857840"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]); | ||||
							
								
								
									
										1
									
								
								assets/frpc/static/vendor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/frpc/static/vendor.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										10
									
								
								assets/frpc/statik/statik.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								assets/frpc/statik/statik.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/frps/static/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/frps/static/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.4 KiB | 
							
								
								
									
										1
									
								
								assets/frps/static/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/frps/static/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <title>frps dashboard</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?14bea8276eef86cc7c61"></script><script type="text/javascript" src="vendor.js?51925ec1a77936b64d61"></script></body> </html>  | ||||
							
								
								
									
										1
									
								
								assets/frps/static/manifest.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/frps/static/manifest.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| !function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"51925ec1a77936b64d61"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]); | ||||
							
								
								
									
										1
									
								
								assets/frps/static/vendor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/frps/static/vendor.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										10
									
								
								assets/frps/statik/statik.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								assets/frps/statik/statik.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										72
									
								
								client/admin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								client/admin.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package client | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/assets" | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	"github.com/gorilla/mux" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	httpServerReadTimeout  = 10 * time.Second | ||||
| 	httpServerWriteTimeout = 10 * time.Second | ||||
| ) | ||||
|  | ||||
| func (svr *Service) RunAdminServer(addr string, port int) (err error) { | ||||
| 	// url router | ||||
| 	router := mux.NewRouter() | ||||
|  | ||||
| 	user, passwd := g.GlbClientCfg.AdminUser, g.GlbClientCfg.AdminPwd | ||||
| 	router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware) | ||||
|  | ||||
| 	// api, see dashboard_api.go | ||||
| 	router.HandleFunc("/api/reload", svr.apiReload).Methods("GET") | ||||
| 	router.HandleFunc("/api/status", svr.apiStatus).Methods("GET") | ||||
| 	router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET") | ||||
| 	router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT") | ||||
|  | ||||
| 	// view | ||||
| 	router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET") | ||||
| 	router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET") | ||||
| 	router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		http.Redirect(w, r, "/static/", http.StatusMovedPermanently) | ||||
| 	}) | ||||
|  | ||||
| 	address := fmt.Sprintf("%s:%d", addr, port) | ||||
| 	server := &http.Server{ | ||||
| 		Addr:         address, | ||||
| 		Handler:      router, | ||||
| 		ReadTimeout:  httpServerReadTimeout, | ||||
| 		WriteTimeout: httpServerWriteTimeout, | ||||
| 	} | ||||
| 	if address == "" { | ||||
| 		address = ":http" | ||||
| 	} | ||||
| 	ln, err := net.Listen("tcp", address) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	go server.Serve(ln) | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										324
									
								
								client/admin_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								client/admin_api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,324 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package client | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/fatedier/frp/client/proxy" | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| ) | ||||
|  | ||||
| type GeneralResponse struct { | ||||
| 	Code int | ||||
| 	Msg  string | ||||
| } | ||||
|  | ||||
| // GET api/reload | ||||
|  | ||||
| func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) { | ||||
| 	res := GeneralResponse{Code: 200} | ||||
|  | ||||
| 	log.Info("Http request [/api/reload]") | ||||
| 	defer func() { | ||||
| 		log.Info("Http response [/api/reload], code [%d]", res.Code) | ||||
| 		w.WriteHeader(res.Code) | ||||
| 		if len(res.Msg) > 0 { | ||||
| 			w.Write([]byte(res.Msg)) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile) | ||||
| 	if err != nil { | ||||
| 		res.Code = 400 | ||||
| 		res.Msg = err.Error() | ||||
| 		log.Warn("reload frpc config file error: %s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content) | ||||
| 	if err != nil { | ||||
| 		res.Code = 400 | ||||
| 		res.Msg = err.Error() | ||||
| 		log.Warn("reload frpc common section error: %s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start) | ||||
| 	if err != nil { | ||||
| 		res.Code = 400 | ||||
| 		res.Msg = err.Error() | ||||
| 		log.Warn("reload frpc proxy config error: %s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = svr.ReloadConf(pxyCfgs, visitorCfgs) | ||||
| 	if err != nil { | ||||
| 		res.Code = 500 | ||||
| 		res.Msg = err.Error() | ||||
| 		log.Warn("reload frpc proxy config error: %s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
| 	log.Info("success reload conf") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type StatusResp struct { | ||||
| 	Tcp   []ProxyStatusResp `json:"tcp"` | ||||
| 	Udp   []ProxyStatusResp `json:"udp"` | ||||
| 	Http  []ProxyStatusResp `json:"http"` | ||||
| 	Https []ProxyStatusResp `json:"https"` | ||||
| 	Stcp  []ProxyStatusResp `json:"stcp"` | ||||
| 	Xtcp  []ProxyStatusResp `json:"xtcp"` | ||||
| } | ||||
|  | ||||
| type ProxyStatusResp struct { | ||||
| 	Name       string `json:"name"` | ||||
| 	Type       string `json:"type"` | ||||
| 	Status     string `json:"status"` | ||||
| 	Err        string `json:"err"` | ||||
| 	LocalAddr  string `json:"local_addr"` | ||||
| 	Plugin     string `json:"plugin"` | ||||
| 	RemoteAddr string `json:"remote_addr"` | ||||
| } | ||||
|  | ||||
| type ByProxyStatusResp []ProxyStatusResp | ||||
|  | ||||
| func (a ByProxyStatusResp) Len() int           { return len(a) } | ||||
| func (a ByProxyStatusResp) Swap(i, j int)      { a[i], a[j] = a[j], a[i] } | ||||
| func (a ByProxyStatusResp) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 } | ||||
|  | ||||
| func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp { | ||||
| 	psr := ProxyStatusResp{ | ||||
| 		Name:   status.Name, | ||||
| 		Type:   status.Type, | ||||
| 		Status: status.Status, | ||||
| 		Err:    status.Err, | ||||
| 	} | ||||
| 	switch cfg := status.Cfg.(type) { | ||||
| 	case *config.TcpProxyConf: | ||||
| 		if cfg.LocalPort != 0 { | ||||
| 			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort) | ||||
| 		} | ||||
| 		psr.Plugin = cfg.Plugin | ||||
| 		if status.Err != "" { | ||||
| 			psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort) | ||||
| 		} else { | ||||
| 			psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr | ||||
| 		} | ||||
| 	case *config.UdpProxyConf: | ||||
| 		if cfg.LocalPort != 0 { | ||||
| 			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort) | ||||
| 		} | ||||
| 		if status.Err != "" { | ||||
| 			psr.RemoteAddr = fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, cfg.RemotePort) | ||||
| 		} else { | ||||
| 			psr.RemoteAddr = g.GlbClientCfg.ServerAddr + status.RemoteAddr | ||||
| 		} | ||||
| 	case *config.HttpProxyConf: | ||||
| 		if cfg.LocalPort != 0 { | ||||
| 			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort) | ||||
| 		} | ||||
| 		psr.Plugin = cfg.Plugin | ||||
| 		psr.RemoteAddr = status.RemoteAddr | ||||
| 	case *config.HttpsProxyConf: | ||||
| 		if cfg.LocalPort != 0 { | ||||
| 			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort) | ||||
| 		} | ||||
| 		psr.Plugin = cfg.Plugin | ||||
| 		psr.RemoteAddr = status.RemoteAddr | ||||
| 	case *config.StcpProxyConf: | ||||
| 		if cfg.LocalPort != 0 { | ||||
| 			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort) | ||||
| 		} | ||||
| 		psr.Plugin = cfg.Plugin | ||||
| 	case *config.XtcpProxyConf: | ||||
| 		if cfg.LocalPort != 0 { | ||||
| 			psr.LocalAddr = fmt.Sprintf("%s:%d", cfg.LocalIp, cfg.LocalPort) | ||||
| 		} | ||||
| 		psr.Plugin = cfg.Plugin | ||||
| 	} | ||||
| 	return psr | ||||
| } | ||||
|  | ||||
| // GET api/status | ||||
| func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) { | ||||
| 	var ( | ||||
| 		buf []byte | ||||
| 		res StatusResp | ||||
| 	) | ||||
| 	res.Tcp = make([]ProxyStatusResp, 0) | ||||
| 	res.Udp = make([]ProxyStatusResp, 0) | ||||
| 	res.Http = make([]ProxyStatusResp, 0) | ||||
| 	res.Https = make([]ProxyStatusResp, 0) | ||||
| 	res.Stcp = make([]ProxyStatusResp, 0) | ||||
| 	res.Xtcp = make([]ProxyStatusResp, 0) | ||||
|  | ||||
| 	log.Info("Http request [/api/status]") | ||||
| 	defer func() { | ||||
| 		log.Info("Http response [/api/status]") | ||||
| 		buf, _ = json.Marshal(&res) | ||||
| 		w.Write(buf) | ||||
| 	}() | ||||
|  | ||||
| 	ps := svr.ctl.pm.GetAllProxyStatus() | ||||
| 	for _, status := range ps { | ||||
| 		switch status.Type { | ||||
| 		case "tcp": | ||||
| 			res.Tcp = append(res.Tcp, NewProxyStatusResp(status)) | ||||
| 		case "udp": | ||||
| 			res.Udp = append(res.Udp, NewProxyStatusResp(status)) | ||||
| 		case "http": | ||||
| 			res.Http = append(res.Http, NewProxyStatusResp(status)) | ||||
| 		case "https": | ||||
| 			res.Https = append(res.Https, NewProxyStatusResp(status)) | ||||
| 		case "stcp": | ||||
| 			res.Stcp = append(res.Stcp, NewProxyStatusResp(status)) | ||||
| 		case "xtcp": | ||||
| 			res.Xtcp = append(res.Xtcp, NewProxyStatusResp(status)) | ||||
| 		} | ||||
| 	} | ||||
| 	sort.Sort(ByProxyStatusResp(res.Tcp)) | ||||
| 	sort.Sort(ByProxyStatusResp(res.Udp)) | ||||
| 	sort.Sort(ByProxyStatusResp(res.Http)) | ||||
| 	sort.Sort(ByProxyStatusResp(res.Https)) | ||||
| 	sort.Sort(ByProxyStatusResp(res.Stcp)) | ||||
| 	sort.Sort(ByProxyStatusResp(res.Xtcp)) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GET api/config | ||||
| func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) { | ||||
| 	res := GeneralResponse{Code: 200} | ||||
|  | ||||
| 	log.Info("Http get request [/api/config]") | ||||
| 	defer func() { | ||||
| 		log.Info("Http get response [/api/config], code [%d]", res.Code) | ||||
| 		w.WriteHeader(res.Code) | ||||
| 		if len(res.Msg) > 0 { | ||||
| 			w.Write([]byte(res.Msg)) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	if g.GlbClientCfg.CfgFile == "" { | ||||
| 		res.Code = 400 | ||||
| 		res.Msg = "frpc has no config file path" | ||||
| 		log.Warn("%s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile) | ||||
| 	if err != nil { | ||||
| 		res.Code = 400 | ||||
| 		res.Msg = err.Error() | ||||
| 		log.Warn("load frpc config file error: %s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	rows := strings.Split(content, "\n") | ||||
| 	newRows := make([]string, 0, len(rows)) | ||||
| 	for _, row := range rows { | ||||
| 		row = strings.TrimSpace(row) | ||||
| 		if strings.HasPrefix(row, "token") { | ||||
| 			continue | ||||
| 		} | ||||
| 		newRows = append(newRows, row) | ||||
| 	} | ||||
| 	res.Msg = strings.Join(newRows, "\n") | ||||
| } | ||||
|  | ||||
| // PUT api/config | ||||
| func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) { | ||||
| 	res := GeneralResponse{Code: 200} | ||||
|  | ||||
| 	log.Info("Http put request [/api/config]") | ||||
| 	defer func() { | ||||
| 		log.Info("Http put response [/api/config], code [%d]", res.Code) | ||||
| 		w.WriteHeader(res.Code) | ||||
| 		if len(res.Msg) > 0 { | ||||
| 			w.Write([]byte(res.Msg)) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	// get new config content | ||||
| 	body, err := ioutil.ReadAll(r.Body) | ||||
| 	if err != nil { | ||||
| 		res.Code = 400 | ||||
| 		res.Msg = fmt.Sprintf("read request body error: %v", err) | ||||
| 		log.Warn("%s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(body) == 0 { | ||||
| 		res.Code = 400 | ||||
| 		res.Msg = "body can't be empty" | ||||
| 		log.Warn("%s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// get token from origin content | ||||
| 	token := "" | ||||
| 	b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile) | ||||
| 	if err != nil { | ||||
| 		res.Code = 400 | ||||
| 		res.Msg = err.Error() | ||||
| 		log.Warn("load frpc config file error: %s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
| 	content := string(b) | ||||
|  | ||||
| 	for _, row := range strings.Split(content, "\n") { | ||||
| 		row = strings.TrimSpace(row) | ||||
| 		if strings.HasPrefix(row, "token") { | ||||
| 			token = row | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	tmpRows := make([]string, 0) | ||||
| 	for _, row := range strings.Split(string(body), "\n") { | ||||
| 		row = strings.TrimSpace(row) | ||||
| 		if strings.HasPrefix(row, "token") { | ||||
| 			continue | ||||
| 		} | ||||
| 		tmpRows = append(tmpRows, row) | ||||
| 	} | ||||
|  | ||||
| 	newRows := make([]string, 0) | ||||
| 	if token != "" { | ||||
| 		for _, row := range tmpRows { | ||||
| 			newRows = append(newRows, row) | ||||
| 			if strings.HasPrefix(row, "[common]") { | ||||
| 				newRows = append(newRows, token) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	content = strings.Join(newRows, "\n") | ||||
|  | ||||
| 	err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644) | ||||
| 	if err != nil { | ||||
| 		res.Code = 500 | ||||
| 		res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err) | ||||
| 		log.Warn("%s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										304
									
								
								client/control.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										304
									
								
								client/control.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,304 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package client | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"runtime/debug" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/client/proxy" | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	"github.com/fatedier/golib/control/shutdown" | ||||
| 	"github.com/fatedier/golib/crypto" | ||||
| 	fmux "github.com/hashicorp/yamux" | ||||
| ) | ||||
|  | ||||
| type Control struct { | ||||
| 	// uniq id got from frps, attach it in loginMsg | ||||
| 	runId string | ||||
|  | ||||
| 	// manage all proxies | ||||
| 	pxyCfgs map[string]config.ProxyConf | ||||
| 	pm      *proxy.ProxyManager | ||||
|  | ||||
| 	// manage all visitors | ||||
| 	vm *VisitorManager | ||||
|  | ||||
| 	// control connection | ||||
| 	conn frpNet.Conn | ||||
|  | ||||
| 	// tcp stream multiplexing, if enabled | ||||
| 	session *fmux.Session | ||||
|  | ||||
| 	// put a message in this channel to send it over control connection to server | ||||
| 	sendCh chan (msg.Message) | ||||
|  | ||||
| 	// read from this channel to get the next message sent by server | ||||
| 	readCh chan (msg.Message) | ||||
|  | ||||
| 	// goroutines can block by reading from this channel, it will be closed only in reader() when control connection is closed | ||||
| 	closedCh chan struct{} | ||||
|  | ||||
| 	closedDoneCh chan struct{} | ||||
|  | ||||
| 	// last time got the Pong message | ||||
| 	lastPong time.Time | ||||
|  | ||||
| 	readerShutdown     *shutdown.Shutdown | ||||
| 	writerShutdown     *shutdown.Shutdown | ||||
| 	msgHandlerShutdown *shutdown.Shutdown | ||||
|  | ||||
| 	mu sync.RWMutex | ||||
|  | ||||
| 	log.Logger | ||||
| } | ||||
|  | ||||
| func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) *Control { | ||||
| 	ctl := &Control{ | ||||
| 		runId:              runId, | ||||
| 		conn:               conn, | ||||
| 		session:            session, | ||||
| 		pxyCfgs:            pxyCfgs, | ||||
| 		sendCh:             make(chan msg.Message, 100), | ||||
| 		readCh:             make(chan msg.Message, 100), | ||||
| 		closedCh:           make(chan struct{}), | ||||
| 		closedDoneCh:       make(chan struct{}), | ||||
| 		readerShutdown:     shutdown.New(), | ||||
| 		writerShutdown:     shutdown.New(), | ||||
| 		msgHandlerShutdown: shutdown.New(), | ||||
| 		Logger:             log.NewPrefixLogger(""), | ||||
| 	} | ||||
| 	ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId) | ||||
|  | ||||
| 	ctl.vm = NewVisitorManager(ctl) | ||||
| 	ctl.vm.Reload(visitorCfgs) | ||||
| 	return ctl | ||||
| } | ||||
|  | ||||
| func (ctl *Control) Run() { | ||||
| 	go ctl.worker() | ||||
|  | ||||
| 	// start all proxies | ||||
| 	ctl.pm.Reload(ctl.pxyCfgs) | ||||
|  | ||||
| 	// start all visitors | ||||
| 	go ctl.vm.Run() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) { | ||||
| 	workConn, err := ctl.connectServer() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	m := &msg.NewWorkConn{ | ||||
| 		RunId: ctl.runId, | ||||
| 	} | ||||
| 	if err = msg.WriteMsg(workConn, m); err != nil { | ||||
| 		ctl.Warn("work connection write to server error: %v", err) | ||||
| 		workConn.Close() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var startMsg msg.StartWorkConn | ||||
| 	if err = msg.ReadMsgInto(workConn, &startMsg); err != nil { | ||||
| 		ctl.Error("work connection closed, %v", err) | ||||
| 		workConn.Close() | ||||
| 		return | ||||
| 	} | ||||
| 	workConn.AddLogPrefix(startMsg.ProxyName) | ||||
|  | ||||
| 	// dispatch this work connection to related proxy | ||||
| 	ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn) | ||||
| } | ||||
|  | ||||
| func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) { | ||||
| 	// Server will return NewProxyResp message to each NewProxy message. | ||||
| 	// Start a new proxy handler if no error got | ||||
| 	err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error) | ||||
| 	if err != nil { | ||||
| 		ctl.Warn("[%s] start error: %v", inMsg.ProxyName, err) | ||||
| 	} else { | ||||
| 		ctl.Info("[%s] start proxy success", inMsg.ProxyName) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (ctl *Control) Close() error { | ||||
| 	ctl.pm.Close() | ||||
| 	ctl.conn.Close() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ClosedDoneCh returns a channel which will be closed after all resources are released | ||||
| func (ctl *Control) ClosedDoneCh() <-chan struct{} { | ||||
| 	return ctl.closedDoneCh | ||||
| } | ||||
|  | ||||
| // connectServer return a new connection to frps | ||||
| func (ctl *Control) connectServer() (conn frpNet.Conn, err error) { | ||||
| 	if g.GlbClientCfg.TcpMux { | ||||
| 		stream, errRet := ctl.session.OpenStream() | ||||
| 		if errRet != nil { | ||||
| 			err = errRet | ||||
| 			ctl.Warn("start new connection to server error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 		conn = frpNet.WrapConn(stream) | ||||
| 	} else { | ||||
| 		conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol, | ||||
| 			fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort)) | ||||
| 		if err != nil { | ||||
| 			ctl.Warn("start new connection to server error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // reader read all messages from frps and send to readCh | ||||
| func (ctl *Control) reader() { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			ctl.Error("panic error: %v", err) | ||||
| 			ctl.Error(string(debug.Stack())) | ||||
| 		} | ||||
| 	}() | ||||
| 	defer ctl.readerShutdown.Done() | ||||
| 	defer close(ctl.closedCh) | ||||
|  | ||||
| 	encReader := crypto.NewReader(ctl.conn, []byte(g.GlbClientCfg.Token)) | ||||
| 	for { | ||||
| 		if m, err := msg.ReadMsg(encReader); err != nil { | ||||
| 			if err == io.EOF { | ||||
| 				ctl.Debug("read from control connection EOF") | ||||
| 				return | ||||
| 			} else { | ||||
| 				ctl.Warn("read error: %v", err) | ||||
| 				return | ||||
| 			} | ||||
| 		} else { | ||||
| 			ctl.readCh <- m | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // writer writes messages got from sendCh to frps | ||||
| func (ctl *Control) writer() { | ||||
| 	defer ctl.writerShutdown.Done() | ||||
| 	encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbClientCfg.Token)) | ||||
| 	if err != nil { | ||||
| 		ctl.conn.Error("crypto new writer error: %v", err) | ||||
| 		ctl.conn.Close() | ||||
| 		return | ||||
| 	} | ||||
| 	for { | ||||
| 		if m, ok := <-ctl.sendCh; !ok { | ||||
| 			ctl.Info("control writer is closing") | ||||
| 			return | ||||
| 		} else { | ||||
| 			if err := msg.WriteMsg(encWriter, m); err != nil { | ||||
| 				ctl.Warn("write message to control connection error: %v", err) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // msgHandler handles all channel events and do corresponding operations. | ||||
| func (ctl *Control) msgHandler() { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			ctl.Error("panic error: %v", err) | ||||
| 			ctl.Error(string(debug.Stack())) | ||||
| 		} | ||||
| 	}() | ||||
| 	defer ctl.msgHandlerShutdown.Done() | ||||
|  | ||||
| 	hbSend := time.NewTicker(time.Duration(g.GlbClientCfg.HeartBeatInterval) * time.Second) | ||||
| 	defer hbSend.Stop() | ||||
| 	hbCheck := time.NewTicker(time.Second) | ||||
| 	defer hbCheck.Stop() | ||||
|  | ||||
| 	ctl.lastPong = time.Now() | ||||
|  | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-hbSend.C: | ||||
| 			// send heartbeat to server | ||||
| 			ctl.Debug("send heartbeat to server") | ||||
| 			ctl.sendCh <- &msg.Ping{} | ||||
| 		case <-hbCheck.C: | ||||
| 			if time.Since(ctl.lastPong) > time.Duration(g.GlbClientCfg.HeartBeatTimeout)*time.Second { | ||||
| 				ctl.Warn("heartbeat timeout") | ||||
| 				// let reader() stop | ||||
| 				ctl.conn.Close() | ||||
| 				return | ||||
| 			} | ||||
| 		case rawMsg, ok := <-ctl.readCh: | ||||
| 			if !ok { | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			switch m := rawMsg.(type) { | ||||
| 			case *msg.ReqWorkConn: | ||||
| 				go ctl.HandleReqWorkConn(m) | ||||
| 			case *msg.NewProxyResp: | ||||
| 				ctl.HandleNewProxyResp(m) | ||||
| 			case *msg.Pong: | ||||
| 				ctl.lastPong = time.Now() | ||||
| 				ctl.Debug("receive heartbeat from server") | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // If controler is notified by closedCh, reader and writer and handler will exit | ||||
| func (ctl *Control) worker() { | ||||
| 	go ctl.msgHandler() | ||||
| 	go ctl.reader() | ||||
| 	go ctl.writer() | ||||
|  | ||||
| 	select { | ||||
| 	case <-ctl.closedCh: | ||||
| 		// close related channels and wait until other goroutines done | ||||
| 		close(ctl.readCh) | ||||
| 		ctl.readerShutdown.WaitDone() | ||||
| 		ctl.msgHandlerShutdown.WaitDone() | ||||
|  | ||||
| 		close(ctl.sendCh) | ||||
| 		ctl.writerShutdown.WaitDone() | ||||
|  | ||||
| 		ctl.pm.Close() | ||||
| 		ctl.vm.Close() | ||||
|  | ||||
| 		close(ctl.closedDoneCh) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (ctl *Control) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error { | ||||
| 	ctl.vm.Reload(visitorCfgs) | ||||
| 	ctl.pm.Reload(pxyCfgs) | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										28
									
								
								client/event/event.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								client/event/event.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| package event | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
| ) | ||||
|  | ||||
| type EventType int | ||||
|  | ||||
| const ( | ||||
| 	EvStartProxy EventType = iota | ||||
| 	EvCloseProxy | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrPayloadType = errors.New("error payload type") | ||||
| ) | ||||
|  | ||||
| type EventHandler func(evType EventType, payload interface{}) error | ||||
|  | ||||
| type StartProxyPayload struct { | ||||
| 	NewProxyMsg *msg.NewProxy | ||||
| } | ||||
|  | ||||
| type CloseProxyPayload struct { | ||||
| 	CloseProxyMsg *msg.CloseProxy | ||||
| } | ||||
							
								
								
									
										178
									
								
								client/health/health.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								client/health/health.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package health | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrHealthCheckType = errors.New("error health check type") | ||||
| ) | ||||
|  | ||||
| type HealthCheckMonitor struct { | ||||
| 	checkType      string | ||||
| 	interval       time.Duration | ||||
| 	timeout        time.Duration | ||||
| 	maxFailedTimes int | ||||
|  | ||||
| 	// For tcp | ||||
| 	addr string | ||||
|  | ||||
| 	// For http | ||||
| 	url string | ||||
|  | ||||
| 	failedTimes    uint64 | ||||
| 	statusOK       bool | ||||
| 	statusNormalFn func() | ||||
| 	statusFailedFn func() | ||||
|  | ||||
| 	ctx    context.Context | ||||
| 	cancel context.CancelFunc | ||||
|  | ||||
| 	l log.Logger | ||||
| } | ||||
|  | ||||
| func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFailedTimes int, addr string, url string, | ||||
| 	statusNormalFn func(), statusFailedFn func()) *HealthCheckMonitor { | ||||
|  | ||||
| 	if intervalS <= 0 { | ||||
| 		intervalS = 10 | ||||
| 	} | ||||
| 	if timeoutS <= 0 { | ||||
| 		timeoutS = 3 | ||||
| 	} | ||||
| 	if maxFailedTimes <= 0 { | ||||
| 		maxFailedTimes = 1 | ||||
| 	} | ||||
| 	ctx, cancel := context.WithCancel(context.Background()) | ||||
| 	return &HealthCheckMonitor{ | ||||
| 		checkType:      checkType, | ||||
| 		interval:       time.Duration(intervalS) * time.Second, | ||||
| 		timeout:        time.Duration(timeoutS) * time.Second, | ||||
| 		maxFailedTimes: maxFailedTimes, | ||||
| 		addr:           addr, | ||||
| 		url:            url, | ||||
| 		statusOK:       false, | ||||
| 		statusNormalFn: statusNormalFn, | ||||
| 		statusFailedFn: statusFailedFn, | ||||
| 		ctx:            ctx, | ||||
| 		cancel:         cancel, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (monitor *HealthCheckMonitor) SetLogger(l log.Logger) { | ||||
| 	monitor.l = l | ||||
| } | ||||
|  | ||||
| func (monitor *HealthCheckMonitor) Start() { | ||||
| 	go monitor.checkWorker() | ||||
| } | ||||
|  | ||||
| func (monitor *HealthCheckMonitor) Stop() { | ||||
| 	monitor.cancel() | ||||
| } | ||||
|  | ||||
| func (monitor *HealthCheckMonitor) checkWorker() { | ||||
| 	for { | ||||
| 		ctx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout)) | ||||
| 		err := monitor.doCheck(ctx) | ||||
|  | ||||
| 		// check if this monitor has been closed | ||||
| 		select { | ||||
| 		case <-ctx.Done(): | ||||
| 			cancel() | ||||
| 			return | ||||
| 		default: | ||||
| 			cancel() | ||||
| 		} | ||||
|  | ||||
| 		if err == nil { | ||||
| 			if monitor.l != nil { | ||||
| 				monitor.l.Trace("do one health check success") | ||||
| 			} | ||||
| 			if !monitor.statusOK && monitor.statusNormalFn != nil { | ||||
| 				if monitor.l != nil { | ||||
| 					monitor.l.Info("health check status change to success") | ||||
| 				} | ||||
| 				monitor.statusOK = true | ||||
| 				monitor.statusNormalFn() | ||||
| 			} | ||||
| 		} else { | ||||
| 			if monitor.l != nil { | ||||
| 				monitor.l.Warn("do one health check failed: %v", err) | ||||
| 			} | ||||
| 			monitor.failedTimes++ | ||||
| 			if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil { | ||||
| 				if monitor.l != nil { | ||||
| 					monitor.l.Warn("health check status change to failed") | ||||
| 				} | ||||
| 				monitor.statusOK = false | ||||
| 				monitor.statusFailedFn() | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		time.Sleep(monitor.interval) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (monitor *HealthCheckMonitor) doCheck(ctx context.Context) error { | ||||
| 	switch monitor.checkType { | ||||
| 	case "tcp": | ||||
| 		return monitor.doTcpCheck(ctx) | ||||
| 	case "http": | ||||
| 		return monitor.doHttpCheck(ctx) | ||||
| 	default: | ||||
| 		return ErrHealthCheckType | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (monitor *HealthCheckMonitor) doTcpCheck(ctx context.Context) error { | ||||
| 	// if tcp address is not specified, always return nil | ||||
| 	if monitor.addr == "" { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	var d net.Dialer | ||||
| 	conn, err := d.DialContext(ctx, "tcp", monitor.addr) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	conn.Close() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error { | ||||
| 	req, err := http.NewRequest("GET", monitor.url, nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	resp, err := http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if resp.StatusCode/100 != 2 { | ||||
| 		return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										455
									
								
								client/proxy/proxy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										455
									
								
								client/proxy/proxy.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,455 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
| 	"github.com/fatedier/frp/models/plugin" | ||||
| 	"github.com/fatedier/frp/models/proto/udp" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	"github.com/fatedier/golib/errors" | ||||
| 	frpIo "github.com/fatedier/golib/io" | ||||
| 	"github.com/fatedier/golib/pool" | ||||
| ) | ||||
|  | ||||
| // Proxy defines how to handle work connections for different proxy type. | ||||
| type Proxy interface { | ||||
| 	Run() error | ||||
|  | ||||
| 	// InWorkConn accept work connections registered to server. | ||||
| 	InWorkConn(conn frpNet.Conn) | ||||
|  | ||||
| 	Close() | ||||
| 	log.Logger | ||||
| } | ||||
|  | ||||
| func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) { | ||||
| 	baseProxy := BaseProxy{ | ||||
| 		Logger: log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName), | ||||
| 	} | ||||
| 	switch cfg := pxyConf.(type) { | ||||
| 	case *config.TcpProxyConf: | ||||
| 		pxy = &TcpProxy{ | ||||
| 			BaseProxy: &baseProxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	case *config.UdpProxyConf: | ||||
| 		pxy = &UdpProxy{ | ||||
| 			BaseProxy: &baseProxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	case *config.HttpProxyConf: | ||||
| 		pxy = &HttpProxy{ | ||||
| 			BaseProxy: &baseProxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	case *config.HttpsProxyConf: | ||||
| 		pxy = &HttpsProxy{ | ||||
| 			BaseProxy: &baseProxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	case *config.StcpProxyConf: | ||||
| 		pxy = &StcpProxy{ | ||||
| 			BaseProxy: &baseProxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	case *config.XtcpProxyConf: | ||||
| 		pxy = &XtcpProxy{ | ||||
| 			BaseProxy: &baseProxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type BaseProxy struct { | ||||
| 	closed bool | ||||
| 	mu     sync.RWMutex | ||||
| 	log.Logger | ||||
| } | ||||
|  | ||||
| // TCP | ||||
| type TcpProxy struct { | ||||
| 	*BaseProxy | ||||
|  | ||||
| 	cfg         *config.TcpProxyConf | ||||
| 	proxyPlugin plugin.Plugin | ||||
| } | ||||
|  | ||||
| func (pxy *TcpProxy) Run() (err error) { | ||||
| 	if pxy.cfg.Plugin != "" { | ||||
| 		pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pxy *TcpProxy) Close() { | ||||
| 	if pxy.proxyPlugin != nil { | ||||
| 		pxy.proxyPlugin.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) { | ||||
| 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, | ||||
| 		[]byte(g.GlbClientCfg.Token)) | ||||
| } | ||||
|  | ||||
| // HTTP | ||||
| type HttpProxy struct { | ||||
| 	*BaseProxy | ||||
|  | ||||
| 	cfg         *config.HttpProxyConf | ||||
| 	proxyPlugin plugin.Plugin | ||||
| } | ||||
|  | ||||
| func (pxy *HttpProxy) Run() (err error) { | ||||
| 	if pxy.cfg.Plugin != "" { | ||||
| 		pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pxy *HttpProxy) Close() { | ||||
| 	if pxy.proxyPlugin != nil { | ||||
| 		pxy.proxyPlugin.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) { | ||||
| 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, | ||||
| 		[]byte(g.GlbClientCfg.Token)) | ||||
| } | ||||
|  | ||||
| // HTTPS | ||||
| type HttpsProxy struct { | ||||
| 	*BaseProxy | ||||
|  | ||||
| 	cfg         *config.HttpsProxyConf | ||||
| 	proxyPlugin plugin.Plugin | ||||
| } | ||||
|  | ||||
| func (pxy *HttpsProxy) Run() (err error) { | ||||
| 	if pxy.cfg.Plugin != "" { | ||||
| 		pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pxy *HttpsProxy) Close() { | ||||
| 	if pxy.proxyPlugin != nil { | ||||
| 		pxy.proxyPlugin.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) { | ||||
| 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, | ||||
| 		[]byte(g.GlbClientCfg.Token)) | ||||
| } | ||||
|  | ||||
| // STCP | ||||
| type StcpProxy struct { | ||||
| 	*BaseProxy | ||||
|  | ||||
| 	cfg         *config.StcpProxyConf | ||||
| 	proxyPlugin plugin.Plugin | ||||
| } | ||||
|  | ||||
| func (pxy *StcpProxy) Run() (err error) { | ||||
| 	if pxy.cfg.Plugin != "" { | ||||
| 		pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pxy *StcpProxy) Close() { | ||||
| 	if pxy.proxyPlugin != nil { | ||||
| 		pxy.proxyPlugin.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) { | ||||
| 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn, | ||||
| 		[]byte(g.GlbClientCfg.Token)) | ||||
| } | ||||
|  | ||||
| // XTCP | ||||
| type XtcpProxy struct { | ||||
| 	*BaseProxy | ||||
|  | ||||
| 	cfg         *config.XtcpProxyConf | ||||
| 	proxyPlugin plugin.Plugin | ||||
| } | ||||
|  | ||||
| func (pxy *XtcpProxy) Run() (err error) { | ||||
| 	if pxy.cfg.Plugin != "" { | ||||
| 		pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pxy *XtcpProxy) Close() { | ||||
| 	if pxy.proxyPlugin != nil { | ||||
| 		pxy.proxyPlugin.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) { | ||||
| 	defer conn.Close() | ||||
| 	var natHoleSidMsg msg.NatHoleSid | ||||
| 	err := msg.ReadMsgInto(conn, &natHoleSidMsg) | ||||
| 	if err != nil { | ||||
| 		pxy.Error("xtcp read from workConn error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	natHoleClientMsg := &msg.NatHoleClient{ | ||||
| 		ProxyName: pxy.cfg.ProxyName, | ||||
| 		Sid:       natHoleSidMsg.Sid, | ||||
| 	} | ||||
| 	raddr, _ := net.ResolveUDPAddr("udp", | ||||
| 		fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort)) | ||||
| 	clientConn, err := net.DialUDP("udp", nil, raddr) | ||||
| 	defer clientConn.Close() | ||||
|  | ||||
| 	err = msg.WriteMsg(clientConn, natHoleClientMsg) | ||||
| 	if err != nil { | ||||
| 		pxy.Error("send natHoleClientMsg to server error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Wait for client address at most 5 seconds. | ||||
| 	var natHoleRespMsg msg.NatHoleResp | ||||
| 	clientConn.SetReadDeadline(time.Now().Add(5 * time.Second)) | ||||
|  | ||||
| 	buf := pool.GetBuf(1024) | ||||
| 	n, err := clientConn.Read(buf) | ||||
| 	if err != nil { | ||||
| 		pxy.Error("get natHoleRespMsg error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg) | ||||
| 	if err != nil { | ||||
| 		pxy.Error("get natHoleRespMsg error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	clientConn.SetReadDeadline(time.Time{}) | ||||
| 	clientConn.Close() | ||||
|  | ||||
| 	if natHoleRespMsg.Error != "" { | ||||
| 		pxy.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr) | ||||
|  | ||||
| 	// Send sid to visitor udp address. | ||||
| 	time.Sleep(time.Second) | ||||
| 	laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String()) | ||||
| 	daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr) | ||||
| 	if err != nil { | ||||
| 		pxy.Error("resolve visitor udp address error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	lConn, err := net.DialUDP("udp", laddr, daddr) | ||||
| 	if err != nil { | ||||
| 		pxy.Error("dial visitor udp address error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	lConn.Write([]byte(natHoleRespMsg.Sid)) | ||||
|  | ||||
| 	kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr) | ||||
| 	if err != nil { | ||||
| 		pxy.Error("create kcp connection from udp connection error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, | ||||
| 		frpNet.WrapConn(kcpConn), []byte(pxy.cfg.Sk)) | ||||
| } | ||||
|  | ||||
| // UDP | ||||
| type UdpProxy struct { | ||||
| 	*BaseProxy | ||||
|  | ||||
| 	cfg *config.UdpProxyConf | ||||
|  | ||||
| 	localAddr *net.UDPAddr | ||||
| 	readCh    chan *msg.UdpPacket | ||||
|  | ||||
| 	// include msg.UdpPacket and msg.Ping | ||||
| 	sendCh   chan msg.Message | ||||
| 	workConn frpNet.Conn | ||||
| } | ||||
|  | ||||
| func (pxy *UdpProxy) Run() (err error) { | ||||
| 	pxy.localAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pxy.cfg.LocalIp, pxy.cfg.LocalPort)) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pxy *UdpProxy) Close() { | ||||
| 	pxy.mu.Lock() | ||||
| 	defer pxy.mu.Unlock() | ||||
|  | ||||
| 	if !pxy.closed { | ||||
| 		pxy.closed = true | ||||
| 		if pxy.workConn != nil { | ||||
| 			pxy.workConn.Close() | ||||
| 		} | ||||
| 		if pxy.readCh != nil { | ||||
| 			close(pxy.readCh) | ||||
| 		} | ||||
| 		if pxy.sendCh != nil { | ||||
| 			close(pxy.sendCh) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) { | ||||
| 	pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String()) | ||||
| 	// close resources releated with old workConn | ||||
| 	pxy.Close() | ||||
|  | ||||
| 	pxy.mu.Lock() | ||||
| 	pxy.workConn = conn | ||||
| 	pxy.readCh = make(chan *msg.UdpPacket, 1024) | ||||
| 	pxy.sendCh = make(chan msg.Message, 1024) | ||||
| 	pxy.closed = false | ||||
| 	pxy.mu.Unlock() | ||||
|  | ||||
| 	workConnReaderFn := func(conn net.Conn, readCh chan *msg.UdpPacket) { | ||||
| 		for { | ||||
| 			var udpMsg msg.UdpPacket | ||||
| 			if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil { | ||||
| 				pxy.Warn("read from workConn for udp error: %v", errRet) | ||||
| 				return | ||||
| 			} | ||||
| 			if errRet := errors.PanicToError(func() { | ||||
| 				pxy.Trace("get udp package from workConn: %s", udpMsg.Content) | ||||
| 				readCh <- &udpMsg | ||||
| 			}); errRet != nil { | ||||
| 				pxy.Info("reader goroutine for udp work connection closed: %v", errRet) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) { | ||||
| 		defer func() { | ||||
| 			pxy.Info("writer goroutine for udp work connection closed") | ||||
| 		}() | ||||
| 		var errRet error | ||||
| 		for rawMsg := range sendCh { | ||||
| 			switch m := rawMsg.(type) { | ||||
| 			case *msg.UdpPacket: | ||||
| 				pxy.Trace("send udp package to workConn: %s", m.Content) | ||||
| 			case *msg.Ping: | ||||
| 				pxy.Trace("send ping message to udp workConn") | ||||
| 			} | ||||
| 			if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil { | ||||
| 				pxy.Error("udp work write error: %v", errRet) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	heartbeatFn := func(conn net.Conn, sendCh chan msg.Message) { | ||||
| 		var errRet error | ||||
| 		for { | ||||
| 			time.Sleep(time.Duration(30) * time.Second) | ||||
| 			if errRet = errors.PanicToError(func() { | ||||
| 				sendCh <- &msg.Ping{} | ||||
| 			}); errRet != nil { | ||||
| 				pxy.Trace("heartbeat goroutine for udp work connection closed") | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	go workConnSenderFn(pxy.workConn, pxy.sendCh) | ||||
| 	go workConnReaderFn(pxy.workConn, pxy.readCh) | ||||
| 	go heartbeatFn(pxy.workConn, pxy.sendCh) | ||||
| 	udp.Forwarder(pxy.localAddr, pxy.readCh, pxy.sendCh) | ||||
| } | ||||
|  | ||||
| // Common handler for tcp work connections. | ||||
| func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin, | ||||
| 	baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte) { | ||||
|  | ||||
| 	var ( | ||||
| 		remote io.ReadWriteCloser | ||||
| 		err    error | ||||
| 	) | ||||
| 	remote = workConn | ||||
|  | ||||
| 	if baseInfo.UseEncryption { | ||||
| 		remote, err = frpIo.WithEncryption(remote, encKey) | ||||
| 		if err != nil { | ||||
| 			workConn.Close() | ||||
| 			workConn.Error("create encryption stream error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if baseInfo.UseCompression { | ||||
| 		remote = frpIo.WithCompression(remote) | ||||
| 	} | ||||
|  | ||||
| 	if proxyPlugin != nil { | ||||
| 		// if plugin is set, let plugin handle connections first | ||||
| 		workConn.Debug("handle by plugin: %s", proxyPlugin.Name()) | ||||
| 		proxyPlugin.Handle(remote, workConn) | ||||
| 		workConn.Debug("handle by plugin finished") | ||||
| 		return | ||||
| 	} else { | ||||
| 		localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort)) | ||||
| 		if err != nil { | ||||
| 			workConn.Close() | ||||
| 			workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(), | ||||
| 			localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String()) | ||||
| 		frpIo.Join(localConn, remote) | ||||
| 		workConn.Debug("join connections closed") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										139
									
								
								client/proxy/proxy_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								client/proxy/proxy_manager.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/fatedier/frp/client/event" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	"github.com/fatedier/golib/errors" | ||||
| ) | ||||
|  | ||||
| type ProxyManager struct { | ||||
| 	sendCh  chan (msg.Message) | ||||
| 	proxies map[string]*ProxyWrapper | ||||
|  | ||||
| 	closed bool | ||||
| 	mu     sync.RWMutex | ||||
|  | ||||
| 	logPrefix string | ||||
| 	log.Logger | ||||
| } | ||||
|  | ||||
| func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string) *ProxyManager { | ||||
| 	return &ProxyManager{ | ||||
| 		proxies:   make(map[string]*ProxyWrapper), | ||||
| 		sendCh:    msgSendCh, | ||||
| 		closed:    false, | ||||
| 		logPrefix: logPrefix, | ||||
| 		Logger:    log.NewPrefixLogger(logPrefix), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pm *ProxyManager) StartProxy(name string, remoteAddr string, serverRespErr string) error { | ||||
| 	pm.mu.RLock() | ||||
| 	pxy, ok := pm.proxies[name] | ||||
| 	pm.mu.RUnlock() | ||||
| 	if !ok { | ||||
| 		return fmt.Errorf("proxy [%s] not found", name) | ||||
| 	} | ||||
|  | ||||
| 	err := pxy.SetRunningStatus(remoteAddr, serverRespErr) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (pm *ProxyManager) Close() { | ||||
| 	pm.mu.Lock() | ||||
| 	defer pm.mu.Unlock() | ||||
| 	for _, pxy := range pm.proxies { | ||||
| 		pxy.Stop() | ||||
| 	} | ||||
| 	pm.proxies = make(map[string]*ProxyWrapper) | ||||
| } | ||||
|  | ||||
| func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn) { | ||||
| 	pm.mu.RLock() | ||||
| 	pw, ok := pm.proxies[name] | ||||
| 	pm.mu.RUnlock() | ||||
| 	if ok { | ||||
| 		pw.InWorkConn(workConn) | ||||
| 	} else { | ||||
| 		workConn.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pm *ProxyManager) HandleEvent(evType event.EventType, payload interface{}) error { | ||||
| 	var m msg.Message | ||||
| 	switch e := payload.(type) { | ||||
| 	case *event.StartProxyPayload: | ||||
| 		m = e.NewProxyMsg | ||||
| 	case *event.CloseProxyPayload: | ||||
| 		m = e.CloseProxyMsg | ||||
| 	default: | ||||
| 		return event.ErrPayloadType | ||||
| 	} | ||||
|  | ||||
| 	err := errors.PanicToError(func() { | ||||
| 		pm.sendCh <- m | ||||
| 	}) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus { | ||||
| 	ps := make([]*ProxyStatus, 0) | ||||
| 	pm.mu.RLock() | ||||
| 	defer pm.mu.RUnlock() | ||||
| 	for _, pxy := range pm.proxies { | ||||
| 		ps = append(ps, pxy.GetStatus()) | ||||
| 	} | ||||
| 	return ps | ||||
| } | ||||
|  | ||||
| func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) { | ||||
| 	pm.mu.Lock() | ||||
| 	defer pm.mu.Unlock() | ||||
|  | ||||
| 	delPxyNames := make([]string, 0) | ||||
| 	for name, pxy := range pm.proxies { | ||||
| 		del := false | ||||
| 		cfg, ok := pxyCfgs[name] | ||||
| 		if !ok { | ||||
| 			del = true | ||||
| 		} else { | ||||
| 			if !pxy.Cfg.Compare(cfg) { | ||||
| 				del = true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if del { | ||||
| 			delPxyNames = append(delPxyNames, name) | ||||
| 			delete(pm.proxies, name) | ||||
|  | ||||
| 			pxy.Stop() | ||||
| 		} | ||||
| 	} | ||||
| 	if len(delPxyNames) > 0 { | ||||
| 		pm.Info("proxy removed: %v", delPxyNames) | ||||
| 	} | ||||
|  | ||||
| 	addPxyNames := make([]string, 0) | ||||
| 	for name, cfg := range pxyCfgs { | ||||
| 		if _, ok := pm.proxies[name]; !ok { | ||||
| 			pxy := NewProxyWrapper(cfg, pm.HandleEvent, pm.logPrefix) | ||||
| 			pm.proxies[name] = pxy | ||||
| 			addPxyNames = append(addPxyNames, name) | ||||
|  | ||||
| 			pxy.Start() | ||||
| 		} | ||||
| 	} | ||||
| 	if len(addPxyNames) > 0 { | ||||
| 		pm.Info("proxy added: %v", addPxyNames) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										244
									
								
								client/proxy/proxy_wrapper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								client/proxy/proxy_wrapper.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,244 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/client/event" | ||||
| 	"github.com/fatedier/frp/client/health" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	"github.com/fatedier/golib/errors" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	ProxyStatusNew         = "new" | ||||
| 	ProxyStatusWaitStart   = "wait start" | ||||
| 	ProxyStatusStartErr    = "start error" | ||||
| 	ProxyStatusRunning     = "running" | ||||
| 	ProxyStatusCheckFailed = "check failed" | ||||
| 	ProxyStatusClosed      = "closed" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	statusCheckInterval time.Duration = 3 * time.Second | ||||
| 	waitResponseTimeout               = 20 * time.Second | ||||
| 	startErrTimeout                   = 30 * time.Second | ||||
| ) | ||||
|  | ||||
| type ProxyStatus struct { | ||||
| 	Name   string           `json:"name"` | ||||
| 	Type   string           `json:"type"` | ||||
| 	Status string           `json:"status"` | ||||
| 	Err    string           `json:"err"` | ||||
| 	Cfg    config.ProxyConf `json:"cfg"` | ||||
|  | ||||
| 	// Got from server. | ||||
| 	RemoteAddr string `json:"remote_addr"` | ||||
| } | ||||
|  | ||||
| type ProxyWrapper struct { | ||||
| 	ProxyStatus | ||||
|  | ||||
| 	// underlying proxy | ||||
| 	pxy Proxy | ||||
|  | ||||
| 	// if ProxyConf has healcheck config | ||||
| 	// monitor will watch if it is alive | ||||
| 	monitor *health.HealthCheckMonitor | ||||
|  | ||||
| 	// event handler | ||||
| 	handler event.EventHandler | ||||
|  | ||||
| 	health           uint32 | ||||
| 	lastSendStartMsg time.Time | ||||
| 	lastStartErr     time.Time | ||||
| 	closeCh          chan struct{} | ||||
| 	healthNotifyCh   chan struct{} | ||||
| 	mu               sync.RWMutex | ||||
|  | ||||
| 	log.Logger | ||||
| } | ||||
|  | ||||
| func NewProxyWrapper(cfg config.ProxyConf, eventHandler event.EventHandler, logPrefix string) *ProxyWrapper { | ||||
| 	baseInfo := cfg.GetBaseInfo() | ||||
| 	pw := &ProxyWrapper{ | ||||
| 		ProxyStatus: ProxyStatus{ | ||||
| 			Name:   baseInfo.ProxyName, | ||||
| 			Type:   baseInfo.ProxyType, | ||||
| 			Status: ProxyStatusNew, | ||||
| 			Cfg:    cfg, | ||||
| 		}, | ||||
| 		closeCh:        make(chan struct{}), | ||||
| 		healthNotifyCh: make(chan struct{}), | ||||
| 		handler:        eventHandler, | ||||
| 		Logger:         log.NewPrefixLogger(logPrefix), | ||||
| 	} | ||||
| 	pw.AddLogPrefix(pw.Name) | ||||
|  | ||||
| 	if baseInfo.HealthCheckType != "" { | ||||
| 		pw.health = 1 // means failed | ||||
| 		pw.monitor = health.NewHealthCheckMonitor(baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS, | ||||
| 			baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr, | ||||
| 			baseInfo.HealthCheckUrl, pw.statusNormalCallback, pw.statusFailedCallback) | ||||
| 		pw.monitor.SetLogger(pw.Logger) | ||||
| 		pw.Trace("enable health check monitor") | ||||
| 	} | ||||
|  | ||||
| 	pw.pxy = NewProxy(pw.Cfg) | ||||
| 	return pw | ||||
| } | ||||
|  | ||||
| func (pw *ProxyWrapper) SetRunningStatus(remoteAddr string, respErr string) error { | ||||
| 	pw.mu.Lock() | ||||
| 	defer pw.mu.Unlock() | ||||
| 	if pw.Status != ProxyStatusWaitStart { | ||||
| 		return fmt.Errorf("status not wait start, ignore start message") | ||||
| 	} | ||||
|  | ||||
| 	pw.RemoteAddr = remoteAddr | ||||
| 	if respErr != "" { | ||||
| 		pw.Status = ProxyStatusStartErr | ||||
| 		pw.Err = respErr | ||||
| 		pw.lastStartErr = time.Now() | ||||
| 		return fmt.Errorf(pw.Err) | ||||
| 	} | ||||
|  | ||||
| 	if err := pw.pxy.Run(); err != nil { | ||||
| 		pw.Status = ProxyStatusStartErr | ||||
| 		pw.Err = err.Error() | ||||
| 		pw.lastStartErr = time.Now() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	pw.Status = ProxyStatusRunning | ||||
| 	pw.Err = "" | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (pw *ProxyWrapper) Start() { | ||||
| 	go pw.checkWorker() | ||||
| 	if pw.monitor != nil { | ||||
| 		go pw.monitor.Start() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pw *ProxyWrapper) Stop() { | ||||
| 	pw.mu.Lock() | ||||
| 	defer pw.mu.Unlock() | ||||
| 	close(pw.closeCh) | ||||
| 	close(pw.healthNotifyCh) | ||||
| 	pw.pxy.Close() | ||||
| 	if pw.monitor != nil { | ||||
| 		pw.monitor.Stop() | ||||
| 	} | ||||
| 	pw.Status = ProxyStatusClosed | ||||
|  | ||||
| 	pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{ | ||||
| 		CloseProxyMsg: &msg.CloseProxy{ | ||||
| 			ProxyName: pw.Name, | ||||
| 		}, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (pw *ProxyWrapper) checkWorker() { | ||||
| 	if pw.monitor != nil { | ||||
| 		// let monitor do check request first | ||||
| 		time.Sleep(500 * time.Millisecond) | ||||
| 	} | ||||
| 	for { | ||||
| 		// check proxy status | ||||
| 		now := time.Now() | ||||
| 		if atomic.LoadUint32(&pw.health) == 0 { | ||||
| 			pw.mu.Lock() | ||||
| 			if pw.Status == ProxyStatusNew || | ||||
| 				pw.Status == ProxyStatusCheckFailed || | ||||
| 				(pw.Status == ProxyStatusWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) || | ||||
| 				(pw.Status == ProxyStatusStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) { | ||||
|  | ||||
| 				pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart) | ||||
| 				pw.Status = ProxyStatusWaitStart | ||||
|  | ||||
| 				var newProxyMsg msg.NewProxy | ||||
| 				pw.Cfg.MarshalToMsg(&newProxyMsg) | ||||
| 				pw.lastSendStartMsg = now | ||||
| 				pw.handler(event.EvStartProxy, &event.StartProxyPayload{ | ||||
| 					NewProxyMsg: &newProxyMsg, | ||||
| 				}) | ||||
| 			} | ||||
| 			pw.mu.Unlock() | ||||
| 		} else { | ||||
| 			pw.mu.Lock() | ||||
| 			if pw.Status == ProxyStatusRunning || pw.Status == ProxyStatusWaitStart { | ||||
| 				pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{ | ||||
| 					CloseProxyMsg: &msg.CloseProxy{ | ||||
| 						ProxyName: pw.Name, | ||||
| 					}, | ||||
| 				}) | ||||
| 				pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusCheckFailed) | ||||
| 				pw.Status = ProxyStatusCheckFailed | ||||
| 			} | ||||
| 			pw.mu.Unlock() | ||||
| 		} | ||||
|  | ||||
| 		select { | ||||
| 		case <-pw.closeCh: | ||||
| 			return | ||||
| 		case <-time.After(statusCheckInterval): | ||||
| 		case <-pw.healthNotifyCh: | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pw *ProxyWrapper) statusNormalCallback() { | ||||
| 	atomic.StoreUint32(&pw.health, 0) | ||||
| 	errors.PanicToError(func() { | ||||
| 		select { | ||||
| 		case pw.healthNotifyCh <- struct{}{}: | ||||
| 		default: | ||||
| 		} | ||||
| 	}) | ||||
| 	pw.Info("health check success") | ||||
| } | ||||
|  | ||||
| func (pw *ProxyWrapper) statusFailedCallback() { | ||||
| 	atomic.StoreUint32(&pw.health, 1) | ||||
| 	errors.PanicToError(func() { | ||||
| 		select { | ||||
| 		case pw.healthNotifyCh <- struct{}{}: | ||||
| 		default: | ||||
| 		} | ||||
| 	}) | ||||
| 	pw.Info("health check failed") | ||||
| } | ||||
|  | ||||
| func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn) { | ||||
| 	pw.mu.RLock() | ||||
| 	pxy := pw.pxy | ||||
| 	pw.mu.RUnlock() | ||||
| 	if pxy != nil { | ||||
| 		workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String()) | ||||
| 		go pxy.InWorkConn(workConn) | ||||
| 	} else { | ||||
| 		workConn.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pw *ProxyWrapper) GetStatus() *ProxyStatus { | ||||
| 	pw.mu.RLock() | ||||
| 	defer pw.mu.RUnlock() | ||||
| 	ps := &ProxyStatus{ | ||||
| 		Name:       pw.Name, | ||||
| 		Type:       pw.Type, | ||||
| 		Status:     pw.Status, | ||||
| 		Err:        pw.Err, | ||||
| 		Cfg:        pw.Cfg, | ||||
| 		RemoteAddr: pw.RemoteAddr, | ||||
| 	} | ||||
| 	return ps | ||||
| } | ||||
							
								
								
									
										231
									
								
								client/service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								client/service.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,231 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package client | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"runtime" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/assets" | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
| 	"github.com/fatedier/frp/utils/util" | ||||
| 	"github.com/fatedier/frp/utils/version" | ||||
|  | ||||
| 	fmux "github.com/hashicorp/yamux" | ||||
| ) | ||||
|  | ||||
| type Service struct { | ||||
| 	// uniq id got from frps, attach it in loginMsg | ||||
| 	runId string | ||||
|  | ||||
| 	// manager control connection with server | ||||
| 	ctl   *Control | ||||
| 	ctlMu sync.RWMutex | ||||
|  | ||||
| 	pxyCfgs     map[string]config.ProxyConf | ||||
| 	visitorCfgs map[string]config.VisitorConf | ||||
| 	cfgMu       sync.RWMutex | ||||
|  | ||||
| 	exit     uint32 // 0 means not exit | ||||
| 	closedCh chan int | ||||
| } | ||||
|  | ||||
| func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service, err error) { | ||||
| 	// Init assets | ||||
| 	err = assets.Load("") | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("Load assets error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	svr = &Service{ | ||||
| 		pxyCfgs:     pxyCfgs, | ||||
| 		visitorCfgs: visitorCfgs, | ||||
| 		exit:        0, | ||||
| 		closedCh:    make(chan int), | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (svr *Service) GetController() *Control { | ||||
| 	svr.ctlMu.RLock() | ||||
| 	defer svr.ctlMu.RUnlock() | ||||
| 	return svr.ctl | ||||
| } | ||||
|  | ||||
| func (svr *Service) Run() error { | ||||
| 	// first login | ||||
| 	for { | ||||
| 		conn, session, err := svr.login() | ||||
| 		if err != nil { | ||||
| 			log.Warn("login to server failed: %v", err) | ||||
|  | ||||
| 			// if login_fail_exit is true, just exit this program | ||||
| 			// otherwise sleep a while and try again to connect to server | ||||
| 			if g.GlbClientCfg.LoginFailExit { | ||||
| 				return err | ||||
| 			} else { | ||||
| 				time.Sleep(10 * time.Second) | ||||
| 			} | ||||
| 		} else { | ||||
| 			// login success | ||||
| 			ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs) | ||||
| 			ctl.Run() | ||||
| 			svr.ctlMu.Lock() | ||||
| 			svr.ctl = ctl | ||||
| 			svr.ctlMu.Unlock() | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	go svr.keepControllerWorking() | ||||
|  | ||||
| 	if g.GlbClientCfg.AdminPort != 0 { | ||||
| 		err := svr.RunAdminServer(g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort) | ||||
| 		if err != nil { | ||||
| 			log.Warn("run admin server error: %v", err) | ||||
| 		} | ||||
| 		log.Info("admin server listen on %s:%d", g.GlbClientCfg.AdminAddr, g.GlbClientCfg.AdminPort) | ||||
| 	} | ||||
|  | ||||
| 	<-svr.closedCh | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (svr *Service) keepControllerWorking() { | ||||
| 	maxDelayTime := 20 * time.Second | ||||
| 	delayTime := time.Second | ||||
|  | ||||
| 	for { | ||||
| 		<-svr.ctl.ClosedDoneCh() | ||||
| 		if atomic.LoadUint32(&svr.exit) != 0 { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		for { | ||||
| 			log.Info("try to reconnect to server...") | ||||
| 			conn, session, err := svr.login() | ||||
| 			if err != nil { | ||||
| 				log.Warn("reconnect to server error: %v", err) | ||||
| 				time.Sleep(delayTime) | ||||
| 				delayTime = delayTime * 2 | ||||
| 				if delayTime > maxDelayTime { | ||||
| 					delayTime = maxDelayTime | ||||
| 				} | ||||
| 				continue | ||||
| 			} | ||||
| 			// reconnect success, init delayTime | ||||
| 			delayTime = time.Second | ||||
|  | ||||
| 			ctl := NewControl(svr.runId, conn, session, svr.pxyCfgs, svr.visitorCfgs) | ||||
| 			ctl.Run() | ||||
| 			svr.ctlMu.Lock() | ||||
| 			svr.ctl = ctl | ||||
| 			svr.ctlMu.Unlock() | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // login creates a connection to frps and registers it self as a client | ||||
| // conn: control connection | ||||
| // session: if it's not nil, using tcp mux | ||||
| func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) { | ||||
| 	conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol, | ||||
| 		fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort)) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			conn.Close() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	if g.GlbClientCfg.TcpMux { | ||||
| 		fmuxCfg := fmux.DefaultConfig() | ||||
| 		fmuxCfg.KeepAliveInterval = 20 * time.Second | ||||
| 		fmuxCfg.LogOutput = ioutil.Discard | ||||
| 		session, err = fmux.Client(conn, fmuxCfg) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		stream, errRet := session.OpenStream() | ||||
| 		if errRet != nil { | ||||
| 			session.Close() | ||||
| 			err = errRet | ||||
| 			return | ||||
| 		} | ||||
| 		conn = frpNet.WrapConn(stream) | ||||
| 	} | ||||
|  | ||||
| 	now := time.Now().Unix() | ||||
| 	loginMsg := &msg.Login{ | ||||
| 		Arch:         runtime.GOARCH, | ||||
| 		Os:           runtime.GOOS, | ||||
| 		PoolCount:    g.GlbClientCfg.PoolCount, | ||||
| 		User:         g.GlbClientCfg.User, | ||||
| 		Version:      version.Full(), | ||||
| 		PrivilegeKey: util.GetAuthKey(g.GlbClientCfg.Token, now), | ||||
| 		Timestamp:    now, | ||||
| 		RunId:        svr.runId, | ||||
| 	} | ||||
|  | ||||
| 	if err = msg.WriteMsg(conn, loginMsg); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var loginRespMsg msg.LoginResp | ||||
| 	conn.SetReadDeadline(time.Now().Add(10 * time.Second)) | ||||
| 	if err = msg.ReadMsgInto(conn, &loginRespMsg); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	conn.SetReadDeadline(time.Time{}) | ||||
|  | ||||
| 	if loginRespMsg.Error != "" { | ||||
| 		err = fmt.Errorf("%s", loginRespMsg.Error) | ||||
| 		log.Error("%s", loginRespMsg.Error) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	svr.runId = loginRespMsg.RunId | ||||
| 	g.GlbClientCfg.ServerUdpPort = loginRespMsg.ServerUdpPort | ||||
| 	log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) error { | ||||
| 	svr.cfgMu.Lock() | ||||
| 	svr.pxyCfgs = pxyCfgs | ||||
| 	svr.visitorCfgs = visitorCfgs | ||||
| 	svr.cfgMu.Unlock() | ||||
|  | ||||
| 	return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs) | ||||
| } | ||||
|  | ||||
| func (svr *Service) Close() { | ||||
| 	atomic.StoreUint32(&svr.exit, 1) | ||||
| 	svr.ctl.Close() | ||||
| 	close(svr.closedCh) | ||||
| } | ||||
							
								
								
									
										338
									
								
								client/visitor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										338
									
								
								client/visitor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,338 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package client | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"golang.org/x/net/ipv4" | ||||
|  | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
| 	"github.com/fatedier/frp/utils/util" | ||||
|  | ||||
| 	frpIo "github.com/fatedier/golib/io" | ||||
| 	"github.com/fatedier/golib/pool" | ||||
| ) | ||||
|  | ||||
| // Visitor is used for forward traffics from local port tot remote service. | ||||
| type Visitor interface { | ||||
| 	Run() error | ||||
| 	Close() | ||||
| 	log.Logger | ||||
| } | ||||
|  | ||||
| func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) { | ||||
| 	baseVisitor := BaseVisitor{ | ||||
| 		ctl:    ctl, | ||||
| 		Logger: log.NewPrefixLogger(cfg.GetBaseInfo().ProxyName), | ||||
| 	} | ||||
| 	switch cfg := cfg.(type) { | ||||
| 	case *config.StcpVisitorConf: | ||||
| 		visitor = &StcpVisitor{ | ||||
| 			BaseVisitor: &baseVisitor, | ||||
| 			cfg:         cfg, | ||||
| 		} | ||||
| 	case *config.XtcpVisitorConf: | ||||
| 		visitor = &XtcpVisitor{ | ||||
| 			BaseVisitor: &baseVisitor, | ||||
| 			cfg:         cfg, | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type BaseVisitor struct { | ||||
| 	ctl    *Control | ||||
| 	l      frpNet.Listener | ||||
| 	closed bool | ||||
| 	mu     sync.RWMutex | ||||
| 	log.Logger | ||||
| } | ||||
|  | ||||
| type StcpVisitor struct { | ||||
| 	*BaseVisitor | ||||
|  | ||||
| 	cfg *config.StcpVisitorConf | ||||
| } | ||||
|  | ||||
| func (sv *StcpVisitor) Run() (err error) { | ||||
| 	sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	go sv.worker() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (sv *StcpVisitor) Close() { | ||||
| 	sv.l.Close() | ||||
| } | ||||
|  | ||||
| func (sv *StcpVisitor) worker() { | ||||
| 	for { | ||||
| 		conn, err := sv.l.Accept() | ||||
| 		if err != nil { | ||||
| 			sv.Warn("stcp local listener closed") | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		go sv.handleConn(conn) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) { | ||||
| 	defer userConn.Close() | ||||
|  | ||||
| 	sv.Debug("get a new stcp user connection") | ||||
| 	visitorConn, err := sv.ctl.connectServer() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer visitorConn.Close() | ||||
|  | ||||
| 	now := time.Now().Unix() | ||||
| 	newVisitorConnMsg := &msg.NewVisitorConn{ | ||||
| 		ProxyName:      sv.cfg.ServerName, | ||||
| 		SignKey:        util.GetAuthKey(sv.cfg.Sk, now), | ||||
| 		Timestamp:      now, | ||||
| 		UseEncryption:  sv.cfg.UseEncryption, | ||||
| 		UseCompression: sv.cfg.UseCompression, | ||||
| 	} | ||||
| 	err = msg.WriteMsg(visitorConn, newVisitorConnMsg) | ||||
| 	if err != nil { | ||||
| 		sv.Warn("send newVisitorConnMsg to server error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var newVisitorConnRespMsg msg.NewVisitorConnResp | ||||
| 	visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second)) | ||||
| 	err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg) | ||||
| 	if err != nil { | ||||
| 		sv.Warn("get newVisitorConnRespMsg error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	visitorConn.SetReadDeadline(time.Time{}) | ||||
|  | ||||
| 	if newVisitorConnRespMsg.Error != "" { | ||||
| 		sv.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var remote io.ReadWriteCloser | ||||
| 	remote = visitorConn | ||||
| 	if sv.cfg.UseEncryption { | ||||
| 		remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk)) | ||||
| 		if err != nil { | ||||
| 			sv.Error("create encryption stream error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if sv.cfg.UseCompression { | ||||
| 		remote = frpIo.WithCompression(remote) | ||||
| 	} | ||||
|  | ||||
| 	frpIo.Join(userConn, remote) | ||||
| } | ||||
|  | ||||
| type XtcpVisitor struct { | ||||
| 	*BaseVisitor | ||||
|  | ||||
| 	cfg *config.XtcpVisitorConf | ||||
| } | ||||
|  | ||||
| func (sv *XtcpVisitor) Run() (err error) { | ||||
| 	sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	go sv.worker() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (sv *XtcpVisitor) Close() { | ||||
| 	sv.l.Close() | ||||
| } | ||||
|  | ||||
| func (sv *XtcpVisitor) worker() { | ||||
| 	for { | ||||
| 		conn, err := sv.l.Accept() | ||||
| 		if err != nil { | ||||
| 			sv.Warn("xtcp local listener closed") | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		go sv.handleConn(conn) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) { | ||||
| 	defer userConn.Close() | ||||
|  | ||||
| 	sv.Debug("get a new xtcp user connection") | ||||
| 	if g.GlbClientCfg.ServerUdpPort == 0 { | ||||
| 		sv.Error("xtcp is not supported by server") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	raddr, err := net.ResolveUDPAddr("udp", | ||||
| 		fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerUdpPort)) | ||||
| 	if err != nil { | ||||
| 		sv.Error("resolve server UDP addr error") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	visitorConn, err := net.DialUDP("udp", nil, raddr) | ||||
| 	if err != nil { | ||||
| 		sv.Warn("dial server udp addr error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer visitorConn.Close() | ||||
|  | ||||
| 	now := time.Now().Unix() | ||||
| 	natHoleVisitorMsg := &msg.NatHoleVisitor{ | ||||
| 		ProxyName: sv.cfg.ServerName, | ||||
| 		SignKey:   util.GetAuthKey(sv.cfg.Sk, now), | ||||
| 		Timestamp: now, | ||||
| 	} | ||||
| 	err = msg.WriteMsg(visitorConn, natHoleVisitorMsg) | ||||
| 	if err != nil { | ||||
| 		sv.Warn("send natHoleVisitorMsg to server error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Wait for client address at most 10 seconds. | ||||
| 	var natHoleRespMsg msg.NatHoleResp | ||||
| 	visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second)) | ||||
| 	buf := pool.GetBuf(1024) | ||||
| 	n, err := visitorConn.Read(buf) | ||||
| 	if err != nil { | ||||
| 		sv.Warn("get natHoleRespMsg error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg) | ||||
| 	if err != nil { | ||||
| 		sv.Warn("get natHoleRespMsg error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	visitorConn.SetReadDeadline(time.Time{}) | ||||
| 	pool.PutBuf(buf) | ||||
|  | ||||
| 	if natHoleRespMsg.Error != "" { | ||||
| 		sv.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr) | ||||
|  | ||||
| 	// Close visitorConn, so we can use it's local address. | ||||
| 	visitorConn.Close() | ||||
|  | ||||
| 	// Send detect message. | ||||
| 	array := strings.Split(natHoleRespMsg.ClientAddr, ":") | ||||
| 	if len(array) <= 1 { | ||||
| 		sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr) | ||||
| 		return | ||||
| 	} | ||||
| 	laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String()) | ||||
| 	/* | ||||
| 		for i := 1000; i < 65000; i++ { | ||||
| 			sv.sendDetectMsg(array[0], int64(i), laddr, "a") | ||||
| 		} | ||||
| 	*/ | ||||
| 	port, err := strconv.ParseInt(array[1], 10, 64) | ||||
| 	if err != nil { | ||||
| 		sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr) | ||||
| 		return | ||||
| 	} | ||||
| 	sv.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid)) | ||||
| 	sv.Trace("send all detect msg done") | ||||
|  | ||||
| 	// Listen for visitorConn's address and wait for client connection. | ||||
| 	lConn, err := net.ListenUDP("udp", laddr) | ||||
| 	if err != nil { | ||||
| 		sv.Error("listen on visitorConn's local adress error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	lConn.SetReadDeadline(time.Now().Add(5 * time.Second)) | ||||
| 	sidBuf := pool.GetBuf(1024) | ||||
| 	n, _, err = lConn.ReadFromUDP(sidBuf) | ||||
| 	if err != nil { | ||||
| 		sv.Warn("get sid from client error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	lConn.SetReadDeadline(time.Time{}) | ||||
| 	if string(sidBuf[:n]) != natHoleRespMsg.Sid { | ||||
| 		sv.Warn("incorrect sid from client") | ||||
| 		return | ||||
| 	} | ||||
| 	sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n])) | ||||
| 	pool.PutBuf(sidBuf) | ||||
|  | ||||
| 	var remote io.ReadWriteCloser | ||||
| 	remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr) | ||||
| 	if err != nil { | ||||
| 		sv.Error("create kcp connection from udp connection error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if sv.cfg.UseEncryption { | ||||
| 		remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk)) | ||||
| 		if err != nil { | ||||
| 			sv.Error("create encryption stream error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if sv.cfg.UseCompression { | ||||
| 		remote = frpIo.WithCompression(remote) | ||||
| 	} | ||||
|  | ||||
| 	frpIo.Join(userConn, remote) | ||||
| 	sv.Debug("join connections closed") | ||||
| } | ||||
|  | ||||
| func (sv *XtcpVisitor) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) { | ||||
| 	daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	tConn, err := net.DialUDP("udp", laddr, daddr) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	uConn := ipv4.NewConn(tConn) | ||||
| 	uConn.SetTTL(3) | ||||
|  | ||||
| 	tConn.Write(content) | ||||
| 	tConn.Close() | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										123
									
								
								client/visitor_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								client/visitor_manager.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package client | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| ) | ||||
|  | ||||
| type VisitorManager struct { | ||||
| 	ctl *Control | ||||
|  | ||||
| 	cfgs     map[string]config.VisitorConf | ||||
| 	visitors map[string]Visitor | ||||
|  | ||||
| 	checkInterval time.Duration | ||||
|  | ||||
| 	mu sync.Mutex | ||||
| } | ||||
|  | ||||
| func NewVisitorManager(ctl *Control) *VisitorManager { | ||||
| 	return &VisitorManager{ | ||||
| 		ctl:           ctl, | ||||
| 		cfgs:          make(map[string]config.VisitorConf), | ||||
| 		visitors:      make(map[string]Visitor), | ||||
| 		checkInterval: 10 * time.Second, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (vm *VisitorManager) Run() { | ||||
| 	for { | ||||
| 		time.Sleep(vm.checkInterval) | ||||
| 		vm.mu.Lock() | ||||
| 		for _, cfg := range vm.cfgs { | ||||
| 			name := cfg.GetBaseInfo().ProxyName | ||||
| 			if _, exist := vm.visitors[name]; !exist { | ||||
| 				log.Info("try to start visitor [%s]", name) | ||||
| 				vm.startVisitor(cfg) | ||||
| 			} | ||||
| 		} | ||||
| 		vm.mu.Unlock() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Hold lock before calling this function. | ||||
| func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) { | ||||
| 	name := cfg.GetBaseInfo().ProxyName | ||||
| 	visitor := NewVisitor(vm.ctl, cfg) | ||||
| 	err = visitor.Run() | ||||
| 	if err != nil { | ||||
| 		visitor.Warn("start error: %v", err) | ||||
| 	} else { | ||||
| 		vm.visitors[name] = visitor | ||||
| 		visitor.Info("start visitor success") | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) { | ||||
| 	vm.mu.Lock() | ||||
| 	defer vm.mu.Unlock() | ||||
|  | ||||
| 	delNames := make([]string, 0) | ||||
| 	for name, oldCfg := range vm.cfgs { | ||||
| 		del := false | ||||
| 		cfg, ok := cfgs[name] | ||||
| 		if !ok { | ||||
| 			del = true | ||||
| 		} else { | ||||
| 			if !oldCfg.Compare(cfg) { | ||||
| 				del = true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if del { | ||||
| 			delNames = append(delNames, name) | ||||
| 			delete(vm.cfgs, name) | ||||
| 			if visitor, ok := vm.visitors[name]; ok { | ||||
| 				visitor.Close() | ||||
| 			} | ||||
| 			delete(vm.visitors, name) | ||||
| 		} | ||||
| 	} | ||||
| 	if len(delNames) > 0 { | ||||
| 		log.Info("visitor removed: %v", delNames) | ||||
| 	} | ||||
|  | ||||
| 	addNames := make([]string, 0) | ||||
| 	for name, cfg := range cfgs { | ||||
| 		if _, ok := vm.cfgs[name]; !ok { | ||||
| 			vm.cfgs[name] = cfg | ||||
| 			addNames = append(addNames, name) | ||||
| 			vm.startVisitor(cfg) | ||||
| 		} | ||||
| 	} | ||||
| 	if len(addNames) > 0 { | ||||
| 		log.Info("visitor added: %v", addNames) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (vm *VisitorManager) Close() { | ||||
| 	vm.mu.Lock() | ||||
| 	defer vm.mu.Unlock() | ||||
| 	for _, v := range vm.visitors { | ||||
| 		v.Close() | ||||
| 	} | ||||
| } | ||||
| @@ -12,36 +12,17 @@ | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package pcrypto | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 	_ "github.com/fatedier/frp/assets/frpc/statik" | ||||
| 	"github.com/fatedier/frp/cmd/frpc/sub" | ||||
| 
 | ||||
| 	"github.com/fatedier/golib/crypto" | ||||
| ) | ||||
| 
 | ||||
| func TestEncrypt(t *testing.T) { | ||||
| 	pp := new(Pcrypto) | ||||
| 	pp.Init([]byte("Hana")) | ||||
| 	res, err := pp.Encrypt([]byte("Just One Test!")) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| func main() { | ||||
| 	crypto.DefaultSalt = "frp" | ||||
| 
 | ||||
| 	fmt.Printf("[%x]\n", res) | ||||
| } | ||||
| 
 | ||||
| func TestDecrypt(t *testing.T) { | ||||
| 	pp := new(Pcrypto) | ||||
| 	pp.Init([]byte("Hana")) | ||||
| 	res, err := pp.Encrypt([]byte("Just One Test!")) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	res, err = pp.Decrypt(res) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Printf("[%s]\n", string(res)) | ||||
| 	sub.Execute() | ||||
| } | ||||
							
								
								
									
										96
									
								
								cmd/frpc/sub/http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								cmd/frpc/sub/http.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package sub | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/consts" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	httpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") | ||||
| 	httpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days") | ||||
|  | ||||
| 	httpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") | ||||
| 	httpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&locations, "locations", "", "", "locations") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&httpUser, "http_user", "", "", "http auth user") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&httpPwd, "http_pwd", "", "", "http auth password") | ||||
| 	httpCmd.PersistentFlags().StringVarP(&hostHeaderRewrite, "host_header_rewrite", "", "", "host header rewrite") | ||||
| 	httpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") | ||||
| 	httpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") | ||||
|  | ||||
| 	rootCmd.AddCommand(httpCmd) | ||||
| } | ||||
|  | ||||
| var httpCmd = &cobra.Command{ | ||||
| 	Use:   "http", | ||||
| 	Short: "Run frpc with a single http proxy", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		err := parseClientCommonCfg(CfgFileTypeCmd, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		cfg := &config.HttpProxyConf{} | ||||
| 		var prefix string | ||||
| 		if user != "" { | ||||
| 			prefix = user + "." | ||||
| 		} | ||||
| 		cfg.ProxyName = prefix + proxyName | ||||
| 		cfg.ProxyType = consts.HttpProxy | ||||
| 		cfg.LocalIp = localIp | ||||
| 		cfg.LocalPort = localPort | ||||
| 		cfg.CustomDomains = strings.Split(customDomains, ",") | ||||
| 		cfg.SubDomain = subDomain | ||||
| 		cfg.Locations = strings.Split(locations, ",") | ||||
| 		cfg.HttpUser = httpUser | ||||
| 		cfg.HttpPwd = httpPwd | ||||
| 		cfg.HostHeaderRewrite = hostHeaderRewrite | ||||
| 		cfg.UseEncryption = useEncryption | ||||
| 		cfg.UseCompression = useCompression | ||||
|  | ||||
| 		err = cfg.CheckForCli() | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		proxyConfs := map[string]config.ProxyConf{ | ||||
| 			cfg.ProxyName: cfg, | ||||
| 		} | ||||
| 		err = startService(proxyConfs, nil) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										88
									
								
								cmd/frpc/sub/https.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								cmd/frpc/sub/https.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package sub | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/consts" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	httpsCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address") | ||||
| 	httpsCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user") | ||||
| 	httpsCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket") | ||||
| 	httpsCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token") | ||||
| 	httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") | ||||
| 	httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") | ||||
| 	httpsCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days") | ||||
|  | ||||
| 	httpsCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") | ||||
| 	httpsCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") | ||||
| 	httpsCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") | ||||
| 	httpsCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain") | ||||
| 	httpsCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain") | ||||
| 	httpsCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") | ||||
| 	httpsCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") | ||||
|  | ||||
| 	rootCmd.AddCommand(httpsCmd) | ||||
| } | ||||
|  | ||||
| var httpsCmd = &cobra.Command{ | ||||
| 	Use:   "https", | ||||
| 	Short: "Run frpc with a single https proxy", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		err := parseClientCommonCfg(CfgFileTypeCmd, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		cfg := &config.HttpsProxyConf{} | ||||
| 		var prefix string | ||||
| 		if user != "" { | ||||
| 			prefix = user + "." | ||||
| 		} | ||||
| 		cfg.ProxyName = prefix + proxyName | ||||
| 		cfg.ProxyType = consts.HttpsProxy | ||||
| 		cfg.LocalIp = localIp | ||||
| 		cfg.LocalPort = localPort | ||||
| 		cfg.CustomDomains = strings.Split(customDomains, ",") | ||||
| 		cfg.SubDomain = subDomain | ||||
| 		cfg.UseEncryption = useEncryption | ||||
| 		cfg.UseCompression = useCompression | ||||
|  | ||||
| 		err = cfg.CheckForCli() | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		proxyConfs := map[string]config.ProxyConf{ | ||||
| 			cfg.ProxyName: cfg, | ||||
| 		} | ||||
| 		err = startService(proxyConfs, nil) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										92
									
								
								cmd/frpc/sub/reload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								cmd/frpc/sub/reload.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package sub | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	rootCmd.AddCommand(reloadCmd) | ||||
| } | ||||
|  | ||||
| var reloadCmd = &cobra.Command{ | ||||
| 	Use:   "reload", | ||||
| 	Short: "Hot-Reload frpc configuration", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		iniContent, err := config.GetRenderedConfFromFile(cfgFile) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		err = parseClientCommonCfg(CfgFileTypeIni, iniContent) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		err = reload() | ||||
| 		if err != nil { | ||||
| 			fmt.Printf("frpc reload error: %v\n", err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		fmt.Printf("reload success\n") | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func reload() error { | ||||
| 	if g.GlbClientCfg.AdminPort == 0 { | ||||
| 		return fmt.Errorf("admin_port shoud be set if you want to use reload feature") | ||||
| 	} | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", "http://"+ | ||||
| 		g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/reload", nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+ | ||||
| 		g.GlbClientCfg.AdminPwd)) | ||||
|  | ||||
| 	req.Header.Add("Authorization", authStr) | ||||
| 	resp, err := http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} else { | ||||
| 		if resp.StatusCode == 200 { | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		defer resp.Body.Close() | ||||
| 		body, err := ioutil.ReadAll(resp.Body) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body))) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										224
									
								
								cmd/frpc/sub/root.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								cmd/frpc/sub/root.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,224 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package sub | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	"github.com/fatedier/frp/client" | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| 	"github.com/fatedier/frp/utils/version" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	CfgFileTypeIni = iota | ||||
| 	CfgFileTypeCmd | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	cfgFile     string | ||||
| 	showVersion bool | ||||
|  | ||||
| 	serverAddr string | ||||
| 	user       string | ||||
| 	protocol   string | ||||
| 	token      string | ||||
| 	logLevel   string | ||||
| 	logFile    string | ||||
| 	logMaxDays int | ||||
|  | ||||
| 	proxyName         string | ||||
| 	localIp           string | ||||
| 	localPort         int | ||||
| 	remotePort        int | ||||
| 	useEncryption     bool | ||||
| 	useCompression    bool | ||||
| 	customDomains     string | ||||
| 	subDomain         string | ||||
| 	httpUser          string | ||||
| 	httpPwd           string | ||||
| 	locations         string | ||||
| 	hostHeaderRewrite string | ||||
| 	role              string | ||||
| 	sk                string | ||||
| 	serverName        string | ||||
| 	bindAddr          string | ||||
| 	bindPort          int | ||||
|  | ||||
| 	kcpDoneCh chan struct{} | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "./frpc.ini", "config file of frpc") | ||||
| 	rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc") | ||||
|  | ||||
| 	kcpDoneCh = make(chan struct{}) | ||||
| } | ||||
|  | ||||
| var rootCmd = &cobra.Command{ | ||||
| 	Use:   "frpc", | ||||
| 	Short: "frpc is the client of frp (https://github.com/fatedier/frp)", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		if showVersion { | ||||
| 			fmt.Println(version.Full()) | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		// Do not show command usage here. | ||||
| 		err := runClient(cfgFile) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func Execute() { | ||||
| 	if err := rootCmd.Execute(); err != nil { | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func handleSignal(svr *client.Service) { | ||||
| 	ch := make(chan os.Signal) | ||||
| 	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) | ||||
| 	<-ch | ||||
| 	svr.Close() | ||||
| 	time.Sleep(250 * time.Millisecond) | ||||
| 	close(kcpDoneCh) | ||||
| } | ||||
|  | ||||
| func parseClientCommonCfg(fileType int, content string) (err error) { | ||||
| 	if fileType == CfgFileTypeIni { | ||||
| 		err = parseClientCommonCfgFromIni(content) | ||||
| 	} else if fileType == CfgFileTypeCmd { | ||||
| 		err = parseClientCommonCfgFromCmd() | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = g.GlbClientCfg.ClientCommonConf.Check() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func parseClientCommonCfgFromIni(content string) (err error) { | ||||
| 	cfg, err := config.UnmarshalClientConfFromIni(&g.GlbClientCfg.ClientCommonConf, content) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	g.GlbClientCfg.ClientCommonConf = *cfg | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func parseClientCommonCfgFromCmd() (err error) { | ||||
| 	strs := strings.Split(serverAddr, ":") | ||||
| 	if len(strs) < 2 { | ||||
| 		err = fmt.Errorf("invalid server_addr") | ||||
| 		return | ||||
| 	} | ||||
| 	if strs[0] != "" { | ||||
| 		g.GlbClientCfg.ServerAddr = strs[0] | ||||
| 	} | ||||
| 	g.GlbClientCfg.ServerPort, err = strconv.Atoi(strs[1]) | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("invalid server_addr") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	g.GlbClientCfg.User = user | ||||
| 	g.GlbClientCfg.Protocol = protocol | ||||
| 	g.GlbClientCfg.Token = token | ||||
| 	g.GlbClientCfg.LogLevel = logLevel | ||||
| 	g.GlbClientCfg.LogFile = logFile | ||||
| 	g.GlbClientCfg.LogMaxDays = int64(logMaxDays) | ||||
| 	if logFile == "console" { | ||||
| 		g.GlbClientCfg.LogWay = "console" | ||||
| 	} else { | ||||
| 		g.GlbClientCfg.LogWay = "file" | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func runClient(cfgFilePath string) (err error) { | ||||
| 	var content string | ||||
| 	content, err = config.GetRenderedConfFromFile(cfgFilePath) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	g.GlbClientCfg.CfgFile = cfgFilePath | ||||
|  | ||||
| 	err = parseClientCommonCfg(CfgFileTypeIni, content) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, g.GlbClientCfg.Start) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = startService(pxyCfgs, visitorCfgs) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (err error) { | ||||
| 	log.InitLog(g.GlbClientCfg.LogWay, g.GlbClientCfg.LogFile, g.GlbClientCfg.LogLevel, g.GlbClientCfg.LogMaxDays) | ||||
| 	if g.GlbClientCfg.DnsServer != "" { | ||||
| 		s := g.GlbClientCfg.DnsServer | ||||
| 		if !strings.Contains(s, ":") { | ||||
| 			s += ":53" | ||||
| 		} | ||||
| 		// Change default dns server for frpc | ||||
| 		net.DefaultResolver = &net.Resolver{ | ||||
| 			PreferGo: true, | ||||
| 			Dial: func(ctx context.Context, network, address string) (net.Conn, error) { | ||||
| 				return net.Dial("udp", s) | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
| 	svr, errRet := client.NewService(pxyCfgs, visitorCfgs) | ||||
| 	if errRet != nil { | ||||
| 		err = errRet | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Capture the exit signal if we use kcp. | ||||
| 	if g.GlbClientCfg.Protocol == "kcp" { | ||||
| 		go handleSignal(svr) | ||||
| 	} | ||||
|  | ||||
| 	err = svr.Run() | ||||
| 	if g.GlbClientCfg.Protocol == "kcp" { | ||||
| 		<-kcpDoneCh | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										153
									
								
								cmd/frpc/sub/status.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								cmd/frpc/sub/status.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package sub | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/rodaine/table" | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	"github.com/fatedier/frp/client" | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	rootCmd.AddCommand(statusCmd) | ||||
| } | ||||
|  | ||||
| var statusCmd = &cobra.Command{ | ||||
| 	Use:   "status", | ||||
| 	Short: "Overview of all proxies status", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		iniContent, err := config.GetRenderedConfFromFile(cfgFile) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		err = parseClientCommonCfg(CfgFileTypeIni, iniContent) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		err = status() | ||||
| 		if err != nil { | ||||
| 			fmt.Printf("frpc get status error: %v\n", err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func status() error { | ||||
| 	if g.GlbClientCfg.AdminPort == 0 { | ||||
| 		return fmt.Errorf("admin_port shoud be set if you want to get proxy status") | ||||
| 	} | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", "http://"+ | ||||
| 		g.GlbClientCfg.AdminAddr+":"+fmt.Sprintf("%d", g.GlbClientCfg.AdminPort)+"/api/status", nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(g.GlbClientCfg.AdminUser+":"+ | ||||
| 		g.GlbClientCfg.AdminPwd)) | ||||
|  | ||||
| 	req.Header.Add("Authorization", authStr) | ||||
| 	resp, err := http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} else { | ||||
| 		if resp.StatusCode != 200 { | ||||
| 			return fmt.Errorf("admin api status code [%d]", resp.StatusCode) | ||||
| 		} | ||||
| 		defer resp.Body.Close() | ||||
| 		body, err := ioutil.ReadAll(resp.Body) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		res := &client.StatusResp{} | ||||
| 		err = json.Unmarshal(body, &res) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body))) | ||||
| 		} | ||||
|  | ||||
| 		fmt.Println("Proxy Status...") | ||||
| 		if len(res.Tcp) > 0 { | ||||
| 			fmt.Printf("TCP") | ||||
| 			tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") | ||||
| 			for _, ps := range res.Tcp { | ||||
| 				tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) | ||||
| 			} | ||||
| 			tbl.Print() | ||||
| 			fmt.Println("") | ||||
| 		} | ||||
| 		if len(res.Udp) > 0 { | ||||
| 			fmt.Printf("UDP") | ||||
| 			tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") | ||||
| 			for _, ps := range res.Udp { | ||||
| 				tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) | ||||
| 			} | ||||
| 			tbl.Print() | ||||
| 			fmt.Println("") | ||||
| 		} | ||||
| 		if len(res.Http) > 0 { | ||||
| 			fmt.Printf("HTTP") | ||||
| 			tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") | ||||
| 			for _, ps := range res.Http { | ||||
| 				tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) | ||||
| 			} | ||||
| 			tbl.Print() | ||||
| 			fmt.Println("") | ||||
| 		} | ||||
| 		if len(res.Https) > 0 { | ||||
| 			fmt.Printf("HTTPS") | ||||
| 			tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") | ||||
| 			for _, ps := range res.Https { | ||||
| 				tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) | ||||
| 			} | ||||
| 			tbl.Print() | ||||
| 			fmt.Println("") | ||||
| 		} | ||||
| 		if len(res.Stcp) > 0 { | ||||
| 			fmt.Printf("STCP") | ||||
| 			tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") | ||||
| 			for _, ps := range res.Stcp { | ||||
| 				tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) | ||||
| 			} | ||||
| 			tbl.Print() | ||||
| 			fmt.Println("") | ||||
| 		} | ||||
| 		if len(res.Xtcp) > 0 { | ||||
| 			fmt.Printf("XTCP") | ||||
| 			tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error") | ||||
| 			for _, ps := range res.Xtcp { | ||||
| 				tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err) | ||||
| 			} | ||||
| 			tbl.Print() | ||||
| 			fmt.Println("") | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										113
									
								
								cmd/frpc/sub/stcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								cmd/frpc/sub/stcp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package sub | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/consts" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address") | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user") | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket") | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token") | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") | ||||
| 	stcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days") | ||||
|  | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role") | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key") | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name") | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") | ||||
| 	stcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") | ||||
| 	stcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr") | ||||
| 	stcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port") | ||||
| 	stcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") | ||||
| 	stcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") | ||||
|  | ||||
| 	rootCmd.AddCommand(stcpCmd) | ||||
| } | ||||
|  | ||||
| var stcpCmd = &cobra.Command{ | ||||
| 	Use:   "stcp", | ||||
| 	Short: "Run frpc with a single stcp proxy", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		err := parseClientCommonCfg(CfgFileTypeCmd, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		proxyConfs := make(map[string]config.ProxyConf) | ||||
| 		visitorConfs := make(map[string]config.VisitorConf) | ||||
|  | ||||
| 		var prefix string | ||||
| 		if user != "" { | ||||
| 			prefix = user + "." | ||||
| 		} | ||||
|  | ||||
| 		if role == "server" { | ||||
| 			cfg := &config.StcpProxyConf{} | ||||
| 			cfg.ProxyName = prefix + proxyName | ||||
| 			cfg.ProxyType = consts.StcpProxy | ||||
| 			cfg.UseEncryption = useEncryption | ||||
| 			cfg.UseCompression = useCompression | ||||
| 			cfg.Role = role | ||||
| 			cfg.Sk = sk | ||||
| 			cfg.LocalIp = localIp | ||||
| 			cfg.LocalPort = localPort | ||||
| 			err = cfg.CheckForCli() | ||||
| 			if err != nil { | ||||
| 				fmt.Println(err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			proxyConfs[cfg.ProxyName] = cfg | ||||
| 		} else if role == "visitor" { | ||||
| 			cfg := &config.StcpVisitorConf{} | ||||
| 			cfg.ProxyName = prefix + proxyName | ||||
| 			cfg.ProxyType = consts.StcpProxy | ||||
| 			cfg.UseEncryption = useEncryption | ||||
| 			cfg.UseCompression = useCompression | ||||
| 			cfg.Role = role | ||||
| 			cfg.Sk = sk | ||||
| 			cfg.ServerName = serverName | ||||
| 			cfg.BindAddr = bindAddr | ||||
| 			cfg.BindPort = bindPort | ||||
| 			err = cfg.Check() | ||||
| 			if err != nil { | ||||
| 				fmt.Println(err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			visitorConfs[cfg.ProxyName] = cfg | ||||
| 		} else { | ||||
| 			fmt.Println("invalid role") | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		err = startService(proxyConfs, visitorConfs) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										85
									
								
								cmd/frpc/sub/tcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								cmd/frpc/sub/tcp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package sub | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/consts" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	tcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address") | ||||
| 	tcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user") | ||||
| 	tcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp") | ||||
| 	tcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token") | ||||
| 	tcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") | ||||
| 	tcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") | ||||
| 	tcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days") | ||||
|  | ||||
| 	tcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") | ||||
| 	tcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") | ||||
| 	tcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") | ||||
| 	tcpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port") | ||||
| 	tcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") | ||||
| 	tcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") | ||||
|  | ||||
| 	rootCmd.AddCommand(tcpCmd) | ||||
| } | ||||
|  | ||||
| var tcpCmd = &cobra.Command{ | ||||
| 	Use:   "tcp", | ||||
| 	Short: "Run frpc with a single tcp proxy", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		err := parseClientCommonCfg(CfgFileTypeCmd, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		cfg := &config.TcpProxyConf{} | ||||
| 		var prefix string | ||||
| 		if user != "" { | ||||
| 			prefix = user + "." | ||||
| 		} | ||||
| 		cfg.ProxyName = prefix + proxyName | ||||
| 		cfg.ProxyType = consts.TcpProxy | ||||
| 		cfg.LocalIp = localIp | ||||
| 		cfg.LocalPort = localPort | ||||
| 		cfg.RemotePort = remotePort | ||||
| 		cfg.UseEncryption = useEncryption | ||||
| 		cfg.UseCompression = useCompression | ||||
|  | ||||
| 		err = cfg.CheckForCli() | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		proxyConfs := map[string]config.ProxyConf{ | ||||
| 			cfg.ProxyName: cfg, | ||||
| 		} | ||||
| 		err = startService(proxyConfs, nil) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										85
									
								
								cmd/frpc/sub/udp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								cmd/frpc/sub/udp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package sub | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/consts" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	udpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address") | ||||
| 	udpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user") | ||||
| 	udpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket") | ||||
| 	udpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token") | ||||
| 	udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") | ||||
| 	udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") | ||||
| 	udpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days") | ||||
|  | ||||
| 	udpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") | ||||
| 	udpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") | ||||
| 	udpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") | ||||
| 	udpCmd.PersistentFlags().IntVarP(&remotePort, "remote_port", "r", 0, "remote port") | ||||
| 	udpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") | ||||
| 	udpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") | ||||
|  | ||||
| 	rootCmd.AddCommand(udpCmd) | ||||
| } | ||||
|  | ||||
| var udpCmd = &cobra.Command{ | ||||
| 	Use:   "udp", | ||||
| 	Short: "Run frpc with a single udp proxy", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		err := parseClientCommonCfg(CfgFileTypeCmd, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		cfg := &config.UdpProxyConf{} | ||||
| 		var prefix string | ||||
| 		if user != "" { | ||||
| 			prefix = user + "." | ||||
| 		} | ||||
| 		cfg.ProxyName = prefix + proxyName | ||||
| 		cfg.ProxyType = consts.UdpProxy | ||||
| 		cfg.LocalIp = localIp | ||||
| 		cfg.LocalPort = localPort | ||||
| 		cfg.RemotePort = remotePort | ||||
| 		cfg.UseEncryption = useEncryption | ||||
| 		cfg.UseCompression = useCompression | ||||
|  | ||||
| 		err = cfg.CheckForCli() | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		proxyConfs := map[string]config.ProxyConf{ | ||||
| 			cfg.ProxyName: cfg, | ||||
| 		} | ||||
| 		err = startService(proxyConfs, nil) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										113
									
								
								cmd/frpc/sub/xtcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								cmd/frpc/sub/xtcp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package sub | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/consts" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address") | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user") | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket") | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token") | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") | ||||
| 	xtcpCmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days") | ||||
|  | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role") | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key") | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name") | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&localIp, "local_ip", "i", "127.0.0.1", "local ip") | ||||
| 	xtcpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") | ||||
| 	xtcpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr") | ||||
| 	xtcpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port") | ||||
| 	xtcpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") | ||||
| 	xtcpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") | ||||
|  | ||||
| 	rootCmd.AddCommand(xtcpCmd) | ||||
| } | ||||
|  | ||||
| var xtcpCmd = &cobra.Command{ | ||||
| 	Use:   "xtcp", | ||||
| 	Short: "Run frpc with a single xtcp proxy", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		err := parseClientCommonCfg(CfgFileTypeCmd, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		proxyConfs := make(map[string]config.ProxyConf) | ||||
| 		visitorConfs := make(map[string]config.VisitorConf) | ||||
|  | ||||
| 		var prefix string | ||||
| 		if user != "" { | ||||
| 			prefix = user + "." | ||||
| 		} | ||||
|  | ||||
| 		if role == "server" { | ||||
| 			cfg := &config.XtcpProxyConf{} | ||||
| 			cfg.ProxyName = prefix + proxyName | ||||
| 			cfg.ProxyType = consts.StcpProxy | ||||
| 			cfg.UseEncryption = useEncryption | ||||
| 			cfg.UseCompression = useCompression | ||||
| 			cfg.Role = role | ||||
| 			cfg.Sk = sk | ||||
| 			cfg.LocalIp = localIp | ||||
| 			cfg.LocalPort = localPort | ||||
| 			err = cfg.CheckForCli() | ||||
| 			if err != nil { | ||||
| 				fmt.Println(err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			proxyConfs[cfg.ProxyName] = cfg | ||||
| 		} else if role == "visitor" { | ||||
| 			cfg := &config.XtcpVisitorConf{} | ||||
| 			cfg.ProxyName = prefix + proxyName | ||||
| 			cfg.ProxyType = consts.StcpProxy | ||||
| 			cfg.UseEncryption = useEncryption | ||||
| 			cfg.UseCompression = useCompression | ||||
| 			cfg.Role = role | ||||
| 			cfg.Sk = sk | ||||
| 			cfg.ServerName = serverName | ||||
| 			cfg.BindAddr = bindAddr | ||||
| 			cfg.BindPort = bindPort | ||||
| 			err = cfg.Check() | ||||
| 			if err != nil { | ||||
| 				fmt.Println(err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			visitorConfs[cfg.ProxyName] = cfg | ||||
| 		} else { | ||||
| 			fmt.Println("invalid role") | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		err = startService(proxyConfs, visitorConfs) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										27
									
								
								cmd/frps/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								cmd/frps/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"github.com/fatedier/golib/crypto" | ||||
|  | ||||
| 	_ "github.com/fatedier/frp/assets/frps/statik" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	crypto.DefaultSalt = "frp" | ||||
|  | ||||
| 	Execute() | ||||
| } | ||||
							
								
								
									
										208
									
								
								cmd/frps/root.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								cmd/frps/root.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,208 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/server" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| 	"github.com/fatedier/frp/utils/util" | ||||
| 	"github.com/fatedier/frp/utils/version" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	CfgFileTypeIni = iota | ||||
| 	CfgFileTypeCmd | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	cfgFile     string | ||||
| 	showVersion bool | ||||
|  | ||||
| 	bindAddr          string | ||||
| 	bindPort          int | ||||
| 	bindUdpPort       int | ||||
| 	kcpBindPort       int | ||||
| 	proxyBindAddr     string | ||||
| 	vhostHttpPort     int | ||||
| 	vhostHttpsPort    int | ||||
| 	vhostHttpTimeout  int64 | ||||
| 	dashboardAddr     string | ||||
| 	dashboardPort     int | ||||
| 	dashboardUser     string | ||||
| 	dashboardPwd      string | ||||
| 	assetsDir         string | ||||
| 	logFile           string | ||||
| 	logLevel          string | ||||
| 	logMaxDays        int64 | ||||
| 	token             string | ||||
| 	subDomainHost     string | ||||
| 	tcpMux            bool | ||||
| 	allowPorts        string | ||||
| 	maxPoolCount      int64 | ||||
| 	maxPortsPerClient int64 | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "", "config file of frps") | ||||
| 	rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc") | ||||
|  | ||||
| 	rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address") | ||||
| 	rootCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "p", 7000, "bind port") | ||||
| 	rootCmd.PersistentFlags().IntVarP(&bindUdpPort, "bind_udp_port", "", 0, "bind udp port") | ||||
| 	rootCmd.PersistentFlags().IntVarP(&kcpBindPort, "kcp_bind_port", "", 0, "kcp bind udp port") | ||||
| 	rootCmd.PersistentFlags().StringVarP(&proxyBindAddr, "proxy_bind_addr", "", "0.0.0.0", "proxy bind address") | ||||
| 	rootCmd.PersistentFlags().IntVarP(&vhostHttpPort, "vhost_http_port", "", 0, "vhost http port") | ||||
| 	rootCmd.PersistentFlags().IntVarP(&vhostHttpsPort, "vhost_https_port", "", 0, "vhost https port") | ||||
| 	rootCmd.PersistentFlags().Int64VarP(&vhostHttpTimeout, "vhost_http_timeout", "", 60, "vhost http response header timeout") | ||||
| 	rootCmd.PersistentFlags().StringVarP(&dashboardAddr, "dashboard_addr", "", "0.0.0.0", "dasboard address") | ||||
| 	rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port") | ||||
| 	rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user") | ||||
| 	rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password") | ||||
| 	rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file") | ||||
| 	rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") | ||||
| 	rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log_max_days") | ||||
| 	rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token") | ||||
| 	rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host") | ||||
| 	rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports") | ||||
| 	rootCmd.PersistentFlags().Int64VarP(&maxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client") | ||||
| } | ||||
|  | ||||
| var rootCmd = &cobra.Command{ | ||||
| 	Use:   "frps", | ||||
| 	Short: "frps is the server of frp (https://github.com/fatedier/frp)", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		if showVersion { | ||||
| 			fmt.Println(version.Full()) | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		var err error | ||||
| 		if cfgFile != "" { | ||||
| 			var content string | ||||
| 			content, err = config.GetRenderedConfFromFile(cfgFile) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			g.GlbServerCfg.CfgFile = cfgFile | ||||
| 			err = parseServerCommonCfg(CfgFileTypeIni, content) | ||||
| 		} else { | ||||
| 			err = parseServerCommonCfg(CfgFileTypeCmd, "") | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		err = runServer() | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func Execute() { | ||||
| 	if err := rootCmd.Execute(); err != nil { | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func parseServerCommonCfg(fileType int, content string) (err error) { | ||||
| 	if fileType == CfgFileTypeIni { | ||||
| 		err = parseServerCommonCfgFromIni(content) | ||||
| 	} else if fileType == CfgFileTypeCmd { | ||||
| 		err = parseServerCommonCfgFromCmd() | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = g.GlbServerCfg.ServerCommonConf.Check() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	config.InitServerCfg(&g.GlbServerCfg.ServerCommonConf) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func parseServerCommonCfgFromIni(content string) (err error) { | ||||
| 	cfg, err := config.UnmarshalServerConfFromIni(&g.GlbServerCfg.ServerCommonConf, content) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	g.GlbServerCfg.ServerCommonConf = *cfg | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func parseServerCommonCfgFromCmd() (err error) { | ||||
| 	g.GlbServerCfg.BindAddr = bindAddr | ||||
| 	g.GlbServerCfg.BindPort = bindPort | ||||
| 	g.GlbServerCfg.BindUdpPort = bindUdpPort | ||||
| 	g.GlbServerCfg.KcpBindPort = kcpBindPort | ||||
| 	g.GlbServerCfg.ProxyBindAddr = proxyBindAddr | ||||
| 	g.GlbServerCfg.VhostHttpPort = vhostHttpPort | ||||
| 	g.GlbServerCfg.VhostHttpsPort = vhostHttpsPort | ||||
| 	g.GlbServerCfg.VhostHttpTimeout = vhostHttpTimeout | ||||
| 	g.GlbServerCfg.DashboardAddr = dashboardAddr | ||||
| 	g.GlbServerCfg.DashboardPort = dashboardPort | ||||
| 	g.GlbServerCfg.DashboardUser = dashboardUser | ||||
| 	g.GlbServerCfg.DashboardPwd = dashboardPwd | ||||
| 	g.GlbServerCfg.LogFile = logFile | ||||
| 	g.GlbServerCfg.LogLevel = logLevel | ||||
| 	g.GlbServerCfg.LogMaxDays = logMaxDays | ||||
| 	g.GlbServerCfg.Token = token | ||||
| 	g.GlbServerCfg.SubDomainHost = subDomainHost | ||||
| 	if len(allowPorts) > 0 { | ||||
| 		// e.g. 1000-2000,2001,2002,3000-4000 | ||||
| 		ports, errRet := util.ParseRangeNumbers(allowPorts) | ||||
| 		if errRet != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		for _, port := range ports { | ||||
| 			g.GlbServerCfg.AllowPorts[int(port)] = struct{}{} | ||||
| 		} | ||||
| 	} | ||||
| 	g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient | ||||
|  | ||||
| 	if logFile == "console" { | ||||
| 		g.GlbClientCfg.LogWay = "console" | ||||
| 	} else { | ||||
| 		g.GlbClientCfg.LogWay = "file" | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func runServer() (err error) { | ||||
| 	log.InitLog(g.GlbServerCfg.LogWay, g.GlbServerCfg.LogFile, g.GlbServerCfg.LogLevel, | ||||
| 		g.GlbServerCfg.LogMaxDays) | ||||
| 	svr, err := server.NewService() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	log.Info("Start frps success") | ||||
| 	server.ServerService = svr | ||||
| 	svr.Run() | ||||
| 	return | ||||
| } | ||||
| @@ -1,17 +1,9 @@ | ||||
| # [common] is integral section | ||||
| [common] | ||||
| server_addr = 0.0.0.0 | ||||
| server_addr = 127.0.0.1 | ||||
| server_port = 7000 | ||||
| # console or real logFile path like ./frpc.log | ||||
| log_file = console | ||||
| # debug, info, warn, error | ||||
| log_level = debug | ||||
| # for authentication | ||||
| auth_token = 123 | ||||
|  | ||||
| # test1 is the proxy name same as server's configuration | ||||
| [test1] | ||||
| [ssh] | ||||
| type = tcp | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 22 | ||||
| # true or false, if true, messages between frps and frpc will be encrypted, default is false | ||||
| use_encryption = true | ||||
| remote_port = 6000 | ||||
|   | ||||
							
								
								
									
										228
									
								
								conf/frpc_full.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								conf/frpc_full.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,228 @@ | ||||
| # [common] is integral section | ||||
| [common] | ||||
| # A literal address or host name for IPv6 must be enclosed | ||||
| # in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80" | ||||
| server_addr = 0.0.0.0 | ||||
| server_port = 7000 | ||||
|  | ||||
| # if you want to connect frps by http proxy or socks5 proxy, you can set http_proxy here or in global environment variables | ||||
| # it only works when protocol is tcp | ||||
| # http_proxy = http://user:passwd@192.168.1.128:8080 | ||||
| # http_proxy = socks5://user:passwd@192.168.1.128:1080 | ||||
|  | ||||
| # console or real logFile path like ./frpc.log | ||||
| log_file = ./frpc.log | ||||
|  | ||||
| # trace, debug, info, warn, error | ||||
| log_level = info | ||||
|  | ||||
| log_max_days = 3 | ||||
|  | ||||
| # for authentication | ||||
| token = 12345678 | ||||
|  | ||||
| # set admin address for control frpc's action by http api such as reload | ||||
| admin_addr = 127.0.0.1 | ||||
| admin_port = 7400 | ||||
| admin_user = admin | ||||
| admin_pwd = admin | ||||
|  | ||||
| # connections will be established in advance, default value is zero | ||||
| pool_count = 5 | ||||
|  | ||||
| # if tcp stream multiplexing is used, default is true, it must be same with frps | ||||
| tcp_mux = true | ||||
|  | ||||
| # your proxy name will be changed to {user}.{proxy} | ||||
| user = your_name | ||||
|  | ||||
| # decide if exit program when first login failed, otherwise continuous relogin to frps | ||||
| # default is true | ||||
| login_fail_exit = true | ||||
|  | ||||
| # communication protocol used to connect to server | ||||
| # now it supports tcp and kcp and websocket, default is tcp | ||||
| protocol = tcp | ||||
|  | ||||
| # specify a dns server, so frpc will use this instead of default one | ||||
| # dns_server = 8.8.8.8 | ||||
|  | ||||
| # proxy names you want to start divided by ',' | ||||
| # default is empty, means all proxies | ||||
| # start = ssh,dns | ||||
|  | ||||
| # heartbeat configure, it's not recommended to modify the default value | ||||
| # the default value of heartbeat_interval is 10 and heartbeat_timeout is 90 | ||||
| # heartbeat_interval = 30 | ||||
| # heartbeat_timeout = 90 | ||||
|  | ||||
| # 'ssh' is the unique proxy name | ||||
| # if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh' | ||||
| [ssh] | ||||
| # tcp | udp | http | https | stcp | xtcp, default is tcp | ||||
| type = tcp | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 22 | ||||
| # true or false, if true, messages between frps and frpc will be encrypted, default is false | ||||
| use_encryption = false | ||||
| # if true, message will be compressed | ||||
| use_compression = false | ||||
| # remote port listen by frps | ||||
| remote_port = 6001 | ||||
| # frps will load balancing connections for proxies in same group | ||||
| group = test_group | ||||
| # group should have same group key | ||||
| group_key = 123456 | ||||
| # enable health check for the backend service, it support 'tcp' and 'http' now | ||||
| # frpc will connect local service's port to detect it's healthy status | ||||
| health_check_type = tcp | ||||
| # health check connection timeout | ||||
| health_check_timeout_s = 3 | ||||
| # if continuous failed in 3 times, the proxy will be removed from frps | ||||
| health_check_max_failed = 3 | ||||
| # every 10 seconds will do a health check | ||||
| health_check_interval_s = 10 | ||||
|  | ||||
| [ssh_random] | ||||
| type = tcp | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 22 | ||||
| # if remote_port is 0, frps will assign a random port for you | ||||
| remote_port = 0 | ||||
|  | ||||
| # if you want to expose multiple ports, add 'range:' prefix to the section name | ||||
| # frpc will generate multiple proxies such as 'tcp_port_6010', 'tcp_port_6011' and so on. | ||||
| [range:tcp_port] | ||||
| type = tcp | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 6010-6020,6022,6024-6028 | ||||
| remote_port = 6010-6020,6022,6024-6028 | ||||
| use_encryption = false | ||||
| use_compression = false | ||||
|  | ||||
| [dns] | ||||
| type = udp | ||||
| local_ip = 114.114.114.114 | ||||
| local_port = 53 | ||||
| remote_port = 6002 | ||||
| use_encryption = false | ||||
| use_compression = false | ||||
|  | ||||
| [range:udp_port] | ||||
| type = udp | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 6010-6020 | ||||
| remote_port = 6010-6020 | ||||
| use_encryption = false | ||||
| use_compression = false | ||||
|  | ||||
| # Resolve your domain names to [server_addr] so you can use http://web01.yourdomain.com to browse web01 and http://web02.yourdomain.com to browse web02 | ||||
| [web01] | ||||
| type = http | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 80 | ||||
| use_encryption = false | ||||
| use_compression = true | ||||
| # http username and password are safety certification for http protocol | ||||
| # if not set, you can access this custom_domains without certification | ||||
| http_user = admin | ||||
| http_pwd = admin | ||||
| # if domain for frps is frps.com, then you can access [web01] proxy by URL http://test.frps.com | ||||
| subdomain = web01 | ||||
| custom_domains = web02.yourdomain.com | ||||
| # locations is only available for http type | ||||
| locations = /,/pic | ||||
| host_header_rewrite = example.com | ||||
| # params with prefix "header_" will be used to update http request headers | ||||
| header_X-From-Where = frp | ||||
| health_check_type = http | ||||
| # frpc will send a GET http request '/status' to local http service | ||||
| # http service is alive when it return 2xx http response code | ||||
| health_check_url = /status | ||||
| health_check_interval_s = 10 | ||||
| health_check_max_failed = 3 | ||||
| health_check_timeout_s = 3 | ||||
|  | ||||
| [web02] | ||||
| type = https | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 8000 | ||||
| use_encryption = false | ||||
| use_compression = false | ||||
| subdomain = web01 | ||||
| custom_domains = web02.yourdomain.com | ||||
|  | ||||
| [plugin_unix_domain_socket] | ||||
| type = tcp | ||||
| remote_port = 6003 | ||||
| # if plugin is defined, local_ip and local_port is useless | ||||
| # plugin will handle connections got from frps | ||||
| plugin = unix_domain_socket | ||||
| # params with prefix "plugin_" that plugin needed | ||||
| plugin_unix_path = /var/run/docker.sock | ||||
|  | ||||
| [plugin_http_proxy] | ||||
| type = tcp | ||||
| remote_port = 6004 | ||||
| plugin = http_proxy | ||||
| plugin_http_user = abc | ||||
| plugin_http_passwd = abc | ||||
|  | ||||
| [plugin_socks5] | ||||
| type = tcp | ||||
| remote_port = 6005 | ||||
| plugin = socks5 | ||||
| plugin_user = abc | ||||
| plugin_passwd = abc | ||||
|  | ||||
| [plugin_static_file] | ||||
| type = tcp | ||||
| remote_port = 6006 | ||||
| plugin = static_file | ||||
| plugin_local_path = /var/www/blog | ||||
| plugin_strip_prefix = static | ||||
| plugin_http_user = abc | ||||
| plugin_http_passwd = abc | ||||
|  | ||||
| [secret_tcp] | ||||
| # If the type is secret tcp, remote_port is useless | ||||
| # Who want to connect local port should deploy another frpc with stcp proxy and role is visitor | ||||
| type = stcp | ||||
| # sk used for authentication for visitors | ||||
| sk = abcdefg | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 22 | ||||
| use_encryption = false | ||||
| use_compression = false | ||||
|  | ||||
| # user of frpc should be same in both stcp server and stcp visitor | ||||
| [secret_tcp_visitor] | ||||
| # frpc role visitor -> frps -> frpc role server | ||||
| role = visitor | ||||
| type = stcp | ||||
| # the server name you want to visitor | ||||
| server_name = secret_tcp | ||||
| sk = abcdefg | ||||
| # connect this address to visitor stcp server | ||||
| bind_addr = 127.0.0.1 | ||||
| bind_port = 9000 | ||||
| use_encryption = false | ||||
| use_compression = false | ||||
|  | ||||
| [p2p_tcp] | ||||
| type = xtcp | ||||
| sk = abcdefg | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 22 | ||||
| use_encryption = false | ||||
| use_compression = false | ||||
|  | ||||
| [p2p_tcp_visitor] | ||||
| role = visitor | ||||
| type = xtcp | ||||
| server_name = p2p_tcp | ||||
| sk = abcdefg | ||||
| bind_addr = 127.0.0.1 | ||||
| bind_port = 9001 | ||||
| use_encryption = false | ||||
| use_compression = false | ||||
| @@ -1,14 +1,2 @@ | ||||
| # [common] is integral section | ||||
| [common] | ||||
| bind_addr = 0.0.0.0 | ||||
| bind_port = 7000 | ||||
| # console or real logFile path like ./frps.log | ||||
| log_file = console | ||||
| # debug, info, warn, error | ||||
| log_level = debug | ||||
|  | ||||
| # test1 is the proxy name, client will use this name and auth_token to connect to server | ||||
| [test1] | ||||
| auth_token = 123 | ||||
| bind_addr = 0.0.0.0 | ||||
| listen_port = 6000 | ||||
|   | ||||
							
								
								
									
										67
									
								
								conf/frps_full.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								conf/frps_full.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| # [common] is integral section | ||||
| [common] | ||||
| # A literal address or host name for IPv6 must be enclosed | ||||
| # in square brackets, as in "[::1]:80", "[ipv6-host]:http" or "[ipv6-host%zone]:80" | ||||
| bind_addr = 0.0.0.0 | ||||
| bind_port = 7000 | ||||
|  | ||||
| # udp port to help make udp hole to penetrate nat | ||||
| bind_udp_port = 7001 | ||||
|  | ||||
| # udp port used for kcp protocol, it can be same with 'bind_port' | ||||
| # if not set, kcp is disabled in frps | ||||
| kcp_bind_port = 7000 | ||||
|  | ||||
| # specify which address proxy will listen for, default value is same with bind_addr | ||||
| # proxy_bind_addr = 127.0.0.1 | ||||
|  | ||||
| # if you want to support virtual host, you must set the http port for listening (optional) | ||||
| # Note: http port and https port can be same with bind_port | ||||
| vhost_http_port = 80 | ||||
| vhost_https_port = 443 | ||||
|  | ||||
| # response header timeout(seconds) for vhost http server, default is 60s | ||||
| # vhost_http_timeout = 60 | ||||
|  | ||||
| # set dashboard_addr and dashboard_port to view dashboard of frps | ||||
| # dashboard_addr's default value is same with bind_addr | ||||
| # dashboard is available only if dashboard_port is set | ||||
| dashboard_addr = 0.0.0.0 | ||||
| dashboard_port = 7500 | ||||
|  | ||||
| # dashboard user and passwd for basic auth protect, if not set, both default value is admin | ||||
| dashboard_user = admin | ||||
| dashboard_pwd = admin | ||||
|  | ||||
| # dashboard assets directory(only for debug mode) | ||||
| # assets_dir = ./static | ||||
| # console or real logFile path like ./frps.log | ||||
| log_file = ./frps.log | ||||
|  | ||||
| # trace, debug, info, warn, error | ||||
| log_level = info | ||||
|  | ||||
| log_max_days = 3 | ||||
|  | ||||
| # auth token | ||||
| token = 12345678 | ||||
|  | ||||
| # heartbeat configure, it's not recommended to modify the default value | ||||
| # the default value of heartbeat_timeout is 90 | ||||
| # heartbeat_timeout = 90 | ||||
|  | ||||
| # only allow frpc to bind ports you list, if you set nothing, there won't be any limit | ||||
| allow_ports = 2000-3000,3001,3003,4000-50000 | ||||
|  | ||||
| # pool_count in each proxy will change to max_pool_count if they exceed the maximum value | ||||
| max_pool_count = 5 | ||||
|  | ||||
| # max ports can be used for each client, default value is 0 means no limit | ||||
| max_ports_per_client = 0 | ||||
|  | ||||
| # if subdomain_host is not empty, you can set subdomain when type is http or https in frpc's configure file | ||||
| # when subdomain is test, the host used by routing is test.frps.com | ||||
| subdomain_host = frps.com | ||||
|  | ||||
| # if tcp stream multiplexing is used, default is true | ||||
| tcp_mux = true | ||||
							
								
								
									
										
											BIN
										
									
								
								doc/pic/dashboard.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/pic/dashboard.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 31 KiB | 
							
								
								
									
										
											BIN
										
									
								
								doc/pic/donate-alipay.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/pic/donate-alipay.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 36 KiB | 
							
								
								
									
										
											BIN
										
									
								
								doc/pic/donate-wechatpay.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/pic/donate-wechatpay.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 27 KiB | 
							
								
								
									
										
											BIN
										
									
								
								doc/pic/zsxq.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/pic/zsxq.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 12 KiB | 
| @@ -1,71 +0,0 @@ | ||||
| # Quick Start | ||||
|  | ||||
| frp is easier to use compared with other similar projects. | ||||
|  | ||||
| We will use a simple demo to demonstrate how to create a connection to server A's ssh port by server B with public IP address x.x.x.x(replace to the real IP address of your server). | ||||
|  | ||||
| ### Download SourceCode | ||||
|  | ||||
| `go get github.com/fatedier/frp` is recommended, then the code will be copied to the directory `$GOPATH/src/github.com/fatedier/frp`. | ||||
|  | ||||
| Or you can use `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp`. | ||||
|  | ||||
| ### Compile | ||||
|  | ||||
| Enter the root directory and execute `make`, then wait until finished. | ||||
|  | ||||
| **bin** include all executable programs when **conf** include corresponding configuration files. | ||||
|  | ||||
| ### Pre-requirement | ||||
|  | ||||
| * Go environment. Version of go >= 1.4. | ||||
| * Godep (if not exist, go get will be executed to download godep when compiling) | ||||
|  | ||||
| ### Deploy | ||||
|  | ||||
| 1. Move `./bin/frps` and `./conf/frps.ini` to any directory of server B. | ||||
| 2. Move `./bin/frpc` and `./conf/frpc.ini` to any directory of server A. | ||||
| 3. Modify all configuration files, details in next paragraph. | ||||
| 4. Execute `nohup ./frps &` or `nohup ./frps -c ./frps.ini &` in server B. | ||||
| 5. Execute `nohup ./frpc &` or `nohup ./frpc -c ./frpc.ini &` in server A. | ||||
| 6. Use `ssh -oPort=6000 {user}@x.x.x.x` to test if frp is work(replace {user} to real username in server A). | ||||
|  | ||||
| ### Configuration files | ||||
|  | ||||
| #### frps.ini | ||||
|  | ||||
| ```ini | ||||
| [common] | ||||
| bind_addr = 0.0.0.0 | ||||
| # for accept connections from frpc | ||||
| bind_port = 7000 | ||||
| log_file = ./frps.log | ||||
| log_level = info | ||||
|  | ||||
| # test is the custom name of proxy and there can be many proxies with unique name in one configure file | ||||
| [test] | ||||
| auth_token = 123 | ||||
| bind_addr = 0.0.0.0 | ||||
| # finally we connect to server A by this port | ||||
| listen_port = 6000 | ||||
| ``` | ||||
|  | ||||
| #### frpc.ini | ||||
|  | ||||
| ```ini | ||||
| [common] | ||||
| # server address of frps | ||||
| server_addr = x.x.x.x | ||||
| server_port = 7000 | ||||
| log_file = ./frpc.log | ||||
| log_level = info | ||||
| # for authentication | ||||
| auth_token = 123 | ||||
|  | ||||
| # test is proxy name same with configure in frps.ini | ||||
| [test] | ||||
| # local port which need to be transferred | ||||
| local_port = 22 | ||||
| # if use_encryption equals true, messages between frpc and frps will be encrypted, default is false | ||||
| use_encryption = true | ||||
| ``` | ||||
| @@ -1,69 +0,0 @@ | ||||
| # frp 使用文档 | ||||
|  | ||||
| frp 相比于其他项目而言非常易于部署和使用,这里我们用一个简单的示例演示如何通过一台拥有公网IP地址的服务器B,访问处于内网环境中的服务器A的ssh端口,服务器B的IP地址为 x.x.x.x(测试时替换为真实的IP地址)。 | ||||
|  | ||||
| ### 下载源码 | ||||
|  | ||||
| 推荐直接使用 `go get github.com/fatedier/frp` 下载源代码安装,执行命令后代码将会拷贝到 `$GOPATH/src/github.com/fatedier/frp` 目录下。 | ||||
|  | ||||
| 或者可以使用 `git clone https://github.com/fatedier/frp.git $GOPATH/src/github.com/fatedier/frp` 拷贝到相应目录下。 | ||||
|  | ||||
| ### 编译 | ||||
|  | ||||
| 进入下载后的源码根目录,执行 `make` 命令,等待编译完成。 | ||||
|  | ||||
| 编译完成后, **bin** 目录下是编译好的可执行文件,**conf** 目录下是示例配置文件。 | ||||
|  | ||||
| ### 依赖 | ||||
|  | ||||
| * go 1.4 以上版本 | ||||
| * godep (如果检查不存在,编译时会通过 go get 命令安装) | ||||
|  | ||||
| ### 部署 | ||||
|  | ||||
| 1. 将 ./bin/frps 和 ./conf/frps.ini 拷贝至服务器B任意目录。 | ||||
| 2. 将 ./bin/frpc 和 ./conf/frpc.ini 拷贝至服务器A任意目录。 | ||||
| 3. 修改两边的配置文件,见下一节说明。 | ||||
| 4. 在服务器B执行 `nohup ./frps &` 或者 `nohup ./frps -c ./frps.ini &`。 | ||||
| 5. 在服务器A执行 `nohup ./frpc &` 或者 `nohup ./frpc -c ./frpc.ini &`。 | ||||
| 6. 通过 `ssh -oPort=6000 {user}@x.x.x.x` 测试是否能够成功连接服务器A({user}替换为服务器A上存在的真实用户)。 | ||||
|  | ||||
| ### 配置文件 | ||||
|  | ||||
| #### frps.ini | ||||
|  | ||||
| ```ini | ||||
| [common] | ||||
| bind_addr = 0.0.0.0 | ||||
| # 用于接收 frpc 连接的端口 | ||||
| bind_port = 7000 | ||||
| log_file = ./frps.log | ||||
| log_level = info | ||||
|  | ||||
| # test 为代理的自定义名称,可以有多个,不能重复,和frpc中名称对应 | ||||
| [test] | ||||
| auth_token = 123 | ||||
| bind_addr = 0.0.0.0 | ||||
| # 最后将通过此端口访问后端服务 | ||||
| listen_port = 6000 | ||||
| ``` | ||||
|  | ||||
| #### frpc.ini | ||||
|  | ||||
| ```ini | ||||
| [common] | ||||
| # frps 所在服务器绑定的IP地址 | ||||
| server_addr = x.x.x.x | ||||
| server_port = 7000 | ||||
| log_file = ./frpc.log | ||||
| log_level = info | ||||
| # 用于身份验证 | ||||
| auth_token = 123 | ||||
|  | ||||
| # test需要和 frps.ini 中配置一致 | ||||
| [test] | ||||
| # 需要转发的本地端口 | ||||
| local_port = 22 | ||||
| # 启用加密,frpc与frps之间通信加密,默认为 false | ||||
| use_encryption = true | ||||
| ``` | ||||
							
								
								
									
										32
									
								
								g/g.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								g/g.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| package g | ||||
|  | ||||
| import ( | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	GlbClientCfg *ClientCfg | ||||
| 	GlbServerCfg *ServerCfg | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	GlbClientCfg = &ClientCfg{ | ||||
| 		ClientCommonConf: *config.GetDefaultClientConf(), | ||||
| 	} | ||||
| 	GlbServerCfg = &ServerCfg{ | ||||
| 		ServerCommonConf: *config.GetDefaultServerConf(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type ClientCfg struct { | ||||
| 	config.ClientCommonConf | ||||
|  | ||||
| 	CfgFile       string | ||||
| 	ServerUdpPort int // this is configured by login response from frps | ||||
| } | ||||
|  | ||||
| type ServerCfg struct { | ||||
| 	config.ServerCommonConf | ||||
|  | ||||
| 	CfgFile string | ||||
| } | ||||
							
								
								
									
										32
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| module github.com/fatedier/frp | ||||
|  | ||||
| go 1.12 | ||||
|  | ||||
| require ( | ||||
| 	github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 | ||||
| 	github.com/davecgh/go-spew v1.1.0 // indirect | ||||
| 	github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb | ||||
| 	github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 | ||||
| 	github.com/fatedier/kcp-go v0.0.0-20171023144637-cd167d2f15f4 | ||||
| 	github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect | ||||
| 	github.com/gorilla/context v1.1.1 // indirect | ||||
| 	github.com/gorilla/mux v1.6.2 | ||||
| 	github.com/gorilla/websocket v1.2.0 | ||||
| 	github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0 | ||||
| 	github.com/inconshreveable/mousetrap v1.0.0 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.4 // indirect | ||||
| 	github.com/pkg/errors v0.8.0 // indirect | ||||
| 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||
| 	github.com/rakyll/statik v0.1.1 | ||||
| 	github.com/rodaine/table v1.0.0 | ||||
| 	github.com/spf13/cobra v0.0.3 | ||||
| 	github.com/spf13/pflag v1.0.1 // indirect | ||||
| 	github.com/stretchr/testify v1.2.1 | ||||
| 	github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect | ||||
| 	github.com/templexxx/reedsolomon v0.0.0-20170926020725-5e06b81a1c76 // indirect | ||||
| 	github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect | ||||
| 	github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect | ||||
| 	github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec | ||||
| 	golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab // indirect | ||||
| 	golang.org/x/net v0.0.0-20180524181706-dfa909b99c79 | ||||
| ) | ||||
							
								
								
									
										30
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk= | ||||
| github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8= | ||||
| github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM= | ||||
| github.com/fatedier/kcp-go v0.0.0-20171023144637-cd167d2f15f4/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s= | ||||
| github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||
| github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= | ||||
| github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= | ||||
| github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= | ||||
| github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= | ||||
| github.com/hashicorp/yamux v0.0.0-20180314200745-2658be15c5f0/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= | ||||
| github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= | ||||
| github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= | ||||
| github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= | ||||
| github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= | ||||
| github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= | ||||
| github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I= | ||||
| github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= | ||||
| github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||||
| github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||
| github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= | ||||
| github.com/templexxx/reedsolomon v0.0.0-20170926020725-5e06b81a1c76/go.mod h1:ToWcj2sZ6xHl14JjZiVDktYpFtrFZJXBlsu7TV23lNg= | ||||
| github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= | ||||
| github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= | ||||
| github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw= | ||||
| golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
							
								
								
									
										228
									
								
								models/config/client_common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								models/config/client_common.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,228 @@ | ||||
| // Copyright 2016 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	ini "github.com/vaughan0/go-ini" | ||||
| ) | ||||
|  | ||||
| // client common config | ||||
| type ClientCommonConf struct { | ||||
| 	ServerAddr        string              `json:"server_addr"` | ||||
| 	ServerPort        int                 `json:"server_port"` | ||||
| 	HttpProxy         string              `json:"http_proxy"` | ||||
| 	LogFile           string              `json:"log_file"` | ||||
| 	LogWay            string              `json:"log_way"` | ||||
| 	LogLevel          string              `json:"log_level"` | ||||
| 	LogMaxDays        int64               `json:"log_max_days"` | ||||
| 	Token             string              `json:"token"` | ||||
| 	AdminAddr         string              `json:"admin_addr"` | ||||
| 	AdminPort         int                 `json:"admin_port"` | ||||
| 	AdminUser         string              `json:"admin_user"` | ||||
| 	AdminPwd          string              `json:"admin_pwd"` | ||||
| 	PoolCount         int                 `json:"pool_count"` | ||||
| 	TcpMux            bool                `json:"tcp_mux"` | ||||
| 	User              string              `json:"user"` | ||||
| 	DnsServer         string              `json:"dns_server"` | ||||
| 	LoginFailExit     bool                `json:"login_fail_exit"` | ||||
| 	Start             map[string]struct{} `json:"start"` | ||||
| 	Protocol          string              `json:"protocol"` | ||||
| 	HeartBeatInterval int64               `json:"heartbeat_interval"` | ||||
| 	HeartBeatTimeout  int64               `json:"heartbeat_timeout"` | ||||
| } | ||||
|  | ||||
| func GetDefaultClientConf() *ClientCommonConf { | ||||
| 	return &ClientCommonConf{ | ||||
| 		ServerAddr:        "0.0.0.0", | ||||
| 		ServerPort:        7000, | ||||
| 		HttpProxy:         os.Getenv("http_proxy"), | ||||
| 		LogFile:           "console", | ||||
| 		LogWay:            "console", | ||||
| 		LogLevel:          "info", | ||||
| 		LogMaxDays:        3, | ||||
| 		Token:             "", | ||||
| 		AdminAddr:         "127.0.0.1", | ||||
| 		AdminPort:         0, | ||||
| 		AdminUser:         "", | ||||
| 		AdminPwd:          "", | ||||
| 		PoolCount:         1, | ||||
| 		TcpMux:            true, | ||||
| 		User:              "", | ||||
| 		DnsServer:         "", | ||||
| 		LoginFailExit:     true, | ||||
| 		Start:             make(map[string]struct{}), | ||||
| 		Protocol:          "tcp", | ||||
| 		HeartBeatInterval: 30, | ||||
| 		HeartBeatTimeout:  90, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (cfg *ClientCommonConf, err error) { | ||||
| 	cfg = defaultCfg | ||||
| 	if cfg == nil { | ||||
| 		cfg = GetDefaultClientConf() | ||||
| 	} | ||||
|  | ||||
| 	conf, err := ini.Load(strings.NewReader(content)) | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("parse ini conf file error: %v", err) | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		tmpStr string | ||||
| 		ok     bool | ||||
| 		v      int64 | ||||
| 	) | ||||
| 	if tmpStr, ok = conf.Get("common", "server_addr"); ok { | ||||
| 		cfg.ServerAddr = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "server_port"); ok { | ||||
| 		v, err = strconv.ParseInt(tmpStr, 10, 64) | ||||
| 		if err != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid server_port") | ||||
| 			return | ||||
| 		} | ||||
| 		cfg.ServerPort = int(v) | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "http_proxy"); ok { | ||||
| 		cfg.HttpProxy = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "log_file"); ok { | ||||
| 		cfg.LogFile = tmpStr | ||||
| 		if cfg.LogFile == "console" { | ||||
| 			cfg.LogWay = "console" | ||||
| 		} else { | ||||
| 			cfg.LogWay = "file" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "log_level"); ok { | ||||
| 		cfg.LogLevel = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "log_max_days"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil { | ||||
| 			cfg.LogMaxDays = v | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "token"); ok { | ||||
| 		cfg.Token = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "admin_addr"); ok { | ||||
| 		cfg.AdminAddr = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "admin_port"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil { | ||||
| 			cfg.AdminPort = int(v) | ||||
| 		} else { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid admin_port") | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "admin_user"); ok { | ||||
| 		cfg.AdminUser = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "admin_pwd"); ok { | ||||
| 		cfg.AdminPwd = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "pool_count"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil { | ||||
| 			cfg.PoolCount = int(v) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" { | ||||
| 		cfg.TcpMux = false | ||||
| 	} else { | ||||
| 		cfg.TcpMux = true | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "user"); ok { | ||||
| 		cfg.User = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "dns_server"); ok { | ||||
| 		cfg.DnsServer = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "start"); ok { | ||||
| 		proxyNames := strings.Split(tmpStr, ",") | ||||
| 		for _, name := range proxyNames { | ||||
| 			cfg.Start[strings.TrimSpace(name)] = struct{}{} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "login_fail_exit"); ok && tmpStr == "false" { | ||||
| 		cfg.LoginFailExit = false | ||||
| 	} else { | ||||
| 		cfg.LoginFailExit = true | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "protocol"); ok { | ||||
| 		// Now it only support tcp and kcp and websocket. | ||||
| 		if tmpStr != "tcp" && tmpStr != "kcp" && tmpStr != "websocket" { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid protocol") | ||||
| 			return | ||||
| 		} | ||||
| 		cfg.Protocol = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout") | ||||
| 			return | ||||
| 		} else { | ||||
| 			cfg.HeartBeatTimeout = v | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid heartbeat_interval") | ||||
| 			return | ||||
| 		} else { | ||||
| 			cfg.HeartBeatInterval = v | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *ClientCommonConf) Check() (err error) { | ||||
| 	if cfg.HeartBeatInterval <= 0 { | ||||
| 		err = fmt.Errorf("Parse conf error: invalid heartbeat_interval") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if cfg.HeartBeatTimeout < cfg.HeartBeatInterval { | ||||
| 		err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval") | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										964
									
								
								models/config/proxy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										964
									
								
								models/config/proxy.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,964 @@ | ||||
| // Copyright 2016 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/consts" | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
| 	"github.com/fatedier/frp/utils/util" | ||||
|  | ||||
| 	ini "github.com/vaughan0/go-ini" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	proxyConfTypeMap map[string]reflect.Type | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	proxyConfTypeMap = make(map[string]reflect.Type) | ||||
| 	proxyConfTypeMap[consts.TcpProxy] = reflect.TypeOf(TcpProxyConf{}) | ||||
| 	proxyConfTypeMap[consts.UdpProxy] = reflect.TypeOf(UdpProxyConf{}) | ||||
| 	proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{}) | ||||
| 	proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{}) | ||||
| 	proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{}) | ||||
| 	proxyConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpProxyConf{}) | ||||
| } | ||||
|  | ||||
| // NewConfByType creates a empty ProxyConf object by proxyType. | ||||
| // If proxyType isn't exist, return nil. | ||||
| func NewConfByType(proxyType string) ProxyConf { | ||||
| 	v, ok := proxyConfTypeMap[proxyType] | ||||
| 	if !ok { | ||||
| 		return nil | ||||
| 	} | ||||
| 	cfg := reflect.New(v).Interface().(ProxyConf) | ||||
| 	return cfg | ||||
| } | ||||
|  | ||||
| type ProxyConf interface { | ||||
| 	GetBaseInfo() *BaseProxyConf | ||||
| 	UnmarshalFromMsg(pMsg *msg.NewProxy) | ||||
| 	UnmarshalFromIni(prefix string, name string, conf ini.Section) error | ||||
| 	MarshalToMsg(pMsg *msg.NewProxy) | ||||
| 	CheckForCli() error | ||||
| 	CheckForSvr() error | ||||
| 	Compare(conf ProxyConf) bool | ||||
| } | ||||
|  | ||||
| func NewProxyConfFromMsg(pMsg *msg.NewProxy) (cfg ProxyConf, err error) { | ||||
| 	if pMsg.ProxyType == "" { | ||||
| 		pMsg.ProxyType = consts.TcpProxy | ||||
| 	} | ||||
|  | ||||
| 	cfg = NewConfByType(pMsg.ProxyType) | ||||
| 	if cfg == nil { | ||||
| 		err = fmt.Errorf("proxy [%s] type [%s] error", pMsg.ProxyName, pMsg.ProxyType) | ||||
| 		return | ||||
| 	} | ||||
| 	cfg.UnmarshalFromMsg(pMsg) | ||||
| 	err = cfg.CheckForSvr() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg ProxyConf, err error) { | ||||
| 	proxyType := section["type"] | ||||
| 	if proxyType == "" { | ||||
| 		proxyType = consts.TcpProxy | ||||
| 		section["type"] = consts.TcpProxy | ||||
| 	} | ||||
| 	cfg = NewConfByType(proxyType) | ||||
| 	if cfg == nil { | ||||
| 		err = fmt.Errorf("proxy [%s] type [%s] error", name, proxyType) | ||||
| 		return | ||||
| 	} | ||||
| 	if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = cfg.CheckForCli(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // BaseProxy info | ||||
| type BaseProxyConf struct { | ||||
| 	ProxyName string `json:"proxy_name"` | ||||
| 	ProxyType string `json:"proxy_type"` | ||||
|  | ||||
| 	UseEncryption  bool   `json:"use_encryption"` | ||||
| 	UseCompression bool   `json:"use_compression"` | ||||
| 	Group          string `json:"group"` | ||||
| 	GroupKey       string `json:"group_key"` | ||||
|  | ||||
| 	LocalSvrConf | ||||
| 	HealthCheckConf // only used for client | ||||
| } | ||||
|  | ||||
| func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf { | ||||
| 	return cfg | ||||
| } | ||||
|  | ||||
| func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool { | ||||
| 	if cfg.ProxyName != cmp.ProxyName || | ||||
| 		cfg.ProxyType != cmp.ProxyType || | ||||
| 		cfg.UseEncryption != cmp.UseEncryption || | ||||
| 		cfg.UseCompression != cmp.UseCompression || | ||||
| 		cfg.Group != cmp.Group || | ||||
| 		cfg.GroupKey != cmp.GroupKey { | ||||
| 		return false | ||||
| 	} | ||||
| 	if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) { | ||||
| 		return false | ||||
| 	} | ||||
| 	if !cfg.HealthCheckConf.compare(&cmp.HealthCheckConf) { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *BaseProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.ProxyName = pMsg.ProxyName | ||||
| 	cfg.ProxyType = pMsg.ProxyType | ||||
| 	cfg.UseEncryption = pMsg.UseEncryption | ||||
| 	cfg.UseCompression = pMsg.UseCompression | ||||
| 	cfg.Group = pMsg.Group | ||||
| 	cfg.GroupKey = pMsg.GroupKey | ||||
| } | ||||
|  | ||||
| func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) error { | ||||
| 	var ( | ||||
| 		tmpStr string | ||||
| 		ok     bool | ||||
| 	) | ||||
| 	cfg.ProxyName = prefix + name | ||||
| 	cfg.ProxyType = section["type"] | ||||
|  | ||||
| 	tmpStr, ok = section["use_encryption"] | ||||
| 	if ok && tmpStr == "true" { | ||||
| 		cfg.UseEncryption = true | ||||
| 	} | ||||
|  | ||||
| 	tmpStr, ok = section["use_compression"] | ||||
| 	if ok && tmpStr == "true" { | ||||
| 		cfg.UseCompression = true | ||||
| 	} | ||||
|  | ||||
| 	cfg.Group = section["group"] | ||||
| 	cfg.GroupKey = section["group_key"] | ||||
|  | ||||
| 	if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := cfg.HealthCheckConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if cfg.HealthCheckType == "tcp" && cfg.Plugin == "" { | ||||
| 		cfg.HealthCheckAddr = cfg.LocalIp + fmt.Sprintf(":%d", cfg.LocalPort) | ||||
| 	} | ||||
| 	if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckUrl != "" { | ||||
| 		s := fmt.Sprintf("http://%s:%d", cfg.LocalIp, cfg.LocalPort) | ||||
| 		if !strings.HasPrefix(cfg.HealthCheckUrl, "/") { | ||||
| 			s += "/" | ||||
| 		} | ||||
| 		cfg.HealthCheckUrl = s + cfg.HealthCheckUrl | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { | ||||
| 	pMsg.ProxyName = cfg.ProxyName | ||||
| 	pMsg.ProxyType = cfg.ProxyType | ||||
| 	pMsg.UseEncryption = cfg.UseEncryption | ||||
| 	pMsg.UseCompression = cfg.UseCompression | ||||
| 	pMsg.Group = cfg.Group | ||||
| 	pMsg.GroupKey = cfg.GroupKey | ||||
| } | ||||
|  | ||||
| func (cfg *BaseProxyConf) checkForCli() (err error) { | ||||
| 	if err = cfg.LocalSvrConf.checkForCli(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = cfg.HealthCheckConf.checkForCli(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Bind info | ||||
| type BindInfoConf struct { | ||||
| 	RemotePort int `json:"remote_port"` | ||||
| } | ||||
|  | ||||
| func (cfg *BindInfoConf) compare(cmp *BindInfoConf) bool { | ||||
| 	if cfg.RemotePort != cmp.RemotePort { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *BindInfoConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.RemotePort = pMsg.RemotePort | ||||
| } | ||||
|  | ||||
| func (cfg *BindInfoConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	var ( | ||||
| 		tmpStr string | ||||
| 		ok     bool | ||||
| 		v      int64 | ||||
| 	) | ||||
| 	if tmpStr, ok = section["remote_port"]; ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { | ||||
| 			return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", name) | ||||
| 		} else { | ||||
| 			cfg.RemotePort = int(v) | ||||
| 		} | ||||
| 	} else { | ||||
| 		return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", name) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (cfg *BindInfoConf) MarshalToMsg(pMsg *msg.NewProxy) { | ||||
| 	pMsg.RemotePort = cfg.RemotePort | ||||
| } | ||||
|  | ||||
| // Domain info | ||||
| type DomainConf struct { | ||||
| 	CustomDomains []string `json:"custom_domains"` | ||||
| 	SubDomain     string   `json:"sub_domain"` | ||||
| } | ||||
|  | ||||
| func (cfg *DomainConf) compare(cmp *DomainConf) bool { | ||||
| 	if strings.Join(cfg.CustomDomains, " ") != strings.Join(cmp.CustomDomains, " ") || | ||||
| 		cfg.SubDomain != cmp.SubDomain { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *DomainConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.CustomDomains = pMsg.CustomDomains | ||||
| 	cfg.SubDomain = pMsg.SubDomain | ||||
| } | ||||
|  | ||||
| func (cfg *DomainConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	var ( | ||||
| 		tmpStr string | ||||
| 		ok     bool | ||||
| 	) | ||||
| 	if tmpStr, ok = section["custom_domains"]; ok { | ||||
| 		cfg.CustomDomains = strings.Split(tmpStr, ",") | ||||
| 		for i, domain := range cfg.CustomDomains { | ||||
| 			cfg.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = section["subdomain"]; ok { | ||||
| 		cfg.SubDomain = tmpStr | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *DomainConf) MarshalToMsg(pMsg *msg.NewProxy) { | ||||
| 	pMsg.CustomDomains = cfg.CustomDomains | ||||
| 	pMsg.SubDomain = cfg.SubDomain | ||||
| } | ||||
|  | ||||
| func (cfg *DomainConf) check() (err error) { | ||||
| 	if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" { | ||||
| 		err = fmt.Errorf("custom_domains and subdomain should set at least one of them") | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *DomainConf) checkForCli() (err error) { | ||||
| 	if err = cfg.check(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *DomainConf) checkForSvr() (err error) { | ||||
| 	if err = cfg.check(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, domain := range cfg.CustomDomains { | ||||
| 		if subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(domain, ".")) { | ||||
| 			if strings.Contains(domain, subDomainHost) { | ||||
| 				return fmt.Errorf("custom domain [%s] should not belong to subdomain_host [%s]", domain, subDomainHost) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if cfg.SubDomain != "" { | ||||
| 		if subDomainHost == "" { | ||||
| 			return fmt.Errorf("subdomain is not supported because this feature is not enabled in remote frps") | ||||
| 		} | ||||
| 		if strings.Contains(cfg.SubDomain, ".") || strings.Contains(cfg.SubDomain, "*") { | ||||
| 			return fmt.Errorf("'.' and '*' is not supported in subdomain") | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Local service info | ||||
| type LocalSvrConf struct { | ||||
| 	LocalIp   string `json:"local_ip"` | ||||
| 	LocalPort int    `json:"local_port"` | ||||
|  | ||||
| 	Plugin       string            `json:"plugin"` | ||||
| 	PluginParams map[string]string `json:"plugin_params"` | ||||
| } | ||||
|  | ||||
| func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool { | ||||
| 	if cfg.LocalIp != cmp.LocalIp || | ||||
| 		cfg.LocalPort != cmp.LocalPort { | ||||
| 		return false | ||||
| 	} | ||||
| 	if cfg.Plugin != cmp.Plugin || | ||||
| 		len(cfg.PluginParams) != len(cmp.PluginParams) { | ||||
| 		return false | ||||
| 	} | ||||
| 	for k, v := range cfg.PluginParams { | ||||
| 		value, ok := cmp.PluginParams[k] | ||||
| 		if !ok || v != value { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	cfg.Plugin = section["plugin"] | ||||
| 	cfg.PluginParams = make(map[string]string) | ||||
| 	if cfg.Plugin != "" { | ||||
| 		// get params begin with "plugin_" | ||||
| 		for k, v := range section { | ||||
| 			if strings.HasPrefix(k, "plugin_") { | ||||
| 				cfg.PluginParams[k] = v | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		if cfg.LocalIp = section["local_ip"]; cfg.LocalIp == "" { | ||||
| 			cfg.LocalIp = "127.0.0.1" | ||||
| 		} | ||||
|  | ||||
| 		if tmpStr, ok := section["local_port"]; ok { | ||||
| 			if cfg.LocalPort, err = strconv.Atoi(tmpStr); err != nil { | ||||
| 				return fmt.Errorf("Parse conf error: proxy [%s] local_port error", name) | ||||
| 			} | ||||
| 		} else { | ||||
| 			return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", name) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *LocalSvrConf) checkForCli() (err error) { | ||||
| 	if cfg.Plugin == "" { | ||||
| 		if cfg.LocalIp == "" { | ||||
| 			err = fmt.Errorf("local ip or plugin is required") | ||||
| 			return | ||||
| 		} | ||||
| 		if cfg.LocalPort <= 0 { | ||||
| 			err = fmt.Errorf("error local_port") | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Health check info | ||||
| type HealthCheckConf struct { | ||||
| 	HealthCheckType      string `json:"health_check_type"` // tcp | http | ||||
| 	HealthCheckTimeoutS  int    `json:"health_check_timeout_s"` | ||||
| 	HealthCheckMaxFailed int    `json:"health_check_max_failed"` | ||||
| 	HealthCheckIntervalS int    `json:"health_check_interval_s"` | ||||
| 	HealthCheckUrl       string `json:"health_check_url"` | ||||
|  | ||||
| 	// local_ip + local_port | ||||
| 	HealthCheckAddr string `json:"-"` | ||||
| } | ||||
|  | ||||
| func (cfg *HealthCheckConf) compare(cmp *HealthCheckConf) bool { | ||||
| 	if cfg.HealthCheckType != cmp.HealthCheckType || | ||||
| 		cfg.HealthCheckTimeoutS != cmp.HealthCheckTimeoutS || | ||||
| 		cfg.HealthCheckMaxFailed != cmp.HealthCheckMaxFailed || | ||||
| 		cfg.HealthCheckIntervalS != cmp.HealthCheckIntervalS || | ||||
| 		cfg.HealthCheckUrl != cmp.HealthCheckUrl { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *HealthCheckConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	cfg.HealthCheckType = section["health_check_type"] | ||||
| 	cfg.HealthCheckUrl = section["health_check_url"] | ||||
|  | ||||
| 	if tmpStr, ok := section["health_check_timeout_s"]; ok { | ||||
| 		if cfg.HealthCheckTimeoutS, err = strconv.Atoi(tmpStr); err != nil { | ||||
| 			return fmt.Errorf("Parse conf error: proxy [%s] health_check_timeout_s error", name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok := section["health_check_max_failed"]; ok { | ||||
| 		if cfg.HealthCheckMaxFailed, err = strconv.Atoi(tmpStr); err != nil { | ||||
| 			return fmt.Errorf("Parse conf error: proxy [%s] health_check_max_failed error", name) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok := section["health_check_interval_s"]; ok { | ||||
| 		if cfg.HealthCheckIntervalS, err = strconv.Atoi(tmpStr); err != nil { | ||||
| 			return fmt.Errorf("Parse conf error: proxy [%s] health_check_interval_s error", name) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *HealthCheckConf) checkForCli() error { | ||||
| 	if cfg.HealthCheckType != "" && cfg.HealthCheckType != "tcp" && cfg.HealthCheckType != "http" { | ||||
| 		return fmt.Errorf("unsupport health check type") | ||||
| 	} | ||||
| 	if cfg.HealthCheckType != "" { | ||||
| 		if cfg.HealthCheckType == "http" && cfg.HealthCheckUrl == "" { | ||||
| 			return fmt.Errorf("health_check_url is required for health check type 'http'") | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // TCP | ||||
| type TcpProxyConf struct { | ||||
| 	BaseProxyConf | ||||
| 	BindInfoConf | ||||
| } | ||||
|  | ||||
| func (cfg *TcpProxyConf) Compare(cmp ProxyConf) bool { | ||||
| 	cmpConf, ok := cmp.(*TcpProxyConf) | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || | ||||
| 		!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *TcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) | ||||
| 	cfg.BindInfoConf.UnmarshalFromMsg(pMsg) | ||||
| } | ||||
|  | ||||
| func (cfg *TcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *TcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.MarshalToMsg(pMsg) | ||||
| 	cfg.BindInfoConf.MarshalToMsg(pMsg) | ||||
| } | ||||
|  | ||||
| func (cfg *TcpProxyConf) CheckForCli() (err error) { | ||||
| 	if err = cfg.BaseProxyConf.checkForCli(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *TcpProxyConf) CheckForSvr() error { return nil } | ||||
|  | ||||
| // UDP | ||||
| type UdpProxyConf struct { | ||||
| 	BaseProxyConf | ||||
| 	BindInfoConf | ||||
| } | ||||
|  | ||||
| func (cfg *UdpProxyConf) Compare(cmp ProxyConf) bool { | ||||
| 	cmpConf, ok := cmp.(*UdpProxyConf) | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || | ||||
| 		!cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *UdpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) | ||||
| 	cfg.BindInfoConf.UnmarshalFromMsg(pMsg) | ||||
| } | ||||
|  | ||||
| func (cfg *UdpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *UdpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.MarshalToMsg(pMsg) | ||||
| 	cfg.BindInfoConf.MarshalToMsg(pMsg) | ||||
| } | ||||
|  | ||||
| func (cfg *UdpProxyConf) CheckForCli() (err error) { | ||||
| 	if err = cfg.BaseProxyConf.checkForCli(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *UdpProxyConf) CheckForSvr() error { return nil } | ||||
|  | ||||
| // HTTP | ||||
| type HttpProxyConf struct { | ||||
| 	BaseProxyConf | ||||
| 	DomainConf | ||||
|  | ||||
| 	Locations         []string          `json:"locations"` | ||||
| 	HttpUser          string            `json:"http_user"` | ||||
| 	HttpPwd           string            `json:"http_pwd"` | ||||
| 	HostHeaderRewrite string            `json:"host_header_rewrite"` | ||||
| 	Headers           map[string]string `json:"headers"` | ||||
| } | ||||
|  | ||||
| func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool { | ||||
| 	cmpConf, ok := cmp.(*HttpProxyConf) | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || | ||||
| 		!cfg.DomainConf.compare(&cmpConf.DomainConf) || | ||||
| 		strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") || | ||||
| 		cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite || | ||||
| 		cfg.HttpUser != cmpConf.HttpUser || | ||||
| 		cfg.HttpPwd != cmpConf.HttpPwd || | ||||
| 		len(cfg.Headers) != len(cmpConf.Headers) { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range cfg.Headers { | ||||
| 		if v2, ok := cmpConf.Headers[k]; !ok { | ||||
| 			return false | ||||
| 		} else { | ||||
| 			if v != v2 { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *HttpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) | ||||
| 	cfg.DomainConf.UnmarshalFromMsg(pMsg) | ||||
|  | ||||
| 	cfg.Locations = pMsg.Locations | ||||
| 	cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite | ||||
| 	cfg.HttpUser = pMsg.HttpUser | ||||
| 	cfg.HttpPwd = pMsg.HttpPwd | ||||
| 	cfg.Headers = pMsg.Headers | ||||
| } | ||||
|  | ||||
| func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		tmpStr string | ||||
| 		ok     bool | ||||
| 	) | ||||
| 	if tmpStr, ok = section["locations"]; ok { | ||||
| 		cfg.Locations = strings.Split(tmpStr, ",") | ||||
| 	} else { | ||||
| 		cfg.Locations = []string{""} | ||||
| 	} | ||||
|  | ||||
| 	cfg.HostHeaderRewrite = section["host_header_rewrite"] | ||||
| 	cfg.HttpUser = section["http_user"] | ||||
| 	cfg.HttpPwd = section["http_pwd"] | ||||
| 	cfg.Headers = make(map[string]string) | ||||
|  | ||||
| 	for k, v := range section { | ||||
| 		if strings.HasPrefix(k, "header_") { | ||||
| 			cfg.Headers[strings.TrimPrefix(k, "header_")] = v | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *HttpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.MarshalToMsg(pMsg) | ||||
| 	cfg.DomainConf.MarshalToMsg(pMsg) | ||||
|  | ||||
| 	pMsg.Locations = cfg.Locations | ||||
| 	pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite | ||||
| 	pMsg.HttpUser = cfg.HttpUser | ||||
| 	pMsg.HttpPwd = cfg.HttpPwd | ||||
| 	pMsg.Headers = cfg.Headers | ||||
| } | ||||
|  | ||||
| func (cfg *HttpProxyConf) CheckForCli() (err error) { | ||||
| 	if err = cfg.BaseProxyConf.checkForCli(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = cfg.DomainConf.checkForCli(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *HttpProxyConf) CheckForSvr() (err error) { | ||||
| 	if vhostHttpPort == 0 { | ||||
| 		return fmt.Errorf("type [http] not support when vhost_http_port is not set") | ||||
| 	} | ||||
| 	if err = cfg.DomainConf.checkForSvr(); err != nil { | ||||
| 		err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // HTTPS | ||||
| type HttpsProxyConf struct { | ||||
| 	BaseProxyConf | ||||
| 	DomainConf | ||||
| } | ||||
|  | ||||
| func (cfg *HttpsProxyConf) Compare(cmp ProxyConf) bool { | ||||
| 	cmpConf, ok := cmp.(*HttpsProxyConf) | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || | ||||
| 		!cfg.DomainConf.compare(&cmpConf.DomainConf) { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *HttpsProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) | ||||
| 	cfg.DomainConf.UnmarshalFromMsg(pMsg) | ||||
| } | ||||
|  | ||||
| func (cfg *HttpsProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *HttpsProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.MarshalToMsg(pMsg) | ||||
| 	cfg.DomainConf.MarshalToMsg(pMsg) | ||||
| } | ||||
|  | ||||
| func (cfg *HttpsProxyConf) CheckForCli() (err error) { | ||||
| 	if err = cfg.BaseProxyConf.checkForCli(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = cfg.DomainConf.checkForCli(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *HttpsProxyConf) CheckForSvr() (err error) { | ||||
| 	if vhostHttpsPort == 0 { | ||||
| 		return fmt.Errorf("type [https] not support when vhost_https_port is not set") | ||||
| 	} | ||||
| 	if err = cfg.DomainConf.checkForSvr(); err != nil { | ||||
| 		err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // STCP | ||||
| type StcpProxyConf struct { | ||||
| 	BaseProxyConf | ||||
|  | ||||
| 	Role string `json:"role"` | ||||
| 	Sk   string `json:"sk"` | ||||
| } | ||||
|  | ||||
| func (cfg *StcpProxyConf) Compare(cmp ProxyConf) bool { | ||||
| 	cmpConf, ok := cmp.(*StcpProxyConf) | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || | ||||
| 		cfg.Role != cmpConf.Role || | ||||
| 		cfg.Sk != cmpConf.Sk { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // Only for role server. | ||||
| func (cfg *StcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) | ||||
| 	cfg.Sk = pMsg.Sk | ||||
| } | ||||
|  | ||||
| func (cfg *StcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	cfg.Role = section["role"] | ||||
| 	if cfg.Role != "server" { | ||||
| 		return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role) | ||||
| 	} | ||||
|  | ||||
| 	cfg.Sk = section["sk"] | ||||
|  | ||||
| 	if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *StcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.MarshalToMsg(pMsg) | ||||
| 	pMsg.Sk = cfg.Sk | ||||
| } | ||||
|  | ||||
| func (cfg *StcpProxyConf) CheckForCli() (err error) { | ||||
| 	if err = cfg.BaseProxyConf.checkForCli(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if cfg.Role != "server" { | ||||
| 		err = fmt.Errorf("role should be 'server'") | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *StcpProxyConf) CheckForSvr() (err error) { | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // XTCP | ||||
| type XtcpProxyConf struct { | ||||
| 	BaseProxyConf | ||||
|  | ||||
| 	Role string `json:"role"` | ||||
| 	Sk   string `json:"sk"` | ||||
| } | ||||
|  | ||||
| func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool { | ||||
| 	cmpConf, ok := cmp.(*XtcpProxyConf) | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || | ||||
| 		!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) || | ||||
| 		cfg.Role != cmpConf.Role || | ||||
| 		cfg.Sk != cmpConf.Sk { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // Only for role server. | ||||
| func (cfg *XtcpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) | ||||
| 	cfg.Sk = pMsg.Sk | ||||
| } | ||||
|  | ||||
| func (cfg *XtcpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	cfg.Role = section["role"] | ||||
| 	if cfg.Role != "server" { | ||||
| 		return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role) | ||||
| 	} | ||||
|  | ||||
| 	cfg.Sk = section["sk"] | ||||
|  | ||||
| 	if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *XtcpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { | ||||
| 	cfg.BaseProxyConf.MarshalToMsg(pMsg) | ||||
| 	pMsg.Sk = cfg.Sk | ||||
| } | ||||
|  | ||||
| func (cfg *XtcpProxyConf) CheckForCli() (err error) { | ||||
| 	if err = cfg.BaseProxyConf.checkForCli(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if cfg.Role != "server" { | ||||
| 		err = fmt.Errorf("role should be 'server'") | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *XtcpProxyConf) CheckForSvr() (err error) { | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func ParseRangeSection(name string, section ini.Section) (sections map[string]ini.Section, err error) { | ||||
| 	localPorts, errRet := util.ParseRangeNumbers(section["local_port"]) | ||||
| 	if errRet != nil { | ||||
| 		err = fmt.Errorf("Parse conf error: range section [%s] local_port invalid, %v", name, errRet) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	remotePorts, errRet := util.ParseRangeNumbers(section["remote_port"]) | ||||
| 	if errRet != nil { | ||||
| 		err = fmt.Errorf("Parse conf error: range section [%s] remote_port invalid, %v", name, errRet) | ||||
| 		return | ||||
| 	} | ||||
| 	if len(localPorts) != len(remotePorts) { | ||||
| 		err = fmt.Errorf("Parse conf error: range section [%s] local ports number should be same with remote ports number", name) | ||||
| 		return | ||||
| 	} | ||||
| 	if len(localPorts) == 0 { | ||||
| 		err = fmt.Errorf("Parse conf error: range section [%s] local_port and remote_port is necessary", name) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	sections = make(map[string]ini.Section) | ||||
| 	for i, port := range localPorts { | ||||
| 		subName := fmt.Sprintf("%s_%d", name, i) | ||||
| 		subSection := copySection(section) | ||||
| 		subSection["local_port"] = fmt.Sprintf("%d", port) | ||||
| 		subSection["remote_port"] = fmt.Sprintf("%d", remotePorts[i]) | ||||
| 		sections[subName] = subSection | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // if len(startProxy) is 0, start all | ||||
| // otherwise just start proxies in startProxy map | ||||
| func LoadAllConfFromIni(prefix string, content string, startProxy map[string]struct{}) ( | ||||
| 	proxyConfs map[string]ProxyConf, visitorConfs map[string]VisitorConf, err error) { | ||||
|  | ||||
| 	conf, errRet := ini.Load(strings.NewReader(content)) | ||||
| 	if errRet != nil { | ||||
| 		err = errRet | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if prefix != "" { | ||||
| 		prefix += "." | ||||
| 	} | ||||
|  | ||||
| 	startAll := true | ||||
| 	if len(startProxy) > 0 { | ||||
| 		startAll = false | ||||
| 	} | ||||
| 	proxyConfs = make(map[string]ProxyConf) | ||||
| 	visitorConfs = make(map[string]VisitorConf) | ||||
| 	for name, section := range conf { | ||||
| 		if name == "common" { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		_, shouldStart := startProxy[name] | ||||
| 		if !startAll && !shouldStart { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		subSections := make(map[string]ini.Section) | ||||
|  | ||||
| 		if strings.HasPrefix(name, "range:") { | ||||
| 			// range section | ||||
| 			rangePrefix := strings.TrimSpace(strings.TrimPrefix(name, "range:")) | ||||
| 			subSections, err = ParseRangeSection(rangePrefix, section) | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 		} else { | ||||
| 			subSections[name] = section | ||||
| 		} | ||||
|  | ||||
| 		for subName, subSection := range subSections { | ||||
| 			if subSection["role"] == "" { | ||||
| 				subSection["role"] = "server" | ||||
| 			} | ||||
| 			role := subSection["role"] | ||||
| 			if role == "server" { | ||||
| 				cfg, errRet := NewProxyConfFromIni(prefix, subName, subSection) | ||||
| 				if errRet != nil { | ||||
| 					err = errRet | ||||
| 					return | ||||
| 				} | ||||
| 				proxyConfs[prefix+subName] = cfg | ||||
| 			} else if role == "visitor" { | ||||
| 				cfg, errRet := NewVisitorConfFromIni(prefix, subName, subSection) | ||||
| 				if errRet != nil { | ||||
| 					err = errRet | ||||
| 					return | ||||
| 				} | ||||
| 				visitorConfs[prefix+subName] = cfg | ||||
| 			} else { | ||||
| 				err = fmt.Errorf("role should be 'server' or 'visitor'") | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func copySection(section ini.Section) (out ini.Section) { | ||||
| 	out = make(ini.Section) | ||||
| 	for k, v := range section { | ||||
| 		out[k] = v | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										310
									
								
								models/config/server_common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										310
									
								
								models/config/server_common.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,310 @@ | ||||
| // Copyright 2016 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	ini "github.com/vaughan0/go-ini" | ||||
|  | ||||
| 	"github.com/fatedier/frp/utils/util" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// server global configure used for generate proxy conf used in frps | ||||
| 	proxyBindAddr  string | ||||
| 	subDomainHost  string | ||||
| 	vhostHttpPort  int | ||||
| 	vhostHttpsPort int | ||||
| ) | ||||
|  | ||||
| func InitServerCfg(cfg *ServerCommonConf) { | ||||
| 	proxyBindAddr = cfg.ProxyBindAddr | ||||
| 	subDomainHost = cfg.SubDomainHost | ||||
| 	vhostHttpPort = cfg.VhostHttpPort | ||||
| 	vhostHttpsPort = cfg.VhostHttpsPort | ||||
| } | ||||
|  | ||||
| // common config | ||||
| type ServerCommonConf struct { | ||||
| 	BindAddr      string `json:"bind_addr"` | ||||
| 	BindPort      int    `json:"bind_port"` | ||||
| 	BindUdpPort   int    `json:"bind_udp_port"` | ||||
| 	KcpBindPort   int    `json:"kcp_bind_port"` | ||||
| 	ProxyBindAddr string `json:"proxy_bind_addr"` | ||||
|  | ||||
| 	// If VhostHttpPort equals 0, don't listen a public port for http protocol. | ||||
| 	VhostHttpPort int `json:"vhost_http_port"` | ||||
|  | ||||
| 	// if VhostHttpsPort equals 0, don't listen a public port for https protocol | ||||
| 	VhostHttpsPort int `json:"vhost_https_port"` | ||||
|  | ||||
| 	VhostHttpTimeout int64 `json:"vhost_http_timeout"` | ||||
|  | ||||
| 	DashboardAddr string `json:"dashboard_addr"` | ||||
|  | ||||
| 	// if DashboardPort equals 0, dashboard is not available | ||||
| 	DashboardPort int    `json:"dashboard_port"` | ||||
| 	DashboardUser string `json:"dashboard_user"` | ||||
| 	DashboardPwd  string `json:"dashboard_pwd"` | ||||
| 	AssetsDir     string `json:"asserts_dir"` | ||||
| 	LogFile       string `json:"log_file"` | ||||
| 	LogWay        string `json:"log_way"` // console or file | ||||
| 	LogLevel      string `json:"log_level"` | ||||
| 	LogMaxDays    int64  `json:"log_max_days"` | ||||
| 	Token         string `json:"token"` | ||||
| 	SubDomainHost string `json:"subdomain_host"` | ||||
| 	TcpMux        bool   `json:"tcp_mux"` | ||||
|  | ||||
| 	AllowPorts        map[int]struct{} | ||||
| 	MaxPoolCount      int64 `json:"max_pool_count"` | ||||
| 	MaxPortsPerClient int64 `json:"max_ports_per_client"` | ||||
| 	HeartBeatTimeout  int64 `json:"heart_beat_timeout"` | ||||
| 	UserConnTimeout   int64 `json:"user_conn_timeout"` | ||||
| } | ||||
|  | ||||
| func GetDefaultServerConf() *ServerCommonConf { | ||||
| 	return &ServerCommonConf{ | ||||
| 		BindAddr:          "0.0.0.0", | ||||
| 		BindPort:          7000, | ||||
| 		BindUdpPort:       0, | ||||
| 		KcpBindPort:       0, | ||||
| 		ProxyBindAddr:     "0.0.0.0", | ||||
| 		VhostHttpPort:     0, | ||||
| 		VhostHttpsPort:    0, | ||||
| 		VhostHttpTimeout:  60, | ||||
| 		DashboardAddr:     "0.0.0.0", | ||||
| 		DashboardPort:     0, | ||||
| 		DashboardUser:     "admin", | ||||
| 		DashboardPwd:      "admin", | ||||
| 		AssetsDir:         "", | ||||
| 		LogFile:           "console", | ||||
| 		LogWay:            "console", | ||||
| 		LogLevel:          "info", | ||||
| 		LogMaxDays:        3, | ||||
| 		Token:             "", | ||||
| 		SubDomainHost:     "", | ||||
| 		TcpMux:            true, | ||||
| 		AllowPorts:        make(map[int]struct{}), | ||||
| 		MaxPoolCount:      5, | ||||
| 		MaxPortsPerClient: 0, | ||||
| 		HeartBeatTimeout:  90, | ||||
| 		UserConnTimeout:   10, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (cfg *ServerCommonConf, err error) { | ||||
| 	cfg = defaultCfg | ||||
| 	if cfg == nil { | ||||
| 		cfg = GetDefaultServerConf() | ||||
| 	} | ||||
|  | ||||
| 	conf, err := ini.Load(strings.NewReader(content)) | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("parse ini conf file error: %v", err) | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		tmpStr string | ||||
| 		ok     bool | ||||
| 		v      int64 | ||||
| 	) | ||||
| 	if tmpStr, ok = conf.Get("common", "bind_addr"); ok { | ||||
| 		cfg.BindAddr = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "bind_port"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid bind_port") | ||||
| 			return | ||||
| 		} else { | ||||
| 			cfg.BindPort = int(v) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "bind_udp_port"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid bind_udp_port") | ||||
| 			return | ||||
| 		} else { | ||||
| 			cfg.BindUdpPort = int(v) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "kcp_bind_port"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid kcp_bind_port") | ||||
| 			return | ||||
| 		} else { | ||||
| 			cfg.KcpBindPort = int(v) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "proxy_bind_addr"); ok { | ||||
| 		cfg.ProxyBindAddr = tmpStr | ||||
| 	} else { | ||||
| 		cfg.ProxyBindAddr = cfg.BindAddr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "vhost_http_port"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid vhost_http_port") | ||||
| 			return | ||||
| 		} else { | ||||
| 			cfg.VhostHttpPort = int(v) | ||||
| 		} | ||||
| 	} else { | ||||
| 		cfg.VhostHttpPort = 0 | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "vhost_https_port"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid vhost_https_port") | ||||
| 			return | ||||
| 		} else { | ||||
| 			cfg.VhostHttpsPort = int(v) | ||||
| 		} | ||||
| 	} else { | ||||
| 		cfg.VhostHttpsPort = 0 | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok { | ||||
| 		v, errRet := strconv.ParseInt(tmpStr, 10, 64) | ||||
| 		if errRet != nil || v < 0 { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid vhost_http_timeout") | ||||
| 			return | ||||
| 		} else { | ||||
| 			cfg.VhostHttpTimeout = v | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "dashboard_addr"); ok { | ||||
| 		cfg.DashboardAddr = tmpStr | ||||
| 	} else { | ||||
| 		cfg.DashboardAddr = cfg.BindAddr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "dashboard_port"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid dashboard_port") | ||||
| 			return | ||||
| 		} else { | ||||
| 			cfg.DashboardPort = int(v) | ||||
| 		} | ||||
| 	} else { | ||||
| 		cfg.DashboardPort = 0 | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "dashboard_user"); ok { | ||||
| 		cfg.DashboardUser = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "dashboard_pwd"); ok { | ||||
| 		cfg.DashboardPwd = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "assets_dir"); ok { | ||||
| 		cfg.AssetsDir = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "log_file"); ok { | ||||
| 		cfg.LogFile = tmpStr | ||||
| 		if cfg.LogFile == "console" { | ||||
| 			cfg.LogWay = "console" | ||||
| 		} else { | ||||
| 			cfg.LogWay = "file" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "log_level"); ok { | ||||
| 		cfg.LogLevel = tmpStr | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "log_max_days"); ok { | ||||
| 		v, err = strconv.ParseInt(tmpStr, 10, 64) | ||||
| 		if err == nil { | ||||
| 			cfg.LogMaxDays = v | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	cfg.Token, _ = conf.Get("common", "token") | ||||
|  | ||||
| 	if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok { | ||||
| 		// e.g. 1000-2000,2001,2002,3000-4000 | ||||
| 		ports, errRet := util.ParseRangeNumbers(allowPortsStr) | ||||
| 		if errRet != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		for _, port := range ports { | ||||
| 			cfg.AllowPorts[int(port)] = struct{}{} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "max_pool_count"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid max_pool_count") | ||||
| 			return | ||||
| 		} else { | ||||
| 			if v < 0 { | ||||
| 				err = fmt.Errorf("Parse conf error: invalid max_pool_count") | ||||
| 				return | ||||
| 			} | ||||
| 			cfg.MaxPoolCount = v | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "max_ports_per_client"); ok { | ||||
| 		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: invalid max_ports_per_client") | ||||
| 			return | ||||
| 		} else { | ||||
| 			if v < 0 { | ||||
| 				err = fmt.Errorf("Parse conf error: invalid max_ports_per_client") | ||||
| 				return | ||||
| 			} | ||||
| 			cfg.MaxPortsPerClient = v | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "subdomain_host"); ok { | ||||
| 		cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr)) | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" { | ||||
| 		cfg.TcpMux = false | ||||
| 	} else { | ||||
| 		cfg.TcpMux = true | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok { | ||||
| 		v, errRet := strconv.ParseInt(tmpStr, 10, 64) | ||||
| 		if errRet != nil { | ||||
| 			err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect") | ||||
| 			return | ||||
| 		} else { | ||||
| 			cfg.HeartBeatTimeout = v | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *ServerCommonConf) Check() (err error) { | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										64
									
								
								models/config/value.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								models/config/value.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	glbEnvs map[string]string | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	glbEnvs = make(map[string]string) | ||||
| 	envs := os.Environ() | ||||
| 	for _, env := range envs { | ||||
| 		kv := strings.Split(env, "=") | ||||
| 		if len(kv) != 2 { | ||||
| 			continue | ||||
| 		} | ||||
| 		glbEnvs[kv[0]] = kv[1] | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type Values struct { | ||||
| 	Envs map[string]string // environment vars | ||||
| } | ||||
|  | ||||
| func GetValues() *Values { | ||||
| 	return &Values{ | ||||
| 		Envs: glbEnvs, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func RenderContent(in string) (out string, err error) { | ||||
| 	tmpl, errRet := template.New("frp").Parse(in) | ||||
| 	if errRet != nil { | ||||
| 		err = errRet | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	buffer := bytes.NewBufferString("") | ||||
| 	v := GetValues() | ||||
| 	err = tmpl.Execute(buffer, v) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	out = buffer.String() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func GetRenderedConfFromFile(path string) (out string, err error) { | ||||
| 	var b []byte | ||||
| 	b, err = ioutil.ReadFile(path) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	content := string(b) | ||||
|  | ||||
| 	out, err = RenderContent(content) | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										213
									
								
								models/config/visitor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								models/config/visitor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package config | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/consts" | ||||
|  | ||||
| 	ini "github.com/vaughan0/go-ini" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	visitorConfTypeMap map[string]reflect.Type | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	visitorConfTypeMap = make(map[string]reflect.Type) | ||||
| 	visitorConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpVisitorConf{}) | ||||
| 	visitorConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpVisitorConf{}) | ||||
| } | ||||
|  | ||||
| type VisitorConf interface { | ||||
| 	GetBaseInfo() *BaseVisitorConf | ||||
| 	Compare(cmp VisitorConf) bool | ||||
| 	UnmarshalFromIni(prefix string, name string, section ini.Section) error | ||||
| 	Check() error | ||||
| } | ||||
|  | ||||
| func NewVisitorConfByType(cfgType string) VisitorConf { | ||||
| 	v, ok := visitorConfTypeMap[cfgType] | ||||
| 	if !ok { | ||||
| 		return nil | ||||
| 	} | ||||
| 	cfg := reflect.New(v).Interface().(VisitorConf) | ||||
| 	return cfg | ||||
| } | ||||
|  | ||||
| func NewVisitorConfFromIni(prefix string, name string, section ini.Section) (cfg VisitorConf, err error) { | ||||
| 	cfgType := section["type"] | ||||
| 	if cfgType == "" { | ||||
| 		err = fmt.Errorf("visitor [%s] type shouldn't be empty", name) | ||||
| 		return | ||||
| 	} | ||||
| 	cfg = NewVisitorConfByType(cfgType) | ||||
| 	if cfg == nil { | ||||
| 		err = fmt.Errorf("visitor [%s] type [%s] error", name, cfgType) | ||||
| 		return | ||||
| 	} | ||||
| 	if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if err = cfg.Check(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type BaseVisitorConf struct { | ||||
| 	ProxyName      string `json:"proxy_name"` | ||||
| 	ProxyType      string `json:"proxy_type"` | ||||
| 	UseEncryption  bool   `json:"use_encryption"` | ||||
| 	UseCompression bool   `json:"use_compression"` | ||||
| 	Role           string `json:"role"` | ||||
| 	Sk             string `json:"sk"` | ||||
| 	ServerName     string `json:"server_name"` | ||||
| 	BindAddr       string `json:"bind_addr"` | ||||
| 	BindPort       int    `json:"bind_port"` | ||||
| } | ||||
|  | ||||
| func (cfg *BaseVisitorConf) GetBaseInfo() *BaseVisitorConf { | ||||
| 	return cfg | ||||
| } | ||||
|  | ||||
| func (cfg *BaseVisitorConf) compare(cmp *BaseVisitorConf) bool { | ||||
| 	if cfg.ProxyName != cmp.ProxyName || | ||||
| 		cfg.ProxyType != cmp.ProxyType || | ||||
| 		cfg.UseEncryption != cmp.UseEncryption || | ||||
| 		cfg.UseCompression != cmp.UseCompression || | ||||
| 		cfg.Role != cmp.Role || | ||||
| 		cfg.Sk != cmp.Sk || | ||||
| 		cfg.ServerName != cmp.ServerName || | ||||
| 		cfg.BindAddr != cmp.BindAddr || | ||||
| 		cfg.BindPort != cmp.BindPort { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *BaseVisitorConf) check() (err error) { | ||||
| 	if cfg.Role != "visitor" { | ||||
| 		err = fmt.Errorf("invalid role") | ||||
| 		return | ||||
| 	} | ||||
| 	if cfg.BindAddr == "" { | ||||
| 		err = fmt.Errorf("bind_addr shouldn't be empty") | ||||
| 		return | ||||
| 	} | ||||
| 	if cfg.BindPort <= 0 { | ||||
| 		err = fmt.Errorf("bind_port is required") | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *BaseVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	var ( | ||||
| 		tmpStr string | ||||
| 		ok     bool | ||||
| 	) | ||||
| 	cfg.ProxyName = prefix + name | ||||
| 	cfg.ProxyType = section["type"] | ||||
|  | ||||
| 	if tmpStr, ok = section["use_encryption"]; ok && tmpStr == "true" { | ||||
| 		cfg.UseEncryption = true | ||||
| 	} | ||||
| 	if tmpStr, ok = section["use_compression"]; ok && tmpStr == "true" { | ||||
| 		cfg.UseCompression = true | ||||
| 	} | ||||
|  | ||||
| 	cfg.Role = section["role"] | ||||
| 	if cfg.Role != "visitor" { | ||||
| 		return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role) | ||||
| 	} | ||||
| 	cfg.Sk = section["sk"] | ||||
| 	cfg.ServerName = prefix + section["server_name"] | ||||
| 	if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" { | ||||
| 		cfg.BindAddr = "127.0.0.1" | ||||
| 	} | ||||
|  | ||||
| 	if tmpStr, ok = section["bind_port"]; ok { | ||||
| 		if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil { | ||||
| 			return fmt.Errorf("Parse conf error: proxy [%s] bind_port incorrect", name) | ||||
| 		} | ||||
| 	} else { | ||||
| 		return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type StcpVisitorConf struct { | ||||
| 	BaseVisitorConf | ||||
| } | ||||
|  | ||||
| func (cfg *StcpVisitorConf) Compare(cmp VisitorConf) bool { | ||||
| 	cmpConf, ok := cmp.(*StcpVisitorConf) | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *StcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *StcpVisitorConf) Check() (err error) { | ||||
| 	if err = cfg.BaseVisitorConf.check(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type XtcpVisitorConf struct { | ||||
| 	BaseVisitorConf | ||||
| } | ||||
|  | ||||
| func (cfg *XtcpVisitorConf) Compare(cmp VisitorConf) bool { | ||||
| 	cmpConf, ok := cmp.(*XtcpVisitorConf) | ||||
| 	if !ok { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (cfg *XtcpVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { | ||||
| 	if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (cfg *XtcpVisitorConf) Check() (err error) { | ||||
| 	if err = cfg.BaseVisitorConf.check(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| @@ -12,24 +12,21 @@ | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package msg | ||||
| package consts | ||||
| 
 | ||||
| type GeneralRes struct { | ||||
| 	Code int64  `json:"code"` | ||||
| 	Msg  string `json:"msg"` | ||||
| } | ||||
| var ( | ||||
| 	// proxy status | ||||
| 	Idle    string = "idle" | ||||
| 	Working string = "working" | ||||
| 	Closed  string = "closed" | ||||
| 	Online  string = "online" | ||||
| 	Offline string = "offline" | ||||
| 
 | ||||
| // messages between control connection of frpc and frps | ||||
| type ControlReq struct { | ||||
| 	Type          int64  `json:"type"` | ||||
| 	ProxyName     string `json:"proxy_name,omitempty"` | ||||
| 	AuthKey       string `json:"auth_key, omitempty"` | ||||
| 	UseEncryption bool   `json:"use_encryption, omitempty"` | ||||
| 	Timestamp     int64  `json:"timestamp, omitempty"` | ||||
| } | ||||
| 
 | ||||
| type ControlRes struct { | ||||
| 	Type int64  `json:"type"` | ||||
| 	Code int64  `json:"code"` | ||||
| 	Msg  string `json:"msg"` | ||||
| } | ||||
| 	// proxy type | ||||
| 	TcpProxy   string = "tcp" | ||||
| 	UdpProxy   string = "udp" | ||||
| 	HttpProxy  string = "http" | ||||
| 	HttpsProxy string = "https" | ||||
| 	StcpProxy  string = "stcp" | ||||
| 	XtcpProxy  string = "xtcp" | ||||
| ) | ||||
| @@ -12,21 +12,13 @@ | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package consts | ||||
| package errors | ||||
| 
 | ||||
| // server status | ||||
| const ( | ||||
| 	Idle = iota | ||||
| 	Working | ||||
| 	Closed | ||||
| import ( | ||||
| 	"errors" | ||||
| ) | ||||
| 
 | ||||
| // msg type | ||||
| const ( | ||||
| 	NewCtlConn = iota | ||||
| 	NewWorkConn | ||||
| 	NoticeUserConn | ||||
| 	NewCtlConnRes | ||||
| 	HeartbeatReq | ||||
| 	HeartbeatRes | ||||
| var ( | ||||
| 	ErrMsgType   = errors.New("message type error") | ||||
| 	ErrCtlClosed = errors.New("control is closed") | ||||
| ) | ||||
							
								
								
									
										46
									
								
								models/msg/ctl.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								models/msg/ctl.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package msg | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
|  | ||||
| 	jsonMsg "github.com/fatedier/golib/msg/json" | ||||
| ) | ||||
|  | ||||
| type Message = jsonMsg.Message | ||||
|  | ||||
| var ( | ||||
| 	msgCtl *jsonMsg.MsgCtl | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	msgCtl = jsonMsg.NewMsgCtl() | ||||
| 	for typeByte, msg := range msgTypeMap { | ||||
| 		msgCtl.RegisterMsg(typeByte, msg) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ReadMsg(c io.Reader) (msg Message, err error) { | ||||
| 	return msgCtl.ReadMsg(c) | ||||
| } | ||||
|  | ||||
| func ReadMsgInto(c io.Reader, msg Message) (err error) { | ||||
| 	return msgCtl.ReadMsgInto(c, msg) | ||||
| } | ||||
|  | ||||
| func WriteMsg(c io.Writer, msg interface{}) (err error) { | ||||
| 	return msgCtl.WriteMsg(c, msg) | ||||
| } | ||||
							
								
								
									
										174
									
								
								models/msg/msg.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								models/msg/msg.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | ||||
| // Copyright 2016 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package msg | ||||
|  | ||||
| import "net" | ||||
|  | ||||
| const ( | ||||
| 	TypeLogin              = 'o' | ||||
| 	TypeLoginResp          = '1' | ||||
| 	TypeNewProxy           = 'p' | ||||
| 	TypeNewProxyResp       = '2' | ||||
| 	TypeCloseProxy         = 'c' | ||||
| 	TypeNewWorkConn        = 'w' | ||||
| 	TypeReqWorkConn        = 'r' | ||||
| 	TypeStartWorkConn      = 's' | ||||
| 	TypeNewVisitorConn     = 'v' | ||||
| 	TypeNewVisitorConnResp = '3' | ||||
| 	TypePing               = 'h' | ||||
| 	TypePong               = '4' | ||||
| 	TypeUdpPacket          = 'u' | ||||
| 	TypeNatHoleVisitor     = 'i' | ||||
| 	TypeNatHoleClient      = 'n' | ||||
| 	TypeNatHoleResp        = 'm' | ||||
| 	TypeNatHoleSid         = '5' | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	msgTypeMap = map[byte]interface{}{ | ||||
| 		TypeLogin:              Login{}, | ||||
| 		TypeLoginResp:          LoginResp{}, | ||||
| 		TypeNewProxy:           NewProxy{}, | ||||
| 		TypeNewProxyResp:       NewProxyResp{}, | ||||
| 		TypeCloseProxy:         CloseProxy{}, | ||||
| 		TypeNewWorkConn:        NewWorkConn{}, | ||||
| 		TypeReqWorkConn:        ReqWorkConn{}, | ||||
| 		TypeStartWorkConn:      StartWorkConn{}, | ||||
| 		TypeNewVisitorConn:     NewVisitorConn{}, | ||||
| 		TypeNewVisitorConnResp: NewVisitorConnResp{}, | ||||
| 		TypePing:               Ping{}, | ||||
| 		TypePong:               Pong{}, | ||||
| 		TypeUdpPacket:          UdpPacket{}, | ||||
| 		TypeNatHoleVisitor:     NatHoleVisitor{}, | ||||
| 		TypeNatHoleClient:      NatHoleClient{}, | ||||
| 		TypeNatHoleResp:        NatHoleResp{}, | ||||
| 		TypeNatHoleSid:         NatHoleSid{}, | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| // When frpc start, client send this message to login to server. | ||||
| type Login struct { | ||||
| 	Version      string `json:"version"` | ||||
| 	Hostname     string `json:"hostname"` | ||||
| 	Os           string `json:"os"` | ||||
| 	Arch         string `json:"arch"` | ||||
| 	User         string `json:"user"` | ||||
| 	PrivilegeKey string `json:"privilege_key"` | ||||
| 	Timestamp    int64  `json:"timestamp"` | ||||
| 	RunId        string `json:"run_id"` | ||||
|  | ||||
| 	// Some global configures. | ||||
| 	PoolCount int `json:"pool_count"` | ||||
| } | ||||
|  | ||||
| type LoginResp struct { | ||||
| 	Version       string `json:"version"` | ||||
| 	RunId         string `json:"run_id"` | ||||
| 	ServerUdpPort int    `json:"server_udp_port"` | ||||
| 	Error         string `json:"error"` | ||||
| } | ||||
|  | ||||
| // When frpc login success, send this message to frps for running a new proxy. | ||||
| type NewProxy struct { | ||||
| 	ProxyName      string `json:"proxy_name"` | ||||
| 	ProxyType      string `json:"proxy_type"` | ||||
| 	UseEncryption  bool   `json:"use_encryption"` | ||||
| 	UseCompression bool   `json:"use_compression"` | ||||
| 	Group          string `json:"group"` | ||||
| 	GroupKey       string `json:"group_key"` | ||||
|  | ||||
| 	// tcp and udp only | ||||
| 	RemotePort int `json:"remote_port"` | ||||
|  | ||||
| 	// http and https only | ||||
| 	CustomDomains     []string          `json:"custom_domains"` | ||||
| 	SubDomain         string            `json:"subdomain"` | ||||
| 	Locations         []string          `json:"locations"` | ||||
| 	HttpUser          string            `json:"http_user"` | ||||
| 	HttpPwd           string            `json:"http_pwd"` | ||||
| 	HostHeaderRewrite string            `json:"host_header_rewrite"` | ||||
| 	Headers           map[string]string `json:"headers"` | ||||
|  | ||||
| 	// stcp | ||||
| 	Sk string `json:"sk"` | ||||
| } | ||||
|  | ||||
| type NewProxyResp struct { | ||||
| 	ProxyName  string `json:"proxy_name"` | ||||
| 	RemoteAddr string `json:"remote_addr"` | ||||
| 	Error      string `json:"error"` | ||||
| } | ||||
|  | ||||
| type CloseProxy struct { | ||||
| 	ProxyName string `json:"proxy_name"` | ||||
| } | ||||
|  | ||||
| type NewWorkConn struct { | ||||
| 	RunId string `json:"run_id"` | ||||
| } | ||||
|  | ||||
| type ReqWorkConn struct { | ||||
| } | ||||
|  | ||||
| type StartWorkConn struct { | ||||
| 	ProxyName string `json:"proxy_name"` | ||||
| } | ||||
|  | ||||
| type NewVisitorConn struct { | ||||
| 	ProxyName      string `json:"proxy_name"` | ||||
| 	SignKey        string `json:"sign_key"` | ||||
| 	Timestamp      int64  `json:"timestamp"` | ||||
| 	UseEncryption  bool   `json:"use_encryption"` | ||||
| 	UseCompression bool   `json:"use_compression"` | ||||
| } | ||||
|  | ||||
| type NewVisitorConnResp struct { | ||||
| 	ProxyName string `json:"proxy_name"` | ||||
| 	Error     string `json:"error"` | ||||
| } | ||||
|  | ||||
| type Ping struct { | ||||
| } | ||||
|  | ||||
| type Pong struct { | ||||
| } | ||||
|  | ||||
| type UdpPacket struct { | ||||
| 	Content    string       `json:"c"` | ||||
| 	LocalAddr  *net.UDPAddr `json:"l"` | ||||
| 	RemoteAddr *net.UDPAddr `json:"r"` | ||||
| } | ||||
|  | ||||
| type NatHoleVisitor struct { | ||||
| 	ProxyName string `json:"proxy_name"` | ||||
| 	SignKey   string `json:"sign_key"` | ||||
| 	Timestamp int64  `json:"timestamp"` | ||||
| } | ||||
|  | ||||
| type NatHoleClient struct { | ||||
| 	ProxyName string `json:"proxy_name"` | ||||
| 	Sid       string `json:"sid"` | ||||
| } | ||||
|  | ||||
| type NatHoleResp struct { | ||||
| 	Sid         string `json:"sid"` | ||||
| 	VisitorAddr string `json:"visitor_addr"` | ||||
| 	ClientAddr  string `json:"client_addr"` | ||||
| 	Error       string `json:"error"` | ||||
| } | ||||
|  | ||||
| type NatHoleSid struct { | ||||
| 	Sid string `json:"sid"` | ||||
| } | ||||
							
								
								
									
										205
									
								
								models/nathole/nathole.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								models/nathole/nathole.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,205 @@ | ||||
| package nathole | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| 	"github.com/fatedier/frp/utils/util" | ||||
|  | ||||
| 	"github.com/fatedier/golib/errors" | ||||
| 	"github.com/fatedier/golib/pool" | ||||
| ) | ||||
|  | ||||
| // Timeout seconds. | ||||
| var NatHoleTimeout int64 = 10 | ||||
|  | ||||
| type NatHoleController struct { | ||||
| 	listener *net.UDPConn | ||||
|  | ||||
| 	clientCfgs map[string]*NatHoleClientCfg | ||||
| 	sessions   map[string]*NatHoleSession | ||||
|  | ||||
| 	mu sync.RWMutex | ||||
| } | ||||
|  | ||||
| func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error) { | ||||
| 	addr, err := net.ResolveUDPAddr("udp", udpBindAddr) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	lconn, err := net.ListenUDP("udp", addr) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	nc = &NatHoleController{ | ||||
| 		listener:   lconn, | ||||
| 		clientCfgs: make(map[string]*NatHoleClientCfg), | ||||
| 		sessions:   make(map[string]*NatHoleSession), | ||||
| 	} | ||||
| 	return nc, nil | ||||
| } | ||||
|  | ||||
| func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) { | ||||
| 	clientCfg := &NatHoleClientCfg{ | ||||
| 		Name:  name, | ||||
| 		Sk:    sk, | ||||
| 		SidCh: make(chan string), | ||||
| 	} | ||||
| 	nc.mu.Lock() | ||||
| 	nc.clientCfgs[name] = clientCfg | ||||
| 	nc.mu.Unlock() | ||||
| 	return clientCfg.SidCh | ||||
| } | ||||
|  | ||||
| func (nc *NatHoleController) CloseClient(name string) { | ||||
| 	nc.mu.Lock() | ||||
| 	defer nc.mu.Unlock() | ||||
| 	delete(nc.clientCfgs, name) | ||||
| } | ||||
|  | ||||
| func (nc *NatHoleController) Run() { | ||||
| 	for { | ||||
| 		buf := pool.GetBuf(1024) | ||||
| 		n, raddr, err := nc.listener.ReadFromUDP(buf) | ||||
| 		if err != nil { | ||||
| 			log.Trace("nat hole listener read from udp error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		rd := bytes.NewReader(buf[:n]) | ||||
| 		rawMsg, err := msg.ReadMsg(rd) | ||||
| 		if err != nil { | ||||
| 			log.Trace("read nat hole message error: %v", err) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		switch m := rawMsg.(type) { | ||||
| 		case *msg.NatHoleVisitor: | ||||
| 			go nc.HandleVisitor(m, raddr) | ||||
| 		case *msg.NatHoleClient: | ||||
| 			go nc.HandleClient(m, raddr) | ||||
| 		default: | ||||
| 			log.Trace("error nat hole message type") | ||||
| 			continue | ||||
| 		} | ||||
| 		pool.PutBuf(buf) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (nc *NatHoleController) GenSid() string { | ||||
| 	t := time.Now().Unix() | ||||
| 	id, _ := util.RandId() | ||||
| 	return fmt.Sprintf("%d%s", t, id) | ||||
| } | ||||
|  | ||||
| func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) { | ||||
| 	sid := nc.GenSid() | ||||
| 	session := &NatHoleSession{ | ||||
| 		Sid:         sid, | ||||
| 		VisitorAddr: raddr, | ||||
| 		NotifyCh:    make(chan struct{}, 0), | ||||
| 	} | ||||
| 	nc.mu.Lock() | ||||
| 	clientCfg, ok := nc.clientCfgs[m.ProxyName] | ||||
| 	if !ok { | ||||
| 		nc.mu.Unlock() | ||||
| 		errInfo := fmt.Sprintf("xtcp server for [%s] doesn't exist", m.ProxyName) | ||||
| 		log.Debug(errInfo) | ||||
| 		nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr) | ||||
| 		return | ||||
| 	} | ||||
| 	if m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) { | ||||
| 		nc.mu.Unlock() | ||||
| 		errInfo := fmt.Sprintf("xtcp connection of [%s] auth failed", m.ProxyName) | ||||
| 		log.Debug(errInfo) | ||||
| 		nc.listener.WriteToUDP(nc.GenNatHoleResponse(nil, errInfo), raddr) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	nc.sessions[sid] = session | ||||
| 	nc.mu.Unlock() | ||||
| 	log.Trace("handle visitor message, sid [%s]", sid) | ||||
|  | ||||
| 	defer func() { | ||||
| 		nc.mu.Lock() | ||||
| 		delete(nc.sessions, sid) | ||||
| 		nc.mu.Unlock() | ||||
| 	}() | ||||
|  | ||||
| 	err := errors.PanicToError(func() { | ||||
| 		clientCfg.SidCh <- sid | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Wait client connections. | ||||
| 	select { | ||||
| 	case <-session.NotifyCh: | ||||
| 		resp := nc.GenNatHoleResponse(session, "") | ||||
| 		log.Trace("send nat hole response to visitor") | ||||
| 		nc.listener.WriteToUDP(resp, raddr) | ||||
| 	case <-time.After(time.Duration(NatHoleTimeout) * time.Second): | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) { | ||||
| 	nc.mu.RLock() | ||||
| 	session, ok := nc.sessions[m.Sid] | ||||
| 	nc.mu.RUnlock() | ||||
| 	if !ok { | ||||
| 		return | ||||
| 	} | ||||
| 	log.Trace("handle client message, sid [%s]", session.Sid) | ||||
| 	session.ClientAddr = raddr | ||||
| 	session.NotifyCh <- struct{}{} | ||||
|  | ||||
| 	resp := nc.GenNatHoleResponse(session, "") | ||||
| 	log.Trace("send nat hole response to client") | ||||
| 	nc.listener.WriteToUDP(resp, raddr) | ||||
| } | ||||
|  | ||||
| func (nc *NatHoleController) GenNatHoleResponse(session *NatHoleSession, errInfo string) []byte { | ||||
| 	var ( | ||||
| 		sid         string | ||||
| 		visitorAddr string | ||||
| 		clientAddr  string | ||||
| 	) | ||||
| 	if session != nil { | ||||
| 		sid = session.Sid | ||||
| 		visitorAddr = session.VisitorAddr.String() | ||||
| 		clientAddr = session.ClientAddr.String() | ||||
| 	} | ||||
| 	m := &msg.NatHoleResp{ | ||||
| 		Sid:         sid, | ||||
| 		VisitorAddr: visitorAddr, | ||||
| 		ClientAddr:  clientAddr, | ||||
| 		Error:       errInfo, | ||||
| 	} | ||||
| 	b := bytes.NewBuffer(nil) | ||||
| 	err := msg.WriteMsg(b, m) | ||||
| 	if err != nil { | ||||
| 		return []byte("") | ||||
| 	} | ||||
| 	return b.Bytes() | ||||
| } | ||||
|  | ||||
| type NatHoleSession struct { | ||||
| 	Sid         string | ||||
| 	VisitorAddr *net.UDPAddr | ||||
| 	ClientAddr  *net.UDPAddr | ||||
|  | ||||
| 	NotifyCh chan struct{} | ||||
| } | ||||
|  | ||||
| type NatHoleClientCfg struct { | ||||
| 	Name  string | ||||
| 	Sk    string | ||||
| 	SidCh chan string | ||||
| } | ||||
							
								
								
									
										243
									
								
								models/plugin/http_proxy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								models/plugin/http_proxy.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,243 @@ | ||||
| // Copyright 2017 frp team | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package plugin | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"encoding/base64" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	frpIo "github.com/fatedier/golib/io" | ||||
| 	gnet "github.com/fatedier/golib/net" | ||||
| ) | ||||
|  | ||||
| const PluginHttpProxy = "http_proxy" | ||||
|  | ||||
| func init() { | ||||
| 	Register(PluginHttpProxy, NewHttpProxyPlugin) | ||||
| } | ||||
|  | ||||
| type HttpProxy struct { | ||||
| 	l          *Listener | ||||
| 	s          *http.Server | ||||
| 	AuthUser   string | ||||
| 	AuthPasswd string | ||||
| } | ||||
|  | ||||
| func NewHttpProxyPlugin(params map[string]string) (Plugin, error) { | ||||
| 	user := params["plugin_http_user"] | ||||
| 	passwd := params["plugin_http_passwd"] | ||||
| 	listener := NewProxyListener() | ||||
|  | ||||
| 	hp := &HttpProxy{ | ||||
| 		l:          listener, | ||||
| 		AuthUser:   user, | ||||
| 		AuthPasswd: passwd, | ||||
| 	} | ||||
|  | ||||
| 	hp.s = &http.Server{ | ||||
| 		Handler: hp, | ||||
| 	} | ||||
|  | ||||
| 	go hp.s.Serve(listener) | ||||
| 	return hp, nil | ||||
| } | ||||
|  | ||||
| func (hp *HttpProxy) Name() string { | ||||
| 	return PluginHttpProxy | ||||
| } | ||||
|  | ||||
| func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) { | ||||
| 	wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) | ||||
|  | ||||
| 	sc, rd := gnet.NewSharedConn(wrapConn) | ||||
| 	firstBytes := make([]byte, 7) | ||||
| 	_, err := rd.Read(firstBytes) | ||||
| 	if err != nil { | ||||
| 		wrapConn.Close() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if strings.ToUpper(string(firstBytes)) == "CONNECT" { | ||||
| 		bufRd := bufio.NewReader(sc) | ||||
| 		request, err := http.ReadRequest(bufRd) | ||||
| 		if err != nil { | ||||
| 			wrapConn.Close() | ||||
| 			return | ||||
| 		} | ||||
| 		hp.handleConnectReq(request, frpIo.WrapReadWriteCloser(bufRd, wrapConn, wrapConn.Close)) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	hp.l.PutConn(sc) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (hp *HttpProxy) Close() error { | ||||
| 	hp.s.Close() | ||||
| 	hp.l.Close() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (hp *HttpProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { | ||||
| 	if ok := hp.Auth(req); !ok { | ||||
| 		rw.Header().Set("Proxy-Authenticate", "Basic") | ||||
| 		rw.WriteHeader(http.StatusProxyAuthRequired) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if req.Method == http.MethodConnect { | ||||
| 		// deprecated | ||||
| 		// Connect request is handled in Handle function. | ||||
| 		hp.ConnectHandler(rw, req) | ||||
| 	} else { | ||||
| 		hp.HttpHandler(rw, req) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (hp *HttpProxy) HttpHandler(rw http.ResponseWriter, req *http.Request) { | ||||
| 	removeProxyHeaders(req) | ||||
|  | ||||
| 	resp, err := http.DefaultTransport.RoundTrip(req) | ||||
| 	if err != nil { | ||||
| 		http.Error(rw, err.Error(), http.StatusInternalServerError) | ||||
| 		return | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	copyHeaders(rw.Header(), resp.Header) | ||||
| 	rw.WriteHeader(resp.StatusCode) | ||||
|  | ||||
| 	_, err = io.Copy(rw, resp.Body) | ||||
| 	if err != nil && err != io.EOF { | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // deprecated | ||||
| // Hijack needs to SetReadDeadline on the Conn of the request, but if we use stream compression here, | ||||
| // we may always get i/o timeout error. | ||||
| func (hp *HttpProxy) ConnectHandler(rw http.ResponseWriter, req *http.Request) { | ||||
| 	hj, ok := rw.(http.Hijacker) | ||||
| 	if !ok { | ||||
| 		rw.WriteHeader(http.StatusInternalServerError) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	client, _, err := hj.Hijack() | ||||
| 	if err != nil { | ||||
| 		rw.WriteHeader(http.StatusInternalServerError) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	remote, err := net.Dial("tcp", req.URL.Host) | ||||
| 	if err != nil { | ||||
| 		http.Error(rw, "Failed", http.StatusBadRequest) | ||||
| 		client.Close() | ||||
| 		return | ||||
| 	} | ||||
| 	client.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) | ||||
|  | ||||
| 	go frpIo.Join(remote, client) | ||||
| } | ||||
|  | ||||
| func (hp *HttpProxy) Auth(req *http.Request) bool { | ||||
| 	if hp.AuthUser == "" && hp.AuthPasswd == "" { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	s := strings.SplitN(req.Header.Get("Proxy-Authorization"), " ", 2) | ||||
| 	if len(s) != 2 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	b, err := base64.StdEncoding.DecodeString(s[1]) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	pair := strings.SplitN(string(b), ":", 2) | ||||
| 	if len(pair) != 2 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if pair[0] != hp.AuthUser || pair[1] != hp.AuthPasswd { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (hp *HttpProxy) handleConnectReq(req *http.Request, rwc io.ReadWriteCloser) { | ||||
| 	defer rwc.Close() | ||||
| 	if ok := hp.Auth(req); !ok { | ||||
| 		res := getBadResponse() | ||||
| 		res.Write(rwc) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	remote, err := net.Dial("tcp", req.URL.Host) | ||||
| 	if err != nil { | ||||
| 		res := &http.Response{ | ||||
| 			StatusCode: 400, | ||||
| 			Proto:      "HTTP/1.1", | ||||
| 			ProtoMajor: 1, | ||||
| 			ProtoMinor: 1, | ||||
| 		} | ||||
| 		res.Write(rwc) | ||||
| 		return | ||||
| 	} | ||||
| 	rwc.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) | ||||
|  | ||||
| 	frpIo.Join(remote, rwc) | ||||
| } | ||||
|  | ||||
| func copyHeaders(dst, src http.Header) { | ||||
| 	for key, values := range src { | ||||
| 		for _, value := range values { | ||||
| 			dst.Add(key, value) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func removeProxyHeaders(req *http.Request) { | ||||
| 	req.RequestURI = "" | ||||
| 	req.Header.Del("Proxy-Connection") | ||||
| 	req.Header.Del("Connection") | ||||
| 	req.Header.Del("Proxy-Authenticate") | ||||
| 	req.Header.Del("Proxy-Authorization") | ||||
| 	req.Header.Del("TE") | ||||
| 	req.Header.Del("Trailers") | ||||
| 	req.Header.Del("Transfer-Encoding") | ||||
| 	req.Header.Del("Upgrade") | ||||
| } | ||||
|  | ||||
| func getBadResponse() *http.Response { | ||||
| 	header := make(map[string][]string) | ||||
| 	header["Proxy-Authenticate"] = []string{"Basic"} | ||||
| 	res := &http.Response{ | ||||
| 		Status:     "407 Not authorized", | ||||
| 		StatusCode: 407, | ||||
| 		Proto:      "HTTP/1.1", | ||||
| 		ProtoMajor: 1, | ||||
| 		ProtoMinor: 1, | ||||
| 		Header:     header, | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
							
								
								
									
										92
									
								
								models/plugin/plugin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								models/plugin/plugin.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package plugin | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"sync" | ||||
|  | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	"github.com/fatedier/golib/errors" | ||||
| ) | ||||
|  | ||||
| // Creators is used for create plugins to handle connections. | ||||
| var creators = make(map[string]CreatorFn) | ||||
|  | ||||
| // params has prefix "plugin_" | ||||
| type CreatorFn func(params map[string]string) (Plugin, error) | ||||
|  | ||||
| func Register(name string, fn CreatorFn) { | ||||
| 	creators[name] = fn | ||||
| } | ||||
|  | ||||
| func Create(name string, params map[string]string) (p Plugin, err error) { | ||||
| 	if fn, ok := creators[name]; ok { | ||||
| 		p, err = fn(params) | ||||
| 	} else { | ||||
| 		err = fmt.Errorf("plugin [%s] is not registered", name) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type Plugin interface { | ||||
| 	Name() string | ||||
| 	Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) | ||||
| 	Close() error | ||||
| } | ||||
|  | ||||
| type Listener struct { | ||||
| 	conns  chan net.Conn | ||||
| 	closed bool | ||||
| 	mu     sync.Mutex | ||||
| } | ||||
|  | ||||
| func NewProxyListener() *Listener { | ||||
| 	return &Listener{ | ||||
| 		conns: make(chan net.Conn, 64), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (l *Listener) Accept() (net.Conn, error) { | ||||
| 	conn, ok := <-l.conns | ||||
| 	if !ok { | ||||
| 		return nil, fmt.Errorf("listener closed") | ||||
| 	} | ||||
| 	return conn, nil | ||||
| } | ||||
|  | ||||
| func (l *Listener) PutConn(conn net.Conn) error { | ||||
| 	err := errors.PanicToError(func() { | ||||
| 		l.conns <- conn | ||||
| 	}) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (l *Listener) Close() error { | ||||
| 	l.mu.Lock() | ||||
| 	defer l.mu.Unlock() | ||||
| 	if !l.closed { | ||||
| 		close(l.conns) | ||||
| 		l.closed = true | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (l *Listener) Addr() net.Addr { | ||||
| 	return (*net.TCPAddr)(nil) | ||||
| } | ||||
							
								
								
									
										68
									
								
								models/plugin/socks5.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								models/plugin/socks5.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package plugin | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
|  | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	gosocks5 "github.com/armon/go-socks5" | ||||
| ) | ||||
|  | ||||
| const PluginSocks5 = "socks5" | ||||
|  | ||||
| func init() { | ||||
| 	Register(PluginSocks5, NewSocks5Plugin) | ||||
| } | ||||
|  | ||||
| type Socks5Plugin struct { | ||||
| 	Server *gosocks5.Server | ||||
|  | ||||
| 	user   string | ||||
| 	passwd string | ||||
| } | ||||
|  | ||||
| func NewSocks5Plugin(params map[string]string) (p Plugin, err error) { | ||||
| 	user := params["plugin_user"] | ||||
| 	passwd := params["plugin_passwd"] | ||||
|  | ||||
| 	cfg := &gosocks5.Config{ | ||||
| 		Logger: log.New(ioutil.Discard, "", log.LstdFlags), | ||||
| 	} | ||||
| 	if user != "" || passwd != "" { | ||||
| 		cfg.Credentials = gosocks5.StaticCredentials(map[string]string{user: passwd}) | ||||
| 	} | ||||
| 	sp := &Socks5Plugin{} | ||||
| 	sp.Server, err = gosocks5.New(cfg) | ||||
| 	p = sp | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) { | ||||
| 	defer conn.Close() | ||||
| 	wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) | ||||
| 	sp.Server.ServeConn(wrapConn) | ||||
| } | ||||
|  | ||||
| func (sp *Socks5Plugin) Name() string { | ||||
| 	return PluginSocks5 | ||||
| } | ||||
|  | ||||
| func (sp *Socks5Plugin) Close() error { | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										88
									
								
								models/plugin/static_file.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								models/plugin/static_file.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package plugin | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"net/http" | ||||
|  | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	"github.com/gorilla/mux" | ||||
| ) | ||||
|  | ||||
| const PluginStaticFile = "static_file" | ||||
|  | ||||
| func init() { | ||||
| 	Register(PluginStaticFile, NewStaticFilePlugin) | ||||
| } | ||||
|  | ||||
| type StaticFilePlugin struct { | ||||
| 	localPath   string | ||||
| 	stripPrefix string | ||||
| 	httpUser    string | ||||
| 	httpPasswd  string | ||||
|  | ||||
| 	l *Listener | ||||
| 	s *http.Server | ||||
| } | ||||
|  | ||||
| func NewStaticFilePlugin(params map[string]string) (Plugin, error) { | ||||
| 	localPath := params["plugin_local_path"] | ||||
| 	stripPrefix := params["plugin_strip_prefix"] | ||||
| 	httpUser := params["plugin_http_user"] | ||||
| 	httpPasswd := params["plugin_http_passwd"] | ||||
|  | ||||
| 	listener := NewProxyListener() | ||||
|  | ||||
| 	sp := &StaticFilePlugin{ | ||||
| 		localPath:   localPath, | ||||
| 		stripPrefix: stripPrefix, | ||||
| 		httpUser:    httpUser, | ||||
| 		httpPasswd:  httpPasswd, | ||||
|  | ||||
| 		l: listener, | ||||
| 	} | ||||
| 	var prefix string | ||||
| 	if stripPrefix != "" { | ||||
| 		prefix = "/" + stripPrefix + "/" | ||||
| 	} else { | ||||
| 		prefix = "/" | ||||
| 	} | ||||
|  | ||||
| 	router := mux.NewRouter() | ||||
| 	router.Use(frpNet.NewHttpAuthMiddleware(httpUser, httpPasswd).Middleware) | ||||
| 	router.PathPrefix(prefix).Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix(prefix, http.FileServer(http.Dir(localPath))))).Methods("GET") | ||||
| 	sp.s = &http.Server{ | ||||
| 		Handler: router, | ||||
| 	} | ||||
| 	go sp.s.Serve(listener) | ||||
| 	return sp, nil | ||||
| } | ||||
|  | ||||
| func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) { | ||||
| 	wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn) | ||||
| 	sp.l.PutConn(wrapConn) | ||||
| } | ||||
|  | ||||
| func (sp *StaticFilePlugin) Name() string { | ||||
| 	return PluginStaticFile | ||||
| } | ||||
|  | ||||
| func (sp *StaticFilePlugin) Close() error { | ||||
| 	sp.s.Close() | ||||
| 	sp.l.Close() | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										71
									
								
								models/plugin/unix_domain_socket.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								models/plugin/unix_domain_socket.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package plugin | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
|  | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	frpIo "github.com/fatedier/golib/io" | ||||
| ) | ||||
|  | ||||
| const PluginUnixDomainSocket = "unix_domain_socket" | ||||
|  | ||||
| func init() { | ||||
| 	Register(PluginUnixDomainSocket, NewUnixDomainSocketPlugin) | ||||
| } | ||||
|  | ||||
| type UnixDomainSocketPlugin struct { | ||||
| 	UnixAddr *net.UnixAddr | ||||
| } | ||||
|  | ||||
| func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) { | ||||
| 	unixPath, ok := params["plugin_unix_path"] | ||||
| 	if !ok { | ||||
| 		err = fmt.Errorf("plugin_unix_path not found") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	unixAddr, errRet := net.ResolveUnixAddr("unix", unixPath) | ||||
| 	if errRet != nil { | ||||
| 		err = errRet | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	p = &UnixDomainSocketPlugin{ | ||||
| 		UnixAddr: unixAddr, | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) { | ||||
| 	localConn, err := net.DialUnix("unix", nil, uds.UnixAddr) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	frpIo.Join(localConn, conn) | ||||
| } | ||||
|  | ||||
| func (uds *UnixDomainSocketPlugin) Name() string { | ||||
| 	return PluginUnixDomainSocket | ||||
| } | ||||
|  | ||||
| func (uds *UnixDomainSocketPlugin) Close() error { | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										136
									
								
								models/proto/udp/udp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								models/proto/udp/udp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package udp | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"net" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
|  | ||||
| 	"github.com/fatedier/golib/errors" | ||||
| 	"github.com/fatedier/golib/pool" | ||||
| ) | ||||
|  | ||||
| func NewUdpPacket(buf []byte, laddr, raddr *net.UDPAddr) *msg.UdpPacket { | ||||
| 	return &msg.UdpPacket{ | ||||
| 		Content:    base64.StdEncoding.EncodeToString(buf), | ||||
| 		LocalAddr:  laddr, | ||||
| 		RemoteAddr: raddr, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func GetContent(m *msg.UdpPacket) (buf []byte, err error) { | ||||
| 	buf, err = base64.StdEncoding.DecodeString(m.Content) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh chan<- *msg.UdpPacket) { | ||||
| 	// read | ||||
| 	go func() { | ||||
| 		for udpMsg := range readCh { | ||||
| 			buf, err := GetContent(udpMsg) | ||||
| 			if err != nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			udpConn.WriteToUDP(buf, udpMsg.RemoteAddr) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	// write | ||||
| 	buf := pool.GetBuf(1500) | ||||
| 	defer pool.PutBuf(buf) | ||||
| 	for { | ||||
| 		n, remoteAddr, err := udpConn.ReadFromUDP(buf) | ||||
| 		if err != nil { | ||||
| 			udpConn.Close() | ||||
| 			return | ||||
| 		} | ||||
| 		// buf[:n] will be encoded to string, so the bytes can be reused | ||||
| 		udpMsg := NewUdpPacket(buf[:n], nil, remoteAddr) | ||||
| 		select { | ||||
| 		case sendCh <- udpMsg: | ||||
| 		default: | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) { | ||||
| 	var ( | ||||
| 		mu sync.RWMutex | ||||
| 	) | ||||
| 	udpConnMap := make(map[string]*net.UDPConn) | ||||
|  | ||||
| 	// read from dstAddr and write to sendCh | ||||
| 	writerFn := func(raddr *net.UDPAddr, udpConn *net.UDPConn) { | ||||
| 		addr := raddr.String() | ||||
| 		defer func() { | ||||
| 			mu.Lock() | ||||
| 			delete(udpConnMap, addr) | ||||
| 			mu.Unlock() | ||||
| 			udpConn.Close() | ||||
| 		}() | ||||
|  | ||||
| 		buf := pool.GetBuf(1500) | ||||
| 		for { | ||||
| 			udpConn.SetReadDeadline(time.Now().Add(30 * time.Second)) | ||||
| 			n, _, err := udpConn.ReadFromUDP(buf) | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			udpMsg := NewUdpPacket(buf[:n], nil, raddr) | ||||
| 			if err = errors.PanicToError(func() { | ||||
| 				select { | ||||
| 				case sendCh <- udpMsg: | ||||
| 				default: | ||||
| 				} | ||||
| 			}); err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// read from readCh | ||||
| 	go func() { | ||||
| 		for udpMsg := range readCh { | ||||
| 			buf, err := GetContent(udpMsg) | ||||
| 			if err != nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			mu.Lock() | ||||
| 			udpConn, ok := udpConnMap[udpMsg.RemoteAddr.String()] | ||||
| 			if !ok { | ||||
| 				udpConn, err = net.DialUDP("udp", nil, dstAddr) | ||||
| 				if err != nil { | ||||
| 					continue | ||||
| 				} | ||||
| 				udpConnMap[udpMsg.RemoteAddr.String()] = udpConn | ||||
| 			} | ||||
| 			mu.Unlock() | ||||
|  | ||||
| 			_, err = udpConn.Write(buf) | ||||
| 			if err != nil { | ||||
| 				udpConn.Close() | ||||
| 			} | ||||
|  | ||||
| 			if !ok { | ||||
| 				go writerFn(udpMsg.RemoteAddr, udpConn) | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| } | ||||
							
								
								
									
										18
									
								
								models/proto/udp/udp_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								models/proto/udp/udp_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| package udp | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestUdpPacket(t *testing.T) { | ||||
| 	assert := assert.New(t) | ||||
|  | ||||
| 	buf := []byte("hello world") | ||||
| 	udpMsg := NewUdpPacket(buf, nil, nil) | ||||
|  | ||||
| 	newBuf, err := GetContent(udpMsg) | ||||
| 	assert.NoError(err) | ||||
| 	assert.EqualValues(buf, newBuf) | ||||
| } | ||||
							
								
								
									
										59
									
								
								package.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										59
									
								
								package.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| # compile for version | ||||
| make | ||||
| if [ $? -ne 0 ]; then | ||||
|     echo "make error" | ||||
|     exit 1 | ||||
| fi | ||||
|  | ||||
| frp_version=`./bin/frps --version` | ||||
| echo "build version: $frp_version" | ||||
|  | ||||
| # cross_compiles | ||||
| make -f ./Makefile.cross-compiles | ||||
|  | ||||
| rm -rf ./packages | ||||
| mkdir ./packages | ||||
|  | ||||
| os_all='linux windows darwin freebsd' | ||||
| arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle' | ||||
|  | ||||
| for os in $os_all; do | ||||
|     for arch in $arch_all; do | ||||
|         frp_dir_name="frp_${frp_version}_${os}_${arch}" | ||||
|         frp_path="./packages/frp_${frp_version}_${os}_${arch}" | ||||
|  | ||||
|         if [ "x${os}" = x"windows" ]; then | ||||
|             if [ ! -f "./frpc_${os}_${arch}.exe" ]; then | ||||
|                 continue | ||||
|             fi | ||||
|             if [ ! -f "./frps_${os}_${arch}.exe" ]; then | ||||
|                 continue | ||||
|             fi | ||||
|             mkdir ${frp_path} | ||||
|             mv ./frpc_${os}_${arch}.exe ${frp_path}/frpc.exe | ||||
|             mv ./frps_${os}_${arch}.exe ${frp_path}/frps.exe | ||||
|         else | ||||
|             if [ ! -f "./frpc_${os}_${arch}" ]; then | ||||
|                 continue | ||||
|             fi | ||||
|             if [ ! -f "./frps_${os}_${arch}" ]; then | ||||
|                 continue | ||||
|             fi | ||||
|             mkdir ${frp_path} | ||||
|             mv ./frpc_${os}_${arch} ${frp_path}/frpc | ||||
|             mv ./frps_${os}_${arch} ${frp_path}/frps | ||||
|         fi   | ||||
|         cp ./LICENSE ${frp_path} | ||||
|         cp ./conf/* ${frp_path} | ||||
|  | ||||
|         # packages | ||||
|         cd ./packages | ||||
|         if [ "x${os}" = x"windows" ]; then | ||||
|             zip -rq ${frp_dir_name}.zip ${frp_dir_name} | ||||
|         else | ||||
|             tar -zcf ${frp_dir_name}.tar.gz ${frp_dir_name} | ||||
|         fi   | ||||
|         cd .. | ||||
|         rm -rf ${frp_path} | ||||
|     done | ||||
| done | ||||
							
								
								
									
										493
									
								
								server/control.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										493
									
								
								server/control.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,493 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package server | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"runtime/debug" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/consts" | ||||
| 	frpErr "github.com/fatedier/frp/models/errors" | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
| 	"github.com/fatedier/frp/server/controller" | ||||
| 	"github.com/fatedier/frp/server/proxy" | ||||
| 	"github.com/fatedier/frp/server/stats" | ||||
| 	"github.com/fatedier/frp/utils/net" | ||||
| 	"github.com/fatedier/frp/utils/version" | ||||
|  | ||||
| 	"github.com/fatedier/golib/control/shutdown" | ||||
| 	"github.com/fatedier/golib/crypto" | ||||
| 	"github.com/fatedier/golib/errors" | ||||
| ) | ||||
|  | ||||
| type ControlManager struct { | ||||
| 	// controls indexed by run id | ||||
| 	ctlsByRunId map[string]*Control | ||||
|  | ||||
| 	mu sync.RWMutex | ||||
| } | ||||
|  | ||||
| func NewControlManager() *ControlManager { | ||||
| 	return &ControlManager{ | ||||
| 		ctlsByRunId: make(map[string]*Control), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (cm *ControlManager) Add(runId string, ctl *Control) (oldCtl *Control) { | ||||
| 	cm.mu.Lock() | ||||
| 	defer cm.mu.Unlock() | ||||
|  | ||||
| 	oldCtl, ok := cm.ctlsByRunId[runId] | ||||
| 	if ok { | ||||
| 		oldCtl.Replaced(ctl) | ||||
| 	} | ||||
| 	cm.ctlsByRunId[runId] = ctl | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // we should make sure if it's the same control to prevent delete a new one | ||||
| func (cm *ControlManager) Del(runId string, ctl *Control) { | ||||
| 	cm.mu.Lock() | ||||
| 	defer cm.mu.Unlock() | ||||
| 	if c, ok := cm.ctlsByRunId[runId]; ok && c == ctl { | ||||
| 		delete(cm.ctlsByRunId, runId) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) { | ||||
| 	cm.mu.RLock() | ||||
| 	defer cm.mu.RUnlock() | ||||
| 	ctl, ok = cm.ctlsByRunId[runId] | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type Control struct { | ||||
| 	// all resource managers and controllers | ||||
| 	rc *controller.ResourceController | ||||
|  | ||||
| 	// proxy manager | ||||
| 	pxyManager *proxy.ProxyManager | ||||
|  | ||||
| 	// stats collector to store stats info of clients and proxies | ||||
| 	statsCollector stats.Collector | ||||
|  | ||||
| 	// login message | ||||
| 	loginMsg *msg.Login | ||||
|  | ||||
| 	// control connection | ||||
| 	conn net.Conn | ||||
|  | ||||
| 	// put a message in this channel to send it over control connection to client | ||||
| 	sendCh chan (msg.Message) | ||||
|  | ||||
| 	// read from this channel to get the next message sent by client | ||||
| 	readCh chan (msg.Message) | ||||
|  | ||||
| 	// work connections | ||||
| 	workConnCh chan net.Conn | ||||
|  | ||||
| 	// proxies in one client | ||||
| 	proxies map[string]proxy.Proxy | ||||
|  | ||||
| 	// pool count | ||||
| 	poolCount int | ||||
|  | ||||
| 	// ports used, for limitations | ||||
| 	portsUsedNum int | ||||
|  | ||||
| 	// last time got the Ping message | ||||
| 	lastPing time.Time | ||||
|  | ||||
| 	// A new run id will be generated when a new client login. | ||||
| 	// If run id got from login message has same run id, it means it's the same client, so we can | ||||
| 	// replace old controller instantly. | ||||
| 	runId string | ||||
|  | ||||
| 	// control status | ||||
| 	status string | ||||
|  | ||||
| 	readerShutdown  *shutdown.Shutdown | ||||
| 	writerShutdown  *shutdown.Shutdown | ||||
| 	managerShutdown *shutdown.Shutdown | ||||
| 	allShutdown     *shutdown.Shutdown | ||||
|  | ||||
| 	mu sync.RWMutex | ||||
| } | ||||
|  | ||||
| func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager, | ||||
| 	statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login) *Control { | ||||
|  | ||||
| 	return &Control{ | ||||
| 		rc:              rc, | ||||
| 		pxyManager:      pxyManager, | ||||
| 		statsCollector:  statsCollector, | ||||
| 		conn:            ctlConn, | ||||
| 		loginMsg:        loginMsg, | ||||
| 		sendCh:          make(chan msg.Message, 10), | ||||
| 		readCh:          make(chan msg.Message, 10), | ||||
| 		workConnCh:      make(chan net.Conn, loginMsg.PoolCount+10), | ||||
| 		proxies:         make(map[string]proxy.Proxy), | ||||
| 		poolCount:       loginMsg.PoolCount, | ||||
| 		portsUsedNum:    0, | ||||
| 		lastPing:        time.Now(), | ||||
| 		runId:           loginMsg.RunId, | ||||
| 		status:          consts.Working, | ||||
| 		readerShutdown:  shutdown.New(), | ||||
| 		writerShutdown:  shutdown.New(), | ||||
| 		managerShutdown: shutdown.New(), | ||||
| 		allShutdown:     shutdown.New(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Start send a login success message to client and start working. | ||||
| func (ctl *Control) Start() { | ||||
| 	loginRespMsg := &msg.LoginResp{ | ||||
| 		Version:       version.Full(), | ||||
| 		RunId:         ctl.runId, | ||||
| 		ServerUdpPort: g.GlbServerCfg.BindUdpPort, | ||||
| 		Error:         "", | ||||
| 	} | ||||
| 	msg.WriteMsg(ctl.conn, loginRespMsg) | ||||
|  | ||||
| 	go ctl.writer() | ||||
| 	for i := 0; i < ctl.poolCount; i++ { | ||||
| 		ctl.sendCh <- &msg.ReqWorkConn{} | ||||
| 	} | ||||
|  | ||||
| 	go ctl.manager() | ||||
| 	go ctl.reader() | ||||
| 	go ctl.stoper() | ||||
| } | ||||
|  | ||||
| func (ctl *Control) RegisterWorkConn(conn net.Conn) { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			ctl.conn.Error("panic error: %v", err) | ||||
| 			ctl.conn.Error(string(debug.Stack())) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	select { | ||||
| 	case ctl.workConnCh <- conn: | ||||
| 		ctl.conn.Debug("new work connection registered") | ||||
| 	default: | ||||
| 		ctl.conn.Debug("work connection pool is full, discarding") | ||||
| 		conn.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // When frps get one user connection, we get one work connection from the pool and return it. | ||||
| // If no workConn available in the pool, send message to frpc to get one or more | ||||
| // and wait until it is available. | ||||
| // return an error if wait timeout | ||||
| func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			ctl.conn.Error("panic error: %v", err) | ||||
| 			ctl.conn.Error(string(debug.Stack())) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	var ok bool | ||||
| 	// get a work connection from the pool | ||||
| 	select { | ||||
| 	case workConn, ok = <-ctl.workConnCh: | ||||
| 		if !ok { | ||||
| 			err = frpErr.ErrCtlClosed | ||||
| 			return | ||||
| 		} | ||||
| 		ctl.conn.Debug("get work connection from pool") | ||||
| 	default: | ||||
| 		// no work connections available in the poll, send message to frpc to get more | ||||
| 		err = errors.PanicToError(func() { | ||||
| 			ctl.sendCh <- &msg.ReqWorkConn{} | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			ctl.conn.Error("%v", err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		select { | ||||
| 		case workConn, ok = <-ctl.workConnCh: | ||||
| 			if !ok { | ||||
| 				err = frpErr.ErrCtlClosed | ||||
| 				ctl.conn.Warn("no work connections avaiable, %v", err) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 		case <-time.After(time.Duration(g.GlbServerCfg.UserConnTimeout) * time.Second): | ||||
| 			err = fmt.Errorf("timeout trying to get work connection") | ||||
| 			ctl.conn.Warn("%v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// When we get a work connection from pool, replace it with a new one. | ||||
| 	errors.PanicToError(func() { | ||||
| 		ctl.sendCh <- &msg.ReqWorkConn{} | ||||
| 	}) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (ctl *Control) Replaced(newCtl *Control) { | ||||
| 	ctl.conn.Info("Replaced by client [%s]", newCtl.runId) | ||||
| 	ctl.runId = "" | ||||
| 	ctl.allShutdown.Start() | ||||
| } | ||||
|  | ||||
| func (ctl *Control) writer() { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			ctl.conn.Error("panic error: %v", err) | ||||
| 			ctl.conn.Error(string(debug.Stack())) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	defer ctl.allShutdown.Start() | ||||
| 	defer ctl.writerShutdown.Done() | ||||
|  | ||||
| 	encWriter, err := crypto.NewWriter(ctl.conn, []byte(g.GlbServerCfg.Token)) | ||||
| 	if err != nil { | ||||
| 		ctl.conn.Error("crypto new writer error: %v", err) | ||||
| 		ctl.allShutdown.Start() | ||||
| 		return | ||||
| 	} | ||||
| 	for { | ||||
| 		if m, ok := <-ctl.sendCh; !ok { | ||||
| 			ctl.conn.Info("control writer is closing") | ||||
| 			return | ||||
| 		} else { | ||||
| 			if err := msg.WriteMsg(encWriter, m); err != nil { | ||||
| 				ctl.conn.Warn("write message to control connection error: %v", err) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (ctl *Control) reader() { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			ctl.conn.Error("panic error: %v", err) | ||||
| 			ctl.conn.Error(string(debug.Stack())) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	defer ctl.allShutdown.Start() | ||||
| 	defer ctl.readerShutdown.Done() | ||||
|  | ||||
| 	encReader := crypto.NewReader(ctl.conn, []byte(g.GlbServerCfg.Token)) | ||||
| 	for { | ||||
| 		if m, err := msg.ReadMsg(encReader); err != nil { | ||||
| 			if err == io.EOF { | ||||
| 				ctl.conn.Debug("control connection closed") | ||||
| 				return | ||||
| 			} else { | ||||
| 				ctl.conn.Warn("read error: %v", err) | ||||
| 				return | ||||
| 			} | ||||
| 		} else { | ||||
| 			ctl.readCh <- m | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (ctl *Control) stoper() { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			ctl.conn.Error("panic error: %v", err) | ||||
| 			ctl.conn.Error(string(debug.Stack())) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	ctl.allShutdown.WaitStart() | ||||
|  | ||||
| 	close(ctl.readCh) | ||||
| 	ctl.managerShutdown.WaitDone() | ||||
|  | ||||
| 	close(ctl.sendCh) | ||||
| 	ctl.writerShutdown.WaitDone() | ||||
|  | ||||
| 	ctl.conn.Close() | ||||
| 	ctl.readerShutdown.WaitDone() | ||||
|  | ||||
| 	ctl.mu.Lock() | ||||
| 	defer ctl.mu.Unlock() | ||||
|  | ||||
| 	close(ctl.workConnCh) | ||||
| 	for workConn := range ctl.workConnCh { | ||||
| 		workConn.Close() | ||||
| 	} | ||||
|  | ||||
| 	for _, pxy := range ctl.proxies { | ||||
| 		pxy.Close() | ||||
| 		ctl.pxyManager.Del(pxy.GetName()) | ||||
| 		ctl.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseProxyPayload{ | ||||
| 			Name:      pxy.GetName(), | ||||
| 			ProxyType: pxy.GetConf().GetBaseInfo().ProxyType, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	ctl.allShutdown.Done() | ||||
| 	ctl.conn.Info("client exit success") | ||||
|  | ||||
| 	ctl.statsCollector.Mark(stats.TypeCloseClient, &stats.CloseClientPayload{}) | ||||
| } | ||||
|  | ||||
| // block until Control closed | ||||
| func (ctl *Control) WaitClosed() { | ||||
| 	ctl.allShutdown.WaitDone() | ||||
| } | ||||
|  | ||||
| func (ctl *Control) manager() { | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			ctl.conn.Error("panic error: %v", err) | ||||
| 			ctl.conn.Error(string(debug.Stack())) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	defer ctl.allShutdown.Start() | ||||
| 	defer ctl.managerShutdown.Done() | ||||
|  | ||||
| 	heartbeat := time.NewTicker(time.Second) | ||||
| 	defer heartbeat.Stop() | ||||
|  | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-heartbeat.C: | ||||
| 			if time.Since(ctl.lastPing) > time.Duration(g.GlbServerCfg.HeartBeatTimeout)*time.Second { | ||||
| 				ctl.conn.Warn("heartbeat timeout") | ||||
| 				return | ||||
| 			} | ||||
| 		case rawMsg, ok := <-ctl.readCh: | ||||
| 			if !ok { | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			switch m := rawMsg.(type) { | ||||
| 			case *msg.NewProxy: | ||||
| 				// register proxy in this control | ||||
| 				remoteAddr, err := ctl.RegisterProxy(m) | ||||
| 				resp := &msg.NewProxyResp{ | ||||
| 					ProxyName: m.ProxyName, | ||||
| 				} | ||||
| 				if err != nil { | ||||
| 					resp.Error = err.Error() | ||||
| 					ctl.conn.Warn("new proxy [%s] error: %v", m.ProxyName, err) | ||||
| 				} else { | ||||
| 					resp.RemoteAddr = remoteAddr | ||||
| 					ctl.conn.Info("new proxy [%s] success", m.ProxyName) | ||||
| 					ctl.statsCollector.Mark(stats.TypeNewProxy, &stats.NewProxyPayload{ | ||||
| 						Name:      m.ProxyName, | ||||
| 						ProxyType: m.ProxyType, | ||||
| 					}) | ||||
| 				} | ||||
| 				ctl.sendCh <- resp | ||||
| 			case *msg.CloseProxy: | ||||
| 				ctl.CloseProxy(m) | ||||
| 				ctl.conn.Info("close proxy [%s] success", m.ProxyName) | ||||
| 			case *msg.Ping: | ||||
| 				ctl.lastPing = time.Now() | ||||
| 				ctl.conn.Debug("receive heartbeat") | ||||
| 				ctl.sendCh <- &msg.Pong{} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err error) { | ||||
| 	var pxyConf config.ProxyConf | ||||
| 	// Load configures from NewProxy message and check. | ||||
| 	pxyConf, err = config.NewProxyConfFromMsg(pxyMsg) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// NewProxy will return a interface Proxy. | ||||
| 	// In fact it create different proxies by different proxy type, we just call run() here. | ||||
| 	pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf) | ||||
| 	if err != nil { | ||||
| 		return remoteAddr, err | ||||
| 	} | ||||
|  | ||||
| 	// Check ports used number in each client | ||||
| 	if g.GlbServerCfg.MaxPortsPerClient > 0 { | ||||
| 		ctl.mu.Lock() | ||||
| 		if ctl.portsUsedNum+pxy.GetUsedPortsNum() > int(g.GlbServerCfg.MaxPortsPerClient) { | ||||
| 			ctl.mu.Unlock() | ||||
| 			err = fmt.Errorf("exceed the max_ports_per_client") | ||||
| 			return | ||||
| 		} | ||||
| 		ctl.portsUsedNum = ctl.portsUsedNum + pxy.GetUsedPortsNum() | ||||
| 		ctl.mu.Unlock() | ||||
|  | ||||
| 		defer func() { | ||||
| 			if err != nil { | ||||
| 				ctl.mu.Lock() | ||||
| 				ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum() | ||||
| 				ctl.mu.Unlock() | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	remoteAddr, err = pxy.Run() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			pxy.Close() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	err = ctl.pxyManager.Add(pxyMsg.ProxyName, pxy) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ctl.mu.Lock() | ||||
| 	ctl.proxies[pxy.GetName()] = pxy | ||||
| 	ctl.mu.Unlock() | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (ctl *Control) CloseProxy(closeMsg *msg.CloseProxy) (err error) { | ||||
| 	ctl.mu.Lock() | ||||
| 	pxy, ok := ctl.proxies[closeMsg.ProxyName] | ||||
| 	if !ok { | ||||
| 		ctl.mu.Unlock() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if g.GlbServerCfg.MaxPortsPerClient > 0 { | ||||
| 		ctl.portsUsedNum = ctl.portsUsedNum - pxy.GetUsedPortsNum() | ||||
| 	} | ||||
| 	pxy.Close() | ||||
| 	ctl.pxyManager.Del(pxy.GetName()) | ||||
| 	delete(ctl.proxies, closeMsg.ProxyName) | ||||
| 	ctl.mu.Unlock() | ||||
|  | ||||
| 	ctl.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseProxyPayload{ | ||||
| 		Name:      pxy.GetName(), | ||||
| 		ProxyType: pxy.GetConf().GetBaseInfo().ProxyType, | ||||
| 	}) | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										46
									
								
								server/controller/resource.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								server/controller/resource.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| // Copyright 2019 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package controller | ||||
|  | ||||
| import ( | ||||
| 	"github.com/fatedier/frp/models/nathole" | ||||
| 	"github.com/fatedier/frp/server/group" | ||||
| 	"github.com/fatedier/frp/server/ports" | ||||
| 	"github.com/fatedier/frp/utils/vhost" | ||||
| ) | ||||
|  | ||||
| // All resource managers and controllers | ||||
| type ResourceController struct { | ||||
| 	// Manage all visitor listeners | ||||
| 	VisitorManager *VisitorManager | ||||
|  | ||||
| 	// Tcp Group Controller | ||||
| 	TcpGroupCtl *group.TcpGroupCtl | ||||
|  | ||||
| 	// Manage all tcp ports | ||||
| 	TcpPortManager *ports.PortManager | ||||
|  | ||||
| 	// Manage all udp ports | ||||
| 	UdpPortManager *ports.PortManager | ||||
|  | ||||
| 	// For http proxies, forwarding http requests | ||||
| 	HttpReverseProxy *vhost.HttpReverseProxy | ||||
|  | ||||
| 	// For https proxies, route requests to different clients by hostname and other infomation | ||||
| 	VhostHttpsMuxer *vhost.HttpsMuxer | ||||
|  | ||||
| 	// Controller for nat hole connections | ||||
| 	NatHoleController *nathole.NatHoleController | ||||
| } | ||||
							
								
								
									
										95
									
								
								server/controller/visitor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								server/controller/visitor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| // Copyright 2019 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package controller | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"sync" | ||||
|  | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
| 	"github.com/fatedier/frp/utils/util" | ||||
|  | ||||
| 	frpIo "github.com/fatedier/golib/io" | ||||
| ) | ||||
|  | ||||
| // Manager for visitor listeners. | ||||
| type VisitorManager struct { | ||||
| 	visitorListeners map[string]*frpNet.CustomListener | ||||
| 	skMap            map[string]string | ||||
|  | ||||
| 	mu sync.RWMutex | ||||
| } | ||||
|  | ||||
| func NewVisitorManager() *VisitorManager { | ||||
| 	return &VisitorManager{ | ||||
| 		visitorListeners: make(map[string]*frpNet.CustomListener), | ||||
| 		skMap:            make(map[string]string), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (vm *VisitorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) { | ||||
| 	vm.mu.Lock() | ||||
| 	defer vm.mu.Unlock() | ||||
|  | ||||
| 	if _, ok := vm.visitorListeners[name]; ok { | ||||
| 		err = fmt.Errorf("custom listener for [%s] is repeated", name) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	l = frpNet.NewCustomListener() | ||||
| 	vm.visitorListeners[name] = l | ||||
| 	vm.skMap[name] = sk | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (vm *VisitorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string, | ||||
| 	useEncryption bool, useCompression bool) (err error) { | ||||
|  | ||||
| 	vm.mu.RLock() | ||||
| 	defer vm.mu.RUnlock() | ||||
|  | ||||
| 	if l, ok := vm.visitorListeners[name]; ok { | ||||
| 		var sk string | ||||
| 		if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey { | ||||
| 			err = fmt.Errorf("visitor connection of [%s] auth failed", name) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		var rwc io.ReadWriteCloser = conn | ||||
| 		if useEncryption { | ||||
| 			if rwc, err = frpIo.WithEncryption(rwc, []byte(sk)); err != nil { | ||||
| 				err = fmt.Errorf("create encryption connection failed: %v", err) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		if useCompression { | ||||
| 			rwc = frpIo.WithCompression(rwc) | ||||
| 		} | ||||
| 		err = l.PutConn(frpNet.WrapReadWriteCloserToConn(rwc, conn)) | ||||
| 	} else { | ||||
| 		err = fmt.Errorf("custom listener for [%s] doesn't exist", name) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (vm *VisitorManager) CloseListener(name string) { | ||||
| 	vm.mu.Lock() | ||||
| 	defer vm.mu.Unlock() | ||||
|  | ||||
| 	delete(vm.visitorListeners, name) | ||||
| 	delete(vm.skMap, name) | ||||
| } | ||||
							
								
								
									
										73
									
								
								server/dashboard.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								server/dashboard.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package server | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/assets" | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	"github.com/gorilla/mux" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	httpServerReadTimeout  = 10 * time.Second | ||||
| 	httpServerWriteTimeout = 10 * time.Second | ||||
| ) | ||||
|  | ||||
| func (svr *Service) RunDashboardServer(addr string, port int) (err error) { | ||||
| 	// url router | ||||
| 	router := mux.NewRouter() | ||||
|  | ||||
| 	user, passwd := g.GlbServerCfg.DashboardUser, g.GlbServerCfg.DashboardPwd | ||||
| 	router.Use(frpNet.NewHttpAuthMiddleware(user, passwd).Middleware) | ||||
|  | ||||
| 	// api, see dashboard_api.go | ||||
| 	router.HandleFunc("/api/serverinfo", svr.ApiServerInfo).Methods("GET") | ||||
| 	router.HandleFunc("/api/proxy/{type}", svr.ApiProxyByType).Methods("GET") | ||||
| 	router.HandleFunc("/api/proxy/{type}/{name}", svr.ApiProxyByTypeAndName).Methods("GET") | ||||
| 	router.HandleFunc("/api/traffic/{name}", svr.ApiProxyTraffic).Methods("GET") | ||||
|  | ||||
| 	// view | ||||
| 	router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET") | ||||
| 	router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET") | ||||
|  | ||||
| 	router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		http.Redirect(w, r, "/static/", http.StatusMovedPermanently) | ||||
| 	}) | ||||
|  | ||||
| 	address := fmt.Sprintf("%s:%d", addr, port) | ||||
| 	server := &http.Server{ | ||||
| 		Addr:         address, | ||||
| 		Handler:      router, | ||||
| 		ReadTimeout:  httpServerReadTimeout, | ||||
| 		WriteTimeout: httpServerWriteTimeout, | ||||
| 	} | ||||
| 	if address == "" { | ||||
| 		address = ":http" | ||||
| 	} | ||||
| 	ln, err := net.Listen("tcp", address) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	go server.Serve(ln) | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										323
									
								
								server/dashboard_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								server/dashboard_api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,323 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package server | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/consts" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| 	"github.com/fatedier/frp/utils/version" | ||||
|  | ||||
| 	"github.com/gorilla/mux" | ||||
| ) | ||||
|  | ||||
| type GeneralResponse struct { | ||||
| 	Code int | ||||
| 	Msg  string | ||||
| } | ||||
|  | ||||
| type ServerInfoResp struct { | ||||
| 	Version           string `json:"version"` | ||||
| 	BindPort          int    `json:"bind_port"` | ||||
| 	BindUdpPort       int    `json:"bind_udp_port"` | ||||
| 	VhostHttpPort     int    `json:"vhost_http_port"` | ||||
| 	VhostHttpsPort    int    `json:"vhost_https_port"` | ||||
| 	KcpBindPort       int    `json:"kcp_bind_port"` | ||||
| 	SubdomainHost     string `json:"subdomain_host"` | ||||
| 	MaxPoolCount      int64  `json:"max_pool_count"` | ||||
| 	MaxPortsPerClient int64  `json:"max_ports_per_client"` | ||||
| 	HeartBeatTimeout  int64  `json:"heart_beat_timeout"` | ||||
|  | ||||
| 	TotalTrafficIn  int64            `json:"total_traffic_in"` | ||||
| 	TotalTrafficOut int64            `json:"total_traffic_out"` | ||||
| 	CurConns        int64            `json:"cur_conns"` | ||||
| 	ClientCounts    int64            `json:"client_counts"` | ||||
| 	ProxyTypeCounts map[string]int64 `json:"proxy_type_count"` | ||||
| } | ||||
|  | ||||
| // api/serverinfo | ||||
| func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) { | ||||
| 	res := GeneralResponse{Code: 200} | ||||
| 	defer func() { | ||||
| 		log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) | ||||
| 		w.WriteHeader(res.Code) | ||||
| 		if len(res.Msg) > 0 { | ||||
| 			w.Write([]byte(res.Msg)) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	log.Info("Http request: [%s]", r.URL.Path) | ||||
| 	cfg := &g.GlbServerCfg.ServerCommonConf | ||||
| 	serverStats := svr.statsCollector.GetServer() | ||||
| 	svrResp := ServerInfoResp{ | ||||
| 		Version:           version.Full(), | ||||
| 		BindPort:          cfg.BindPort, | ||||
| 		BindUdpPort:       cfg.BindUdpPort, | ||||
| 		VhostHttpPort:     cfg.VhostHttpPort, | ||||
| 		VhostHttpsPort:    cfg.VhostHttpsPort, | ||||
| 		KcpBindPort:       cfg.KcpBindPort, | ||||
| 		SubdomainHost:     cfg.SubDomainHost, | ||||
| 		MaxPoolCount:      cfg.MaxPoolCount, | ||||
| 		MaxPortsPerClient: cfg.MaxPortsPerClient, | ||||
| 		HeartBeatTimeout:  cfg.HeartBeatTimeout, | ||||
|  | ||||
| 		TotalTrafficIn:  serverStats.TotalTrafficIn, | ||||
| 		TotalTrafficOut: serverStats.TotalTrafficOut, | ||||
| 		CurConns:        serverStats.CurConns, | ||||
| 		ClientCounts:    serverStats.ClientCounts, | ||||
| 		ProxyTypeCounts: serverStats.ProxyTypeCounts, | ||||
| 	} | ||||
|  | ||||
| 	buf, _ := json.Marshal(&svrResp) | ||||
| 	res.Msg = string(buf) | ||||
| } | ||||
|  | ||||
| type BaseOutConf struct { | ||||
| 	config.BaseProxyConf | ||||
| } | ||||
|  | ||||
| type TcpOutConf struct { | ||||
| 	BaseOutConf | ||||
| 	RemotePort int `json:"remote_port"` | ||||
| } | ||||
|  | ||||
| type UdpOutConf struct { | ||||
| 	BaseOutConf | ||||
| 	RemotePort int `json:"remote_port"` | ||||
| } | ||||
|  | ||||
| type HttpOutConf struct { | ||||
| 	BaseOutConf | ||||
| 	config.DomainConf | ||||
| 	Locations         []string `json:"locations"` | ||||
| 	HostHeaderRewrite string   `json:"host_header_rewrite"` | ||||
| } | ||||
|  | ||||
| type HttpsOutConf struct { | ||||
| 	BaseOutConf | ||||
| 	config.DomainConf | ||||
| } | ||||
|  | ||||
| type StcpOutConf struct { | ||||
| 	BaseOutConf | ||||
| } | ||||
|  | ||||
| type XtcpOutConf struct { | ||||
| 	BaseOutConf | ||||
| } | ||||
|  | ||||
| func getConfByType(proxyType string) interface{} { | ||||
| 	switch proxyType { | ||||
| 	case consts.TcpProxy: | ||||
| 		return &TcpOutConf{} | ||||
| 	case consts.UdpProxy: | ||||
| 		return &UdpOutConf{} | ||||
| 	case consts.HttpProxy: | ||||
| 		return &HttpOutConf{} | ||||
| 	case consts.HttpsProxy: | ||||
| 		return &HttpsOutConf{} | ||||
| 	case consts.StcpProxy: | ||||
| 		return &StcpOutConf{} | ||||
| 	case consts.XtcpProxy: | ||||
| 		return &XtcpOutConf{} | ||||
| 	default: | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Get proxy info. | ||||
| type ProxyStatsInfo struct { | ||||
| 	Name            string      `json:"name"` | ||||
| 	Conf            interface{} `json:"conf"` | ||||
| 	TodayTrafficIn  int64       `json:"today_traffic_in"` | ||||
| 	TodayTrafficOut int64       `json:"today_traffic_out"` | ||||
| 	CurConns        int64       `json:"cur_conns"` | ||||
| 	LastStartTime   string      `json:"last_start_time"` | ||||
| 	LastCloseTime   string      `json:"last_close_time"` | ||||
| 	Status          string      `json:"status"` | ||||
| } | ||||
|  | ||||
| type GetProxyInfoResp struct { | ||||
| 	Proxies []*ProxyStatsInfo `json:"proxies"` | ||||
| } | ||||
|  | ||||
| // api/proxy/:type | ||||
| func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) { | ||||
| 	res := GeneralResponse{Code: 200} | ||||
| 	params := mux.Vars(r) | ||||
| 	proxyType := params["type"] | ||||
|  | ||||
| 	defer func() { | ||||
| 		log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) | ||||
| 		w.WriteHeader(res.Code) | ||||
| 		if len(res.Msg) > 0 { | ||||
| 			w.Write([]byte(res.Msg)) | ||||
| 		} | ||||
| 	}() | ||||
| 	log.Info("Http request: [%s]", r.URL.Path) | ||||
|  | ||||
| 	proxyInfoResp := GetProxyInfoResp{} | ||||
| 	proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType) | ||||
|  | ||||
| 	buf, _ := json.Marshal(&proxyInfoResp) | ||||
| 	res.Msg = string(buf) | ||||
| } | ||||
|  | ||||
| func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) { | ||||
| 	proxyStats := svr.statsCollector.GetProxiesByType(proxyType) | ||||
| 	proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats)) | ||||
| 	for _, ps := range proxyStats { | ||||
| 		proxyInfo := &ProxyStatsInfo{} | ||||
| 		if pxy, ok := svr.pxyManager.GetByName(ps.Name); ok { | ||||
| 			content, err := json.Marshal(pxy.GetConf()) | ||||
| 			if err != nil { | ||||
| 				log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err) | ||||
| 				continue | ||||
| 			} | ||||
| 			proxyInfo.Conf = getConfByType(ps.Type) | ||||
| 			if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil { | ||||
| 				log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err) | ||||
| 				continue | ||||
| 			} | ||||
| 			proxyInfo.Status = consts.Online | ||||
| 		} else { | ||||
| 			proxyInfo.Status = consts.Offline | ||||
| 		} | ||||
| 		proxyInfo.Name = ps.Name | ||||
| 		proxyInfo.TodayTrafficIn = ps.TodayTrafficIn | ||||
| 		proxyInfo.TodayTrafficOut = ps.TodayTrafficOut | ||||
| 		proxyInfo.CurConns = ps.CurConns | ||||
| 		proxyInfo.LastStartTime = ps.LastStartTime | ||||
| 		proxyInfo.LastCloseTime = ps.LastCloseTime | ||||
| 		proxyInfos = append(proxyInfos, proxyInfo) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Get proxy info by name. | ||||
| type GetProxyStatsResp struct { | ||||
| 	Name            string      `json:"name"` | ||||
| 	Conf            interface{} `json:"conf"` | ||||
| 	TodayTrafficIn  int64       `json:"today_traffic_in"` | ||||
| 	TodayTrafficOut int64       `json:"today_traffic_out"` | ||||
| 	CurConns        int64       `json:"cur_conns"` | ||||
| 	LastStartTime   string      `json:"last_start_time"` | ||||
| 	LastCloseTime   string      `json:"last_close_time"` | ||||
| 	Status          string      `json:"status"` | ||||
| } | ||||
|  | ||||
| // api/proxy/:type/:name | ||||
| func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) { | ||||
| 	res := GeneralResponse{Code: 200} | ||||
| 	params := mux.Vars(r) | ||||
| 	proxyType := params["type"] | ||||
| 	name := params["name"] | ||||
|  | ||||
| 	defer func() { | ||||
| 		log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) | ||||
| 		w.WriteHeader(res.Code) | ||||
| 		if len(res.Msg) > 0 { | ||||
| 			w.Write([]byte(res.Msg)) | ||||
| 		} | ||||
| 	}() | ||||
| 	log.Info("Http request: [%s]", r.URL.Path) | ||||
|  | ||||
| 	proxyStatsResp := GetProxyStatsResp{} | ||||
| 	proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name) | ||||
| 	if res.Code != 200 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	buf, _ := json.Marshal(&proxyStatsResp) | ||||
| 	res.Msg = string(buf) | ||||
| } | ||||
|  | ||||
| func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) { | ||||
| 	proxyInfo.Name = proxyName | ||||
| 	ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName) | ||||
| 	if ps == nil { | ||||
| 		code = 404 | ||||
| 		msg = "no proxy info found" | ||||
| 	} else { | ||||
| 		if pxy, ok := svr.pxyManager.GetByName(proxyName); ok { | ||||
| 			content, err := json.Marshal(pxy.GetConf()) | ||||
| 			if err != nil { | ||||
| 				log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err) | ||||
| 				code = 400 | ||||
| 				msg = "parse conf error" | ||||
| 				return | ||||
| 			} | ||||
| 			proxyInfo.Conf = getConfByType(ps.Type) | ||||
| 			if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil { | ||||
| 				log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err) | ||||
| 				code = 400 | ||||
| 				msg = "parse conf error" | ||||
| 				return | ||||
| 			} | ||||
| 			proxyInfo.Status = consts.Online | ||||
| 		} else { | ||||
| 			proxyInfo.Status = consts.Offline | ||||
| 		} | ||||
| 		proxyInfo.TodayTrafficIn = ps.TodayTrafficIn | ||||
| 		proxyInfo.TodayTrafficOut = ps.TodayTrafficOut | ||||
| 		proxyInfo.CurConns = ps.CurConns | ||||
| 		proxyInfo.LastStartTime = ps.LastStartTime | ||||
| 		proxyInfo.LastCloseTime = ps.LastCloseTime | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // api/traffic/:name | ||||
| type GetProxyTrafficResp struct { | ||||
| 	Name       string  `json:"name"` | ||||
| 	TrafficIn  []int64 `json:"traffic_in"` | ||||
| 	TrafficOut []int64 `json:"traffic_out"` | ||||
| } | ||||
|  | ||||
| func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) { | ||||
| 	res := GeneralResponse{Code: 200} | ||||
| 	params := mux.Vars(r) | ||||
| 	name := params["name"] | ||||
|  | ||||
| 	defer func() { | ||||
| 		log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code) | ||||
| 		w.WriteHeader(res.Code) | ||||
| 		if len(res.Msg) > 0 { | ||||
| 			w.Write([]byte(res.Msg)) | ||||
| 		} | ||||
| 	}() | ||||
| 	log.Info("Http request: [%s]", r.URL.Path) | ||||
|  | ||||
| 	trafficResp := GetProxyTrafficResp{} | ||||
| 	trafficResp.Name = name | ||||
| 	proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name) | ||||
|  | ||||
| 	if proxyTrafficInfo == nil { | ||||
| 		res.Code = 404 | ||||
| 		res.Msg = "no proxy info found" | ||||
| 		return | ||||
| 	} else { | ||||
| 		trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn | ||||
| 		trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut | ||||
| 	} | ||||
|  | ||||
| 	buf, _ := json.Marshal(&trafficResp) | ||||
| 	res.Msg = string(buf) | ||||
| } | ||||
							
								
								
									
										26
									
								
								server/group/group.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								server/group/group.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package group | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrGroupAuthFailed    = errors.New("group auth failed") | ||||
| 	ErrGroupParamsInvalid = errors.New("group params invalid") | ||||
| 	ErrListenerClosed     = errors.New("group listener closed") | ||||
| 	ErrGroupDifferentPort = errors.New("group should have same remote port") | ||||
| ) | ||||
							
								
								
									
										204
									
								
								server/group/tcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								server/group/tcp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,204 @@ | ||||
| // Copyright 2018 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package group | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/fatedier/frp/server/ports" | ||||
|  | ||||
| 	gerr "github.com/fatedier/golib/errors" | ||||
| ) | ||||
|  | ||||
| type TcpGroupListener struct { | ||||
| 	groupName string | ||||
| 	group     *TcpGroup | ||||
|  | ||||
| 	addr    net.Addr | ||||
| 	closeCh chan struct{} | ||||
| } | ||||
|  | ||||
| func newTcpGroupListener(name string, group *TcpGroup, addr net.Addr) *TcpGroupListener { | ||||
| 	return &TcpGroupListener{ | ||||
| 		groupName: name, | ||||
| 		group:     group, | ||||
| 		addr:      addr, | ||||
| 		closeCh:   make(chan struct{}), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (ln *TcpGroupListener) Accept() (c net.Conn, err error) { | ||||
| 	var ok bool | ||||
| 	select { | ||||
| 	case <-ln.closeCh: | ||||
| 		return nil, ErrListenerClosed | ||||
| 	case c, ok = <-ln.group.Accept(): | ||||
| 		if !ok { | ||||
| 			return nil, ErrListenerClosed | ||||
| 		} | ||||
| 		return c, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (ln *TcpGroupListener) Addr() net.Addr { | ||||
| 	return ln.addr | ||||
| } | ||||
|  | ||||
| func (ln *TcpGroupListener) Close() (err error) { | ||||
| 	close(ln.closeCh) | ||||
| 	ln.group.CloseListener(ln) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type TcpGroup struct { | ||||
| 	group    string | ||||
| 	groupKey string | ||||
| 	addr     string | ||||
| 	port     int | ||||
| 	realPort int | ||||
|  | ||||
| 	acceptCh chan net.Conn | ||||
| 	index    uint64 | ||||
| 	tcpLn    net.Listener | ||||
| 	lns      []*TcpGroupListener | ||||
| 	ctl      *TcpGroupCtl | ||||
| 	mu       sync.Mutex | ||||
| } | ||||
|  | ||||
| func NewTcpGroup(ctl *TcpGroupCtl) *TcpGroup { | ||||
| 	return &TcpGroup{ | ||||
| 		lns:      make([]*TcpGroupListener, 0), | ||||
| 		ctl:      ctl, | ||||
| 		acceptCh: make(chan net.Conn), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (tg *TcpGroup) Listen(proxyName string, group string, groupKey string, addr string, port int) (ln *TcpGroupListener, realPort int, err error) { | ||||
| 	tg.mu.Lock() | ||||
| 	defer tg.mu.Unlock() | ||||
| 	if len(tg.lns) == 0 { | ||||
| 		realPort, err = tg.ctl.portManager.Acquire(proxyName, port) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		tcpLn, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port)) | ||||
| 		if errRet != nil { | ||||
| 			err = errRet | ||||
| 			return | ||||
| 		} | ||||
| 		ln = newTcpGroupListener(group, tg, tcpLn.Addr()) | ||||
|  | ||||
| 		tg.group = group | ||||
| 		tg.groupKey = groupKey | ||||
| 		tg.addr = addr | ||||
| 		tg.port = port | ||||
| 		tg.realPort = realPort | ||||
| 		tg.tcpLn = tcpLn | ||||
| 		tg.lns = append(tg.lns, ln) | ||||
| 		if tg.acceptCh == nil { | ||||
| 			tg.acceptCh = make(chan net.Conn) | ||||
| 		} | ||||
| 		go tg.worker() | ||||
| 	} else { | ||||
| 		if tg.group != group || tg.addr != addr { | ||||
| 			err = ErrGroupParamsInvalid | ||||
| 			return | ||||
| 		} | ||||
| 		if tg.port != port { | ||||
| 			err = ErrGroupDifferentPort | ||||
| 			return | ||||
| 		} | ||||
| 		if tg.groupKey != groupKey { | ||||
| 			err = ErrGroupAuthFailed | ||||
| 			return | ||||
| 		} | ||||
| 		ln = newTcpGroupListener(group, tg, tg.lns[0].Addr()) | ||||
| 		realPort = tg.realPort | ||||
| 		tg.lns = append(tg.lns, ln) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (tg *TcpGroup) worker() { | ||||
| 	for { | ||||
| 		c, err := tg.tcpLn.Accept() | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		err = gerr.PanicToError(func() { | ||||
| 			tg.acceptCh <- c | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (tg *TcpGroup) Accept() <-chan net.Conn { | ||||
| 	return tg.acceptCh | ||||
| } | ||||
|  | ||||
| func (tg *TcpGroup) CloseListener(ln *TcpGroupListener) { | ||||
| 	tg.mu.Lock() | ||||
| 	defer tg.mu.Unlock() | ||||
| 	for i, tmpLn := range tg.lns { | ||||
| 		if tmpLn == ln { | ||||
| 			tg.lns = append(tg.lns[:i], tg.lns[i+1:]...) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if len(tg.lns) == 0 { | ||||
| 		close(tg.acceptCh) | ||||
| 		tg.tcpLn.Close() | ||||
| 		tg.ctl.portManager.Release(tg.realPort) | ||||
| 		tg.ctl.RemoveGroup(tg.group) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type TcpGroupCtl struct { | ||||
| 	groups map[string]*TcpGroup | ||||
|  | ||||
| 	portManager *ports.PortManager | ||||
| 	mu          sync.Mutex | ||||
| } | ||||
|  | ||||
| func NewTcpGroupCtl(portManager *ports.PortManager) *TcpGroupCtl { | ||||
| 	return &TcpGroupCtl{ | ||||
| 		groups:      make(map[string]*TcpGroup), | ||||
| 		portManager: portManager, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (tgc *TcpGroupCtl) Listen(proxyNanme string, group string, groupKey string, | ||||
| 	addr string, port int) (l net.Listener, realPort int, err error) { | ||||
|  | ||||
| 	tgc.mu.Lock() | ||||
| 	defer tgc.mu.Unlock() | ||||
| 	if tcpGroup, ok := tgc.groups[group]; ok { | ||||
| 		return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port) | ||||
| 	} else { | ||||
| 		tcpGroup = NewTcpGroup(tgc) | ||||
| 		tgc.groups[group] = tcpGroup | ||||
| 		return tcpGroup.Listen(proxyNanme, group, groupKey, addr, port) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (tgc *TcpGroupCtl) RemoveGroup(group string) { | ||||
| 	tgc.mu.Lock() | ||||
| 	defer tgc.mu.Unlock() | ||||
| 	delete(tgc.groups, group) | ||||
| } | ||||
							
								
								
									
										180
									
								
								server/ports/ports.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								server/ports/ports.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | ||||
| package ports | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	MinPort                    = 1 | ||||
| 	MaxPort                    = 65535 | ||||
| 	MaxPortReservedDuration    = time.Duration(24) * time.Hour | ||||
| 	CleanReservedPortsInterval = time.Hour | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrPortAlreadyUsed = errors.New("port already used") | ||||
| 	ErrPortNotAllowed  = errors.New("port not allowed") | ||||
| 	ErrPortUnAvailable = errors.New("port unavailable") | ||||
| 	ErrNoAvailablePort = errors.New("no available port") | ||||
| ) | ||||
|  | ||||
| type PortCtx struct { | ||||
| 	ProxyName  string | ||||
| 	Port       int | ||||
| 	Closed     bool | ||||
| 	UpdateTime time.Time | ||||
| } | ||||
|  | ||||
| type PortManager struct { | ||||
| 	reservedPorts map[string]*PortCtx | ||||
| 	usedPorts     map[int]*PortCtx | ||||
| 	freePorts     map[int]struct{} | ||||
|  | ||||
| 	bindAddr string | ||||
| 	netType  string | ||||
| 	mu       sync.Mutex | ||||
| } | ||||
|  | ||||
| func NewPortManager(netType string, bindAddr string, allowPorts map[int]struct{}) *PortManager { | ||||
| 	pm := &PortManager{ | ||||
| 		reservedPorts: make(map[string]*PortCtx), | ||||
| 		usedPorts:     make(map[int]*PortCtx), | ||||
| 		freePorts:     make(map[int]struct{}), | ||||
| 		bindAddr:      bindAddr, | ||||
| 		netType:       netType, | ||||
| 	} | ||||
| 	if len(allowPorts) > 0 { | ||||
| 		for port, _ := range allowPorts { | ||||
| 			pm.freePorts[port] = struct{}{} | ||||
| 		} | ||||
| 	} else { | ||||
| 		for i := MinPort; i <= MaxPort; i++ { | ||||
| 			pm.freePorts[i] = struct{}{} | ||||
| 		} | ||||
| 	} | ||||
| 	go pm.cleanReservedPortsWorker() | ||||
| 	return pm | ||||
| } | ||||
|  | ||||
| func (pm *PortManager) Acquire(name string, port int) (realPort int, err error) { | ||||
| 	portCtx := &PortCtx{ | ||||
| 		ProxyName:  name, | ||||
| 		Closed:     false, | ||||
| 		UpdateTime: time.Now(), | ||||
| 	} | ||||
|  | ||||
| 	var ok bool | ||||
|  | ||||
| 	pm.mu.Lock() | ||||
| 	defer func() { | ||||
| 		if err == nil { | ||||
| 			portCtx.Port = realPort | ||||
| 		} | ||||
| 		pm.mu.Unlock() | ||||
| 	}() | ||||
|  | ||||
| 	// check reserved ports first | ||||
| 	if port == 0 { | ||||
| 		if ctx, ok := pm.reservedPorts[name]; ok { | ||||
| 			if pm.isPortAvailable(ctx.Port) { | ||||
| 				realPort = ctx.Port | ||||
| 				pm.usedPorts[realPort] = portCtx | ||||
| 				pm.reservedPorts[name] = portCtx | ||||
| 				delete(pm.freePorts, realPort) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if port == 0 { | ||||
| 		// get random port | ||||
| 		count := 0 | ||||
| 		maxTryTimes := 5 | ||||
| 		for k, _ := range pm.freePorts { | ||||
| 			count++ | ||||
| 			if count > maxTryTimes { | ||||
| 				break | ||||
| 			} | ||||
| 			if pm.isPortAvailable(k) { | ||||
| 				realPort = k | ||||
| 				pm.usedPorts[realPort] = portCtx | ||||
| 				pm.reservedPorts[name] = portCtx | ||||
| 				delete(pm.freePorts, realPort) | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if realPort == 0 { | ||||
| 			err = ErrNoAvailablePort | ||||
| 		} | ||||
| 	} else { | ||||
| 		// specified port | ||||
| 		if _, ok = pm.freePorts[port]; ok { | ||||
| 			if pm.isPortAvailable(port) { | ||||
| 				realPort = port | ||||
| 				pm.usedPorts[realPort] = portCtx | ||||
| 				pm.reservedPorts[name] = portCtx | ||||
| 				delete(pm.freePorts, realPort) | ||||
| 			} else { | ||||
| 				err = ErrPortUnAvailable | ||||
| 			} | ||||
| 		} else { | ||||
| 			if _, ok = pm.usedPorts[port]; ok { | ||||
| 				err = ErrPortAlreadyUsed | ||||
| 			} else { | ||||
| 				err = ErrPortNotAllowed | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pm *PortManager) isPortAvailable(port int) bool { | ||||
| 	if pm.netType == "udp" { | ||||
| 		addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", pm.bindAddr, port)) | ||||
| 		if err != nil { | ||||
| 			return false | ||||
| 		} | ||||
| 		l, err := net.ListenUDP("udp", addr) | ||||
| 		if err != nil { | ||||
| 			return false | ||||
| 		} | ||||
| 		l.Close() | ||||
| 		return true | ||||
| 	} else { | ||||
| 		l, err := net.Listen(pm.netType, fmt.Sprintf("%s:%d", pm.bindAddr, port)) | ||||
| 		if err != nil { | ||||
| 			return false | ||||
| 		} | ||||
| 		l.Close() | ||||
| 		return true | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pm *PortManager) Release(port int) { | ||||
| 	pm.mu.Lock() | ||||
| 	defer pm.mu.Unlock() | ||||
| 	if ctx, ok := pm.usedPorts[port]; ok { | ||||
| 		pm.freePorts[port] = struct{}{} | ||||
| 		delete(pm.usedPorts, port) | ||||
| 		ctx.Closed = true | ||||
| 		ctx.UpdateTime = time.Now() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Release reserved port if it isn't used in last 24 hours. | ||||
| func (pm *PortManager) cleanReservedPortsWorker() { | ||||
| 	for { | ||||
| 		time.Sleep(CleanReservedPortsInterval) | ||||
| 		pm.mu.Lock() | ||||
| 		for name, ctx := range pm.reservedPorts { | ||||
| 			if ctx.Closed && time.Since(ctx.UpdateTime) > MaxPortReservedDuration { | ||||
| 				delete(pm.reservedPorts, name) | ||||
| 			} | ||||
| 		} | ||||
| 		pm.mu.Unlock() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										138
									
								
								server/proxy/http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								server/proxy/http.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| // Copyright 2019 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/server/stats" | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
| 	"github.com/fatedier/frp/utils/util" | ||||
| 	"github.com/fatedier/frp/utils/vhost" | ||||
|  | ||||
| 	frpIo "github.com/fatedier/golib/io" | ||||
| ) | ||||
|  | ||||
| type HttpProxy struct { | ||||
| 	*BaseProxy | ||||
| 	cfg *config.HttpProxyConf | ||||
|  | ||||
| 	closeFuncs []func() | ||||
| } | ||||
|  | ||||
| func (pxy *HttpProxy) Run() (remoteAddr string, err error) { | ||||
| 	routeConfig := vhost.VhostRouteConfig{ | ||||
| 		RewriteHost:  pxy.cfg.HostHeaderRewrite, | ||||
| 		Headers:      pxy.cfg.Headers, | ||||
| 		Username:     pxy.cfg.HttpUser, | ||||
| 		Password:     pxy.cfg.HttpPwd, | ||||
| 		CreateConnFn: pxy.GetRealConn, | ||||
| 	} | ||||
|  | ||||
| 	locations := pxy.cfg.Locations | ||||
| 	if len(locations) == 0 { | ||||
| 		locations = []string{""} | ||||
| 	} | ||||
|  | ||||
| 	addrs := make([]string, 0) | ||||
| 	for _, domain := range pxy.cfg.CustomDomains { | ||||
| 		routeConfig.Domain = domain | ||||
| 		for _, location := range locations { | ||||
| 			routeConfig.Location = location | ||||
| 			err = pxy.rc.HttpReverseProxy.Register(routeConfig) | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			tmpDomain := routeConfig.Domain | ||||
| 			tmpLocation := routeConfig.Location | ||||
| 			addrs = append(addrs, util.CanonicalAddr(tmpDomain, int(g.GlbServerCfg.VhostHttpPort))) | ||||
| 			pxy.closeFuncs = append(pxy.closeFuncs, func() { | ||||
| 				pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation) | ||||
| 			}) | ||||
| 			pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if pxy.cfg.SubDomain != "" { | ||||
| 		routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost | ||||
| 		for _, location := range locations { | ||||
| 			routeConfig.Location = location | ||||
| 			err = pxy.rc.HttpReverseProxy.Register(routeConfig) | ||||
| 			if err != nil { | ||||
| 				return | ||||
| 			} | ||||
| 			tmpDomain := routeConfig.Domain | ||||
| 			tmpLocation := routeConfig.Location | ||||
| 			addrs = append(addrs, util.CanonicalAddr(tmpDomain, g.GlbServerCfg.VhostHttpPort)) | ||||
| 			pxy.closeFuncs = append(pxy.closeFuncs, func() { | ||||
| 				pxy.rc.HttpReverseProxy.UnRegister(tmpDomain, tmpLocation) | ||||
| 			}) | ||||
| 			pxy.Info("http proxy listen for host [%s] location [%s]", routeConfig.Domain, routeConfig.Location) | ||||
| 		} | ||||
| 	} | ||||
| 	remoteAddr = strings.Join(addrs, ",") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pxy *HttpProxy) GetConf() config.ProxyConf { | ||||
| 	return pxy.cfg | ||||
| } | ||||
|  | ||||
| func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) { | ||||
| 	tmpConn, errRet := pxy.GetWorkConnFromPool() | ||||
| 	if errRet != nil { | ||||
| 		err = errRet | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var rwc io.ReadWriteCloser = tmpConn | ||||
| 	if pxy.cfg.UseEncryption { | ||||
| 		rwc, err = frpIo.WithEncryption(rwc, []byte(g.GlbServerCfg.Token)) | ||||
| 		if err != nil { | ||||
| 			pxy.Error("create encryption stream error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if pxy.cfg.UseCompression { | ||||
| 		rwc = frpIo.WithCompression(rwc) | ||||
| 	} | ||||
| 	workConn = frpNet.WrapReadWriteCloserToConn(rwc, tmpConn) | ||||
| 	workConn = frpNet.WrapStatsConn(workConn, pxy.updateStatsAfterClosedConn) | ||||
| 	pxy.statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()}) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pxy *HttpProxy) updateStatsAfterClosedConn(totalRead, totalWrite int64) { | ||||
| 	name := pxy.GetName() | ||||
| 	pxy.statsCollector.Mark(stats.TypeCloseProxy, &stats.CloseConnectionPayload{ProxyName: name}) | ||||
| 	pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{ | ||||
| 		ProxyName:    name, | ||||
| 		TrafficBytes: totalWrite, | ||||
| 	}) | ||||
| 	pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{ | ||||
| 		ProxyName:    name, | ||||
| 		TrafficBytes: totalRead, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (pxy *HttpProxy) Close() { | ||||
| 	pxy.BaseProxy.Close() | ||||
| 	for _, closeFn := range pxy.closeFuncs { | ||||
| 		closeFn() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										72
									
								
								server/proxy/https.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								server/proxy/https.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| // Copyright 2019 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/utils/util" | ||||
| 	"github.com/fatedier/frp/utils/vhost" | ||||
| ) | ||||
|  | ||||
| type HttpsProxy struct { | ||||
| 	*BaseProxy | ||||
| 	cfg *config.HttpsProxyConf | ||||
| } | ||||
|  | ||||
| func (pxy *HttpsProxy) Run() (remoteAddr string, err error) { | ||||
| 	routeConfig := &vhost.VhostRouteConfig{} | ||||
|  | ||||
| 	addrs := make([]string, 0) | ||||
| 	for _, domain := range pxy.cfg.CustomDomains { | ||||
| 		routeConfig.Domain = domain | ||||
| 		l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig) | ||||
| 		if errRet != nil { | ||||
| 			err = errRet | ||||
| 			return | ||||
| 		} | ||||
| 		l.AddLogPrefix(pxy.name) | ||||
| 		pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) | ||||
| 		pxy.listeners = append(pxy.listeners, l) | ||||
| 		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, g.GlbServerCfg.VhostHttpsPort)) | ||||
| 	} | ||||
|  | ||||
| 	if pxy.cfg.SubDomain != "" { | ||||
| 		routeConfig.Domain = pxy.cfg.SubDomain + "." + g.GlbServerCfg.SubDomainHost | ||||
| 		l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig) | ||||
| 		if errRet != nil { | ||||
| 			err = errRet | ||||
| 			return | ||||
| 		} | ||||
| 		l.AddLogPrefix(pxy.name) | ||||
| 		pxy.Info("https proxy listen for host [%s]", routeConfig.Domain) | ||||
| 		pxy.listeners = append(pxy.listeners, l) | ||||
| 		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(g.GlbServerCfg.VhostHttpsPort))) | ||||
| 	} | ||||
|  | ||||
| 	pxy.startListenHandler(pxy, HandleUserTcpConnection) | ||||
| 	remoteAddr = strings.Join(addrs, ",") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pxy *HttpsProxy) GetConf() config.ProxyConf { | ||||
| 	return pxy.cfg | ||||
| } | ||||
|  | ||||
| func (pxy *HttpsProxy) Close() { | ||||
| 	pxy.BaseProxy.Close() | ||||
| } | ||||
							
								
								
									
										250
									
								
								server/proxy/proxy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								server/proxy/proxy.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,250 @@ | ||||
| // Copyright 2017 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	"github.com/fatedier/frp/models/msg" | ||||
| 	"github.com/fatedier/frp/server/controller" | ||||
| 	"github.com/fatedier/frp/server/stats" | ||||
| 	"github.com/fatedier/frp/utils/log" | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
|  | ||||
| 	frpIo "github.com/fatedier/golib/io" | ||||
| ) | ||||
|  | ||||
| type GetWorkConnFn func() (frpNet.Conn, error) | ||||
|  | ||||
| type Proxy interface { | ||||
| 	Run() (remoteAddr string, err error) | ||||
| 	GetName() string | ||||
| 	GetConf() config.ProxyConf | ||||
| 	GetWorkConnFromPool() (workConn frpNet.Conn, err error) | ||||
| 	GetUsedPortsNum() int | ||||
| 	Close() | ||||
| 	log.Logger | ||||
| } | ||||
|  | ||||
| type BaseProxy struct { | ||||
| 	name           string | ||||
| 	rc             *controller.ResourceController | ||||
| 	statsCollector stats.Collector | ||||
| 	listeners      []frpNet.Listener | ||||
| 	usedPortsNum   int | ||||
| 	poolCount      int | ||||
| 	getWorkConnFn  GetWorkConnFn | ||||
|  | ||||
| 	mu sync.RWMutex | ||||
| 	log.Logger | ||||
| } | ||||
|  | ||||
| func (pxy *BaseProxy) GetName() string { | ||||
| 	return pxy.name | ||||
| } | ||||
|  | ||||
| func (pxy *BaseProxy) GetUsedPortsNum() int { | ||||
| 	return pxy.usedPortsNum | ||||
| } | ||||
|  | ||||
| func (pxy *BaseProxy) Close() { | ||||
| 	pxy.Info("proxy closing") | ||||
| 	for _, l := range pxy.listeners { | ||||
| 		l.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) { | ||||
| 	// try all connections from the pool | ||||
| 	for i := 0; i < pxy.poolCount+1; i++ { | ||||
| 		if workConn, err = pxy.getWorkConnFn(); err != nil { | ||||
| 			pxy.Warn("failed to get work connection: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 		pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String()) | ||||
| 		workConn.AddLogPrefix(pxy.GetName()) | ||||
|  | ||||
| 		err := msg.WriteMsg(workConn, &msg.StartWorkConn{ | ||||
| 			ProxyName: pxy.GetName(), | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i) | ||||
| 			workConn.Close() | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		pxy.Error("try to get work connection failed in the end") | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // startListenHandler start a goroutine handler for each listener. | ||||
| // p: p will just be passed to handler(Proxy, frpNet.Conn). | ||||
| // handler: each proxy type can set different handler function to deal with connections accepted from listeners. | ||||
| func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector)) { | ||||
| 	for _, listener := range pxy.listeners { | ||||
| 		go func(l frpNet.Listener) { | ||||
| 			for { | ||||
| 				// block | ||||
| 				// if listener is closed, err returned | ||||
| 				c, err := l.Accept() | ||||
| 				if err != nil { | ||||
| 					pxy.Info("listener is closed") | ||||
| 					return | ||||
| 				} | ||||
| 				pxy.Debug("get a user connection [%s]", c.RemoteAddr().String()) | ||||
| 				go handler(p, c, pxy.statsCollector) | ||||
| 			} | ||||
| 		}(listener) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int, | ||||
| 	getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf) (pxy Proxy, err error) { | ||||
|  | ||||
| 	basePxy := BaseProxy{ | ||||
| 		name:           pxyConf.GetBaseInfo().ProxyName, | ||||
| 		rc:             rc, | ||||
| 		statsCollector: statsCollector, | ||||
| 		listeners:      make([]frpNet.Listener, 0), | ||||
| 		poolCount:      poolCount, | ||||
| 		getWorkConnFn:  getWorkConnFn, | ||||
| 		Logger:         log.NewPrefixLogger(runId), | ||||
| 	} | ||||
| 	switch cfg := pxyConf.(type) { | ||||
| 	case *config.TcpProxyConf: | ||||
| 		basePxy.usedPortsNum = 1 | ||||
| 		pxy = &TcpProxy{ | ||||
| 			BaseProxy: &basePxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	case *config.HttpProxyConf: | ||||
| 		pxy = &HttpProxy{ | ||||
| 			BaseProxy: &basePxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	case *config.HttpsProxyConf: | ||||
| 		pxy = &HttpsProxy{ | ||||
| 			BaseProxy: &basePxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	case *config.UdpProxyConf: | ||||
| 		basePxy.usedPortsNum = 1 | ||||
| 		pxy = &UdpProxy{ | ||||
| 			BaseProxy: &basePxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	case *config.StcpProxyConf: | ||||
| 		pxy = &StcpProxy{ | ||||
| 			BaseProxy: &basePxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	case *config.XtcpProxyConf: | ||||
| 		pxy = &XtcpProxy{ | ||||
| 			BaseProxy: &basePxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	default: | ||||
| 		return pxy, fmt.Errorf("proxy type not support") | ||||
| 	} | ||||
| 	pxy.AddLogPrefix(pxy.GetName()) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // HandleUserTcpConnection is used for incoming tcp user connections. | ||||
| // It can be used for tcp, http, https type. | ||||
| func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector) { | ||||
| 	defer userConn.Close() | ||||
|  | ||||
| 	// try all connections from the pool | ||||
| 	workConn, err := pxy.GetWorkConnFromPool() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer workConn.Close() | ||||
|  | ||||
| 	var local io.ReadWriteCloser = workConn | ||||
| 	cfg := pxy.GetConf().GetBaseInfo() | ||||
| 	if cfg.UseEncryption { | ||||
| 		local, err = frpIo.WithEncryption(local, []byte(g.GlbServerCfg.Token)) | ||||
| 		if err != nil { | ||||
| 			pxy.Error("create encryption stream error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if cfg.UseCompression { | ||||
| 		local = frpIo.WithCompression(local) | ||||
| 	} | ||||
| 	pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(), | ||||
| 		workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String()) | ||||
|  | ||||
| 	statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()}) | ||||
| 	inCount, outCount := frpIo.Join(local, userConn) | ||||
| 	statsCollector.Mark(stats.TypeCloseConnection, &stats.CloseConnectionPayload{ProxyName: pxy.GetName()}) | ||||
| 	statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{ | ||||
| 		ProxyName:    pxy.GetName(), | ||||
| 		TrafficBytes: inCount, | ||||
| 	}) | ||||
| 	statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{ | ||||
| 		ProxyName:    pxy.GetName(), | ||||
| 		TrafficBytes: outCount, | ||||
| 	}) | ||||
| 	pxy.Debug("join connections closed") | ||||
| } | ||||
|  | ||||
| type ProxyManager struct { | ||||
| 	// proxies indexed by proxy name | ||||
| 	pxys map[string]Proxy | ||||
|  | ||||
| 	mu sync.RWMutex | ||||
| } | ||||
|  | ||||
| func NewProxyManager() *ProxyManager { | ||||
| 	return &ProxyManager{ | ||||
| 		pxys: make(map[string]Proxy), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pm *ProxyManager) Add(name string, pxy Proxy) error { | ||||
| 	pm.mu.Lock() | ||||
| 	defer pm.mu.Unlock() | ||||
| 	if _, ok := pm.pxys[name]; ok { | ||||
| 		return fmt.Errorf("proxy name [%s] is already in use", name) | ||||
| 	} | ||||
|  | ||||
| 	pm.pxys[name] = pxy | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (pm *ProxyManager) Del(name string) { | ||||
| 	pm.mu.Lock() | ||||
| 	defer pm.mu.Unlock() | ||||
| 	delete(pm.pxys, name) | ||||
| } | ||||
|  | ||||
| func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) { | ||||
| 	pm.mu.RLock() | ||||
| 	defer pm.mu.RUnlock() | ||||
| 	pxy, ok = pm.pxys[name] | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										47
									
								
								server/proxy/stcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								server/proxy/stcp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| // Copyright 2019 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| ) | ||||
|  | ||||
| type StcpProxy struct { | ||||
| 	*BaseProxy | ||||
| 	cfg *config.StcpProxyConf | ||||
| } | ||||
|  | ||||
| func (pxy *StcpProxy) Run() (remoteAddr string, err error) { | ||||
| 	listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk) | ||||
| 	if errRet != nil { | ||||
| 		err = errRet | ||||
| 		return | ||||
| 	} | ||||
| 	listener.AddLogPrefix(pxy.name) | ||||
| 	pxy.listeners = append(pxy.listeners, listener) | ||||
| 	pxy.Info("stcp proxy custom listen success") | ||||
|  | ||||
| 	pxy.startListenHandler(pxy, HandleUserTcpConnection) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pxy *StcpProxy) GetConf() config.ProxyConf { | ||||
| 	return pxy.cfg | ||||
| } | ||||
|  | ||||
| func (pxy *StcpProxy) Close() { | ||||
| 	pxy.BaseProxy.Close() | ||||
| 	pxy.rc.VisitorManager.CloseListener(pxy.GetName()) | ||||
| } | ||||
							
								
								
									
										84
									
								
								server/proxy/tcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								server/proxy/tcp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| // Copyright 2019 fatedier, fatedier@gmail.com | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/fatedier/frp/g" | ||||
| 	"github.com/fatedier/frp/models/config" | ||||
| 	frpNet "github.com/fatedier/frp/utils/net" | ||||
| ) | ||||
|  | ||||
| type TcpProxy struct { | ||||
| 	*BaseProxy | ||||
| 	cfg *config.TcpProxyConf | ||||
|  | ||||
| 	realPort int | ||||
| } | ||||
|  | ||||
| func (pxy *TcpProxy) Run() (remoteAddr string, err error) { | ||||
| 	if pxy.cfg.Group != "" { | ||||
| 		l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, g.GlbServerCfg.ProxyBindAddr, pxy.cfg.RemotePort) | ||||
| 		if errRet != nil { | ||||
| 			err = errRet | ||||
| 			return | ||||
| 		} | ||||
| 		defer func() { | ||||
| 			if err != nil { | ||||
| 				l.Close() | ||||
| 			} | ||||
| 		}() | ||||
| 		pxy.realPort = realPort | ||||
| 		listener := frpNet.WrapLogListener(l) | ||||
| 		listener.AddLogPrefix(pxy.name) | ||||
| 		pxy.listeners = append(pxy.listeners, listener) | ||||
| 		pxy.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group) | ||||
| 	} else { | ||||
| 		pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		defer func() { | ||||
| 			if err != nil { | ||||
| 				pxy.rc.TcpPortManager.Release(pxy.realPort) | ||||
| 			} | ||||
| 		}() | ||||
| 		listener, errRet := frpNet.ListenTcp(g.GlbServerCfg.ProxyBindAddr, pxy.realPort) | ||||
| 		if errRet != nil { | ||||
| 			err = errRet | ||||
| 			return | ||||
| 		} | ||||
| 		listener.AddLogPrefix(pxy.name) | ||||
| 		pxy.listeners = append(pxy.listeners, listener) | ||||
| 		pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort) | ||||
| 	} | ||||
|  | ||||
| 	pxy.cfg.RemotePort = pxy.realPort | ||||
| 	remoteAddr = fmt.Sprintf(":%d", pxy.realPort) | ||||
| 	pxy.startListenHandler(pxy, HandleUserTcpConnection) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pxy *TcpProxy) GetConf() config.ProxyConf { | ||||
| 	return pxy.cfg | ||||
| } | ||||
|  | ||||
| func (pxy *TcpProxy) Close() { | ||||
| 	pxy.BaseProxy.Close() | ||||
| 	if pxy.cfg.Group == "" { | ||||
| 		pxy.rc.TcpPortManager.Release(pxy.realPort) | ||||
| 	} | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user