mirror of
				https://github.com/fatedier/frp.git
				synced 2025-10-30 22:27:37 +00:00 
			
		
		
		
	Compare commits
	
		
			881 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | cfd1a3128a | ||
|   | 2393923870 | ||
|   | 5f594e9a71 | ||
|   | 57577ea044 | ||
|   | 8637077d90 | ||
|   | ccb85a9926 | ||
|   | 02b12df887 | ||
|   | c32a2ed140 | ||
|   | 9ae322cccf | ||
|   | 9cebfccb39 | ||
|   | 630dad50ed | ||
|   | 0d84da91d4 | ||
|   | 2408f1df04 | ||
|   | fbaa5f866e | ||
|   | c5c79e4148 | ||
|   | 9a849a29e9 | ||
|   | 6b80861bd6 | ||
|   | fa0e84382e | ||
|   | 1a11b28f8d | ||
|   | bed13d7ef1 | ||
|   | e7d76b180d | ||
|   | dba8925eaa | ||
|   | 55da58eca4 | ||
|   | fdef7448a7 | ||
|   | 0ff27fc9ac | ||
|   | 76a1efccd9 | ||
|   | 9f8db314d6 | ||
|   | 980f084ad1 | ||
|   | 0c35863d97 | ||
|   | 184a0ff9ab | ||
|   | 8e25f13201 | ||
|   | b5aee82ca9 | ||
|   | 0a2384a283 | ||
|   | 78b8bb7bc6 | ||
|   | 8fcd4f4a95 | ||
|   | 976fd81d4d | ||
|   | 52d5c9e25b | ||
|   | fa89671452 | ||
|   | 3621aad1c1 | ||
|   | 3bf1eb8565 | ||
|   | a821db3f45 | ||
|   | ecb6ed9258 | ||
|   | b2ae433e18 | ||
|   | b26080589b | ||
|   | aff979c2b6 | ||
|   | 46f809d711 | ||
|   | 72595b2da8 | ||
|   | c842558ace | ||
|   | ed61049041 | ||
|   | abe6f580c0 | ||
|   | e940066012 | ||
|   | 1e846df870 | ||
|   | 0ab055e946 | ||
|   | fca59c71e2 | ||
|   | fae2f8768d | ||
|   | 3d9499f554 | ||
|   | 7adeeedd55 | ||
|   | 127a31ea6a | ||
|   | a85bd9a4d9 | ||
|   | 01d551ec8d | ||
|   | 16cabf4127 | ||
|   | 968be4a2c2 | ||
|   | aa0a41ee4e | ||
|   | 8a779eb88c | ||
|   | 0138dbd352 | ||
|   | 9b45c93c14 | ||
|   | 191da54980 | ||
|   | c3b7575453 | ||
|   | 1ea1530b36 | ||
|   | 7f7305fa03 | ||
|   | 644a0cfdb6 | ||
|   | 3c2e2bcea5 | ||
|   | e0c45a1aca | ||
|   | e52dfc4a5c | ||
|   | cc003a2570 | ||
|   | 0f8040b875 | ||
|   | ef5ae3e598 | ||
|   | 3acf1bb6e9 | ||
|   | 1089eb9d22 | ||
|   | edf9596ca8 | ||
|   | 26e54b901f | ||
|   | 008933f304 | ||
|   | 317f901c1c | ||
|   | cd5314466c | ||
|   | 3fbdea0f6b | ||
|   | 710ecf44f5 | ||
|   | 04dafd7ff0 | ||
|   | c6aa74a2bb | ||
|   | 813c45f5c2 | ||
|   | c0e05bb41e | ||
|   | aa74dc4646 | ||
|   | 1e420cc766 | ||
|   | 4fff3c7472 | ||
|   | 48fa618c34 | ||
|   | c9fe23eb10 | ||
|   | 268afb3438 | ||
|   | b1181fd17a | ||
|   | b23548eeff | ||
|   | 262317192c | ||
|   | 8b75b8b837 | ||
|   | 2170c481ce | ||
|   | dfbf9c4542 | ||
|   | 964a1bbf39 | ||
|   | 228e225f84 | ||
|   | bd6435c982 | ||
|   | 591023a1f0 | ||
|   | 1ab23b5e0e | ||
|   | d193519329 | ||
|   | 2406ecdfea | ||
|   | 7266154d54 | ||
|   | 4797136965 | ||
|   | 6d78af6144 | ||
|   | 7728e35c52 | ||
|   | 5a61fd84ad | ||
|   | ad0c449a75 | ||
|   | 1c330185c4 | ||
|   | 8668fef136 | ||
|   | 7491b327f8 | ||
|   | abb5b05d49 | ||
|   | b6ec9dad28 | ||
|   | caa6e8cf01 | ||
|   | ffb932390f | ||
|   | a8efaee1f3 | ||
|   | 4c2afb5c28 | ||
|   | 809f517db8 | ||
|   | a4b105dedb | ||
|   | 10acf638f8 | ||
|   | ea62bc5a34 | ||
|   | f65ffe2812 | ||
|   | 23bb76397a | ||
|   | 859a330e6c | ||
|   | 86ac511763 | ||
|   | f2e98ef8a4 | ||
|   | 495d999b6c | ||
|   | 6d1af85e80 | ||
|   | 1db091b381 | ||
|   | 0b9124d4fd | ||
|   | 6c6607ae68 | ||
|   | 83d80857fd | ||
|   | 98fa3855bd | ||
|   | 9440bc5d72 | ||
|   | 95753ebf1c | ||
|   | f8c6795119 | ||
|   | 7033f3e72b | ||
|   | e3101b7aa8 | ||
|   | c747f160aa | ||
|   | c8748a2948 | ||
|   | 487c8d7c29 | ||
|   | 69fa7ed16e | ||
|   | 5336155365 | ||
|   | 4feb74cb89 | ||
|   | 4a4cf552af | ||
|   | 0f59b8f329 | ||
|   | f480160e2d | ||
|   | 4832a2a1e9 | ||
|   | 52ecd84d8a | ||
|   | 30c246c488 | ||
|   | 42014eea23 | ||
|   | c2da396230 | ||
|   | e91c9473be | ||
|   | 13e48c6ca0 | ||
|   | 31e2cb76bb | ||
|   | 91e46a2c53 | ||
|   | a57679f837 | ||
|   | 75f3bce04d | ||
|   | df18375308 | ||
|   | c63737ab3e | ||
|   | 1cdceee347 | ||
|   | 694c434b9e | ||
|   | 62af5c8844 | ||
|   | 56c53909aa | ||
|   | 21a126e4e4 | ||
|   | 8affab1a2b | ||
|   | 12cc53d699 | ||
|   | 2ab832bb89 | ||
|   | 42425d8218 | ||
|   | 6da093a402 | ||
|   | adc3adc13b | ||
|   | 22a79710d8 | ||
|   | 0927553fe4 | ||
|   | 858d8f0ba7 | ||
|   | 8eb945ee9b | ||
|   | dc0fd60d30 | ||
|   | cd44c9f55c | ||
|   | 649f47c345 | ||
|   | 6ca3160b33 | ||
|   | 5f8ed4fc60 | ||
|   | bf0993d2a6 | ||
|   | 5dc8175fc8 | ||
|   | dc6a5a29c1 | ||
|   | e62d9a5242 | ||
|   | 94212ac8b8 | ||
|   | e9e86fccf0 | ||
|   | 58745992ef | ||
|   | 234d634bfe | ||
|   | fdc6902a90 | ||
|   | d8d587fd93 | ||
|   | 92791260a7 | ||
|   | 4dfd851c46 | ||
|   | bc4df74b5e | ||
|   | 666f122a72 | ||
|   | f999c8a87e | ||
|   | 90a32ab75d | ||
|   | 0713fd28da | ||
|   | f5b33e6de8 | ||
|   | fc6043bb4d | ||
|   | bc46e3330a | ||
|   | 5fc7b3ceb5 | ||
|   | 6277af4790 | ||
|   | 00bd0a8af4 | ||
|   | a415573e45 | ||
|   | e68012858e | ||
|   | ca8a5b753c | ||
|   | d1f4ac0f2d | ||
|   | ff357882ac | ||
|   | 934ac2b836 | ||
|   | 1ad50d5982 | ||
|   | 388b016842 | ||
|   | 134a46c00b | ||
|   | 50796643fb | ||
|   | b1838b1d5e | ||
|   | 757b3613fe | ||
|   | ae08811636 | ||
|   | b657c0fe09 | ||
|   | 84df71047c | ||
|   | abc6d720d0 | ||
|   | 80154639e3 | ||
|   | f2117d8331 | ||
|   | 261be6a7b7 | ||
|   | b53a2c1ed9 | ||
|   | ee0df07a3c | ||
|   | 4e363eca2b | ||
|   | 4277405c0e | ||
|   | 6a99f0caf7 | ||
|   | 394af08561 | ||
|   | 6451583e60 | ||
|   | 30cb0a3ab0 | ||
|   | 5680a88267 | ||
|   | 6b089858db | ||
|   | b3ed863021 | ||
|   | 5796c27ed5 | ||
|   | 310e8dd768 | ||
|   | 0b40ac2dbc | ||
|   | f22c8e0882 | ||
|   | a388bb2c95 | ||
|   | e611c44dea | ||
|   | 8e36e2bb67 | ||
|   | 541ad8d899 | ||
|   | 17cc0735d1 | ||
|   | fd336a5503 | ||
|   | 802d1c1861 | ||
|   | 65fe0a1179 | ||
|   | 2d24879fa3 | ||
|   | 75383a95b3 | ||
|   | 95444ea46b | ||
|   | 9f9c01b520 | ||
|   | 285d1eba0d | ||
|   | 0dfd3a421c | ||
|   | 6a1f15b25e | ||
|   | 9f47c324b7 | ||
|   | f0df6084af | ||
|   | 879ca47590 | ||
|   | 6a7efc81c9 | ||
|   | 12c5c553c3 | ||
|   | 988e9b1de3 | ||
|   | db6bbc5187 | ||
|   | c67b4e7b94 | ||
|   | b7a73d3469 | ||
|   | 7f9d88c10a | ||
|   | 79237d2b94 | ||
|   | 9c4ec56491 | ||
|   | 74a8752570 | ||
|   | a8ab4c5003 | ||
|   | 9cee263c91 | ||
|   | c6bf6f59e6 | ||
|   | 4b7aef2196 | ||
|   | f6d0046b5a | ||
|   | 84363266d2 | ||
|   | 9ac8f2a047 | ||
|   | b2b55533b8 | ||
|   | a4cfab689a | ||
|   | c7df39074c | ||
|   | fdcdccb0c2 | ||
|   | e945c1667a | ||
|   | 87a4de4370 | ||
|   | e1e2913b77 | ||
|   | 9be24db410 | ||
|   | 6b61cb3742 | ||
|   | 90b7f2080f | ||
|   | d1f1c72a55 | ||
|   | 1925847ef8 | ||
|   | 8b216b0ca9 | ||
|   | dbfeea99f3 | ||
|   | 5e64bbfa7c | ||
|   | e691a40260 | ||
|   | d812488767 | ||
|   | 3c03690ab7 | ||
|   | 3df27b9c04 | ||
|   | ba45d29b7c | ||
|   | 3cf83f57a8 | ||
|   | 03e4318d79 | ||
|   | 178d134f46 | ||
|   | cbf9c731a0 | ||
|   | de4bfcc43c | ||
|   | 9737978f28 | ||
|   | 5bc7fe2cea | ||
|   | 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 | 
							
								
								
									
										25
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| version: 2 | ||||
| jobs: | ||||
|   test1: | ||||
|     docker: | ||||
|       - image: circleci/golang:1.16-node | ||||
|     working_directory: /go/src/github.com/fatedier/frp | ||||
|     steps: | ||||
|       - checkout | ||||
|       - run: make | ||||
|       - run: make alltest | ||||
|   test2: | ||||
|     docker: | ||||
|       - image: circleci/golang:1.15-node | ||||
|     working_directory: /go/src/github.com/fatedier/frp | ||||
|     steps: | ||||
|       - checkout | ||||
|       - run: make | ||||
|       - run: make alltest | ||||
|  | ||||
| workflows: | ||||
|   version: 2 | ||||
|   build_and_test: | ||||
|     jobs: | ||||
|       - test1 | ||||
|       - test2 | ||||
							
								
								
									
										44
									
								
								.github/ISSUE_TEMPLATE/bug-report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								.github/ISSUE_TEMPLATE/bug-report.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| --- | ||||
| name: Bug Report | ||||
| about: Bug Report for FRP | ||||
| title: '' | ||||
| labels: Requires Testing | ||||
| assignees: '' | ||||
|  | ||||
| --- | ||||
|  | ||||
| <!-- From Chinese to English by machine translation, welcome to revise and polish. --> | ||||
|  | ||||
| <!-- ⚠️⚠️ Incomplete reports will be marked as invalid, and closed, with few exceptions ⚠️⚠️ --> | ||||
| <!-- in addition, please use search well so that the same solution can be found in the feedback, we will close it directly --> | ||||
| <!-- for convenience of differentiation, use FRPS or FRPC to refer to the FRP server or client --> | ||||
|  | ||||
| **[REQUIRED] hat version of frp are you using**  | ||||
| <!-- Use ./frpc -v or ./frps -v --> | ||||
| Version: | ||||
|  | ||||
| **[REQUIRED] What operating system and processor architecture are you using** | ||||
| OS: | ||||
| CPU architecture: | ||||
|  | ||||
| **[REQUIRED] description of errors** | ||||
|  | ||||
| **confile** | ||||
| <!-- Please pay attention to hiding the token, server_addr and other privacy information --> | ||||
|  | ||||
| **log file** | ||||
| <!--  If the file is too large, use Pastebin, for example https://pastebin.ubuntu.com/ --> | ||||
|  | ||||
| **Steps to reproduce the issue** | ||||
| 1.  | ||||
| 2.  | ||||
| 3.  | ||||
|  | ||||
| **Supplementary information** | ||||
|  | ||||
| **Can you guess what caused this issue** | ||||
|  | ||||
| **Checklist**: | ||||
| <!--- Make sure you've completed the following steps (put an "X" between of brackets): --> | ||||
| - [] I included all information required in the sections above | ||||
| - [] I made sure there are no duplicates of this report [(Use Search)](https://github.com/fatedier/frp/issues?q=is%3Aissue) | ||||
							
								
								
									
										5
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| blank_issues_enabled: false | ||||
| contact_links: | ||||
|   - name: DOCS | ||||
|     url: https://github.com/fatedier/frp | ||||
|     about: Here you can find out how to configure frp. | ||||
							
								
								
									
										22
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| --- | ||||
| name: Feature request | ||||
| about: Suggest an idea for this project | ||||
| title: '' | ||||
| labels: "[+] Enhancement" | ||||
| assignees: '' | ||||
|  | ||||
| --- | ||||
|  | ||||
| <!-- From Chinese to English by machine translation, welcome to revise and polish. --> | ||||
|  | ||||
| **The solution you want** | ||||
| <!--A clear and concise description of the solution you want. --> | ||||
|  | ||||
| **Alternatives considered** | ||||
| <!--A clear and concise description of any alternative solutions or features you have considered. --> | ||||
|  | ||||
| **How to implement this function** | ||||
| <!--Implementation steps for the solution you want. --> | ||||
|  | ||||
| **Application scenarios of this function** | ||||
| <!--Make a clear and concise description of the application scenario of the solution you want. --> | ||||
							
								
								
									
										117
									
								
								.github/workflows/build-and-push-image.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								.github/workflows/build-and-push-image.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| name: Build Image and Publish to Dockerhub & GPR | ||||
|  | ||||
| on: | ||||
|   release: | ||||
|     types: [ created ] | ||||
|   workflow_dispatch: | ||||
|     inputs: | ||||
|       tag: | ||||
|         description: 'Image tag' | ||||
|         required: true | ||||
|         default: 'test' | ||||
| jobs: | ||||
|   binary: | ||||
|     name: Build Golang project | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Set up Go 1.x | ||||
|         uses: actions/setup-go@v2 | ||||
|         with: | ||||
|           go-version: 1.16 | ||||
|  | ||||
|       - run: | | ||||
|           # https://github.com/actions/setup-go/issues/107 | ||||
|           cp -f `which go` /usr/bin/go | ||||
|  | ||||
|       - run: go version | ||||
|  | ||||
|       - name: Check out code into the Go module directory | ||||
|         uses: actions/checkout@v2 | ||||
|  | ||||
|       - name: Build | ||||
|         run: make build | ||||
|  | ||||
|       - name: Archive artifacts for frpc | ||||
|         uses: actions/upload-artifact@v1 | ||||
|         with: | ||||
|           name: frpc | ||||
|           path: bin/frpc | ||||
|  | ||||
|       - name: Archive artifacts for frps | ||||
|         uses: actions/upload-artifact@v1 | ||||
|         with: | ||||
|           name: frps | ||||
|           path: bin/frps | ||||
|  | ||||
|   image: | ||||
|     name: Build Image from Dockerfile and binaries | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: binary | ||||
|     steps: | ||||
|       # environment | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: '0' | ||||
|  | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v1 | ||||
|  | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v1 | ||||
|  | ||||
|       # download binaries of frpc and frps | ||||
|       - name: Download binary of frpc | ||||
|         uses: actions/download-artifact@v2 | ||||
|         with: | ||||
|           name: frpc | ||||
|           path: bin/frpc | ||||
|  | ||||
|       - name: Download binary of frps | ||||
|         uses: actions/download-artifact@v2 | ||||
|         with: | ||||
|           name: frps | ||||
|           path: bin/frps | ||||
|  | ||||
|       # get image tag name | ||||
|       - name: Get Image Tag Name | ||||
|         run: | | ||||
|           if [ x${{ github.event.inputs.tag }} == x"" ]; then | ||||
|             echo "TAG_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV | ||||
|           else | ||||
|             echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV | ||||
|           fi | ||||
|  | ||||
|       # prepare image tags | ||||
|       - name: Prepare Image Tags | ||||
|         run: | | ||||
|           echo "DOCKERFILE_FRPC_PATH=dockerfiles/Dockerfile-for-frpc" >> $GITHUB_ENV | ||||
|           echo "DOCKERFILE_FRPS_PATH=dockerfiles/Dockerfile-for-frps" >> $GITHUB_ENV | ||||
|           echo "TAG_FRPC=fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV | ||||
|           echo "TAG_FRPS=fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV | ||||
|           echo "TAG_FRPC_GPR=ghcr.io/fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV | ||||
|           echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV | ||||
|  | ||||
|       # build images | ||||
|       - name: Build Images | ||||
|         run: | | ||||
|           # for Docker hub | ||||
|           docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC }} . | ||||
|           docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS }} . | ||||
|           # for GPR | ||||
|           docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC_GPR }} . | ||||
|           docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS_GPR }} . | ||||
|  | ||||
|       # push to dockerhub | ||||
|       - name: Publish to Dockerhub | ||||
|         run: | | ||||
|           echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login --username ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin | ||||
|           docker push ${{ env.TAG_FRPC }} | ||||
|           docker push ${{ env.TAG_FRPS }} | ||||
|  | ||||
|       # push to gpr | ||||
|       - name: Publish to GPR | ||||
|         run: | | ||||
|           echo ${{ secrets.GPR_TOKEN }} | docker login ghcr.io --username ${{ github.repository_owner }} --password-stdin | ||||
|           docker push ${{ env.TAG_FRPC_GPR }} | ||||
|           docker push ${{ env.TAG_FRPS_GPR }} | ||||
							
								
								
									
										34
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| name: goreleaser | ||||
|  | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|  | ||||
| jobs: | ||||
|   goreleaser: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v2 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: Set up Go | ||||
|         uses: actions/setup-go@v2 | ||||
|         with: | ||||
|           go-version: 1.16 | ||||
|            | ||||
|       - run: | | ||||
|           # https://github.com/actions/setup-go/issues/107 | ||||
|           cp -f `which go` /usr/bin/go | ||||
|  | ||||
|       - name: Make All | ||||
|         run: | | ||||
|           ./package.sh | ||||
|  | ||||
|       - name: Run GoReleaser | ||||
|         uses: goreleaser/goreleaser-action@v2 | ||||
|         with: | ||||
|           version: latest | ||||
|           args: release --rm-dist --release-notes=./Release.md | ||||
|         env: | ||||
|           GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }} | ||||
							
								
								
									
										26
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| name: "Close stale issues" | ||||
| on: | ||||
|   schedule: | ||||
|   - cron: "20 0 * * *" | ||||
|   workflow_dispatch: | ||||
|     inputs: | ||||
|       debug-only: | ||||
|         description: 'In debug mod' | ||||
|         required: false | ||||
|         default: 'false' | ||||
| jobs: | ||||
|   stale: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|     - uses: actions/stale@v3 | ||||
|       with: | ||||
|         repo-token: ${{ secrets.GITHUB_TOKEN }} | ||||
|         stale-issue-message: 'Issues go stale after 45d of inactivity. Stale issues rot after an additional 10d of inactivity and eventually close.' | ||||
|         stale-pr-message: 'Issues go stale after 45d of inactivity. Stale issues rot after an additional 10d of inactivity and eventually close.' | ||||
|         stale-issue-label: 'lifecycle/stale' | ||||
|         exempt-issue-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned' | ||||
|         stale-pr-label: 'lifecycle/stale' | ||||
|         exempt-pr-labels: 'bug,doc,enhancement,future,proposal,question,testing,todo,easy,help wanted,assigned' | ||||
|         days-before-stale: 45 | ||||
|         days-before-close: 10 | ||||
|         debug-only: ${{ github.event.inputs.debug-only }} | ||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -25,6 +25,12 @@ _testmain.go | ||||
|  | ||||
| # Self | ||||
| bin/ | ||||
| packages/ | ||||
| release/ | ||||
| test/bin/ | ||||
| vendor/ | ||||
| dist/ | ||||
| .idea/ | ||||
|  | ||||
| # Cache | ||||
| *.swp | ||||
|   | ||||
							
								
								
									
										19
									
								
								.goreleaser.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.goreleaser.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| builds: | ||||
|   - skip: true | ||||
| checksum: | ||||
|   name_template: 'checksums.txt' | ||||
| release: | ||||
|   # Same as for github | ||||
|   # Note: it can only be one: either github, gitlab or gitea | ||||
|   github: | ||||
|     owner: fatedier | ||||
|     name: frp | ||||
|  | ||||
|   draft: false | ||||
|  | ||||
|   # You can add extra pre-existing files to the release. | ||||
|   # The filename on the release will be the last part of the path (base). If | ||||
|   # another file with the same name exists, the latest one found will be used. | ||||
|   # Defaults to empty. | ||||
|   extra_files: | ||||
|     - glob: ./release/packages/* | ||||
							
								
								
									
										12
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,12 +0,0 @@ | ||||
| sudo: false | ||||
| language: go | ||||
|  | ||||
| go: | ||||
|     - 1.4.2 | ||||
|     - 1.5.3 | ||||
|  | ||||
| install: | ||||
|     - make | ||||
|  | ||||
| script: | ||||
|     - make test | ||||
							
								
								
									
										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 | ||||
							
								
								
									
										13
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,13 +0,0 @@ | ||||
| Copyright 2014 astaxie | ||||
|  | ||||
| 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. | ||||
							
								
								
									
										63
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										63
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,63 +0,0 @@ | ||||
| ## logs | ||||
| logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` . | ||||
|  | ||||
|  | ||||
| ## How to install? | ||||
|  | ||||
| 	go get github.com/astaxie/beego/logs | ||||
|  | ||||
|  | ||||
| ## What adapters are supported? | ||||
|  | ||||
| As of now this logs support console, file,smtp and conn. | ||||
|  | ||||
|  | ||||
| ## How to use it? | ||||
|  | ||||
| First you must import it | ||||
|  | ||||
| 	import ( | ||||
| 		"github.com/astaxie/beego/logs" | ||||
| 	) | ||||
|  | ||||
| Then init a Log (example with console adapter) | ||||
|  | ||||
| 	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") | ||||
|  | ||||
|  | ||||
| ## File adapter | ||||
|  | ||||
| Configure file adapter like this: | ||||
|  | ||||
| 	log := NewLogger(10000) | ||||
| 	log.SetLogger("file", `{"filename":"test.log"}`) | ||||
|  | ||||
|  | ||||
| ## Conn adapter | ||||
|  | ||||
| Configure like this: | ||||
|  | ||||
| 	log := NewLogger(1000) | ||||
| 	log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) | ||||
| 	log.Info("info") | ||||
|  | ||||
|  | ||||
| ## Smtp adapter | ||||
|  | ||||
| Configure like this: | ||||
|  | ||||
| 	log := NewLogger(10000) | ||||
| 	log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) | ||||
| 	log.Critical("sendmail critical") | ||||
| 	time.Sleep(time.Second * 30) | ||||
							
								
								
									
										116
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/conn.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										116
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/conn.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,116 +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" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net" | ||||
| ) | ||||
|  | ||||
| // ConnWriter implements LoggerInterface. | ||||
| // it writes messages in keep-live tcp connection. | ||||
| type ConnWriter struct { | ||||
| 	lg             *log.Logger | ||||
| 	innerWriter    io.WriteCloser | ||||
| 	ReconnectOnMsg bool   `json:"reconnectOnMsg"` | ||||
| 	Reconnect      bool   `json:"reconnect"` | ||||
| 	Net            string `json:"net"` | ||||
| 	Addr           string `json:"addr"` | ||||
| 	Level          int    `json:"level"` | ||||
| } | ||||
|  | ||||
| // create new ConnWrite returning as LoggerInterface. | ||||
| func NewConn() LoggerInterface { | ||||
| 	conn := new(ConnWriter) | ||||
| 	conn.Level = LevelTrace | ||||
| 	return conn | ||||
| } | ||||
|  | ||||
| // init connection writer with json config. | ||||
| // json config only need key "level". | ||||
| func (c *ConnWriter) Init(jsonconfig string) error { | ||||
| 	return json.Unmarshal([]byte(jsonconfig), c) | ||||
| } | ||||
|  | ||||
| // write message in connection. | ||||
| // if connection is down, try to re-connect. | ||||
| func (c *ConnWriter) WriteMsg(msg string, level int) error { | ||||
| 	if level > c.Level { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if c.neddedConnectOnMsg() { | ||||
| 		err := c.connect() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if c.ReconnectOnMsg { | ||||
| 		defer c.innerWriter.Close() | ||||
| 	} | ||||
| 	c.lg.Println(msg) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // implementing method. empty. | ||||
| func (c *ConnWriter) Flush() { | ||||
|  | ||||
| } | ||||
|  | ||||
| // destroy connection writer and close tcp listener. | ||||
| func (c *ConnWriter) Destroy() { | ||||
| 	if c.innerWriter != nil { | ||||
| 		c.innerWriter.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *ConnWriter) connect() error { | ||||
| 	if c.innerWriter != nil { | ||||
| 		c.innerWriter.Close() | ||||
| 		c.innerWriter = nil | ||||
| 	} | ||||
|  | ||||
| 	conn, err := net.Dial(c.Net, c.Addr) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if tcpConn, ok := conn.(*net.TCPConn); ok { | ||||
| 		tcpConn.SetKeepAlive(true) | ||||
| 	} | ||||
|  | ||||
| 	c.innerWriter = conn | ||||
| 	c.lg = log.New(conn, "", log.Ldate|log.Ltime) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *ConnWriter) neddedConnectOnMsg() bool { | ||||
| 	if c.Reconnect { | ||||
| 		c.Reconnect = false | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if c.innerWriter == nil { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return c.ReconnectOnMsg | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	Register("conn", NewConn) | ||||
| } | ||||
							
								
								
									
										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() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										165
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/smtp.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										165
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/logs/smtp.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,165 +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 ( | ||||
| 	"crypto/tls" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/smtp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| // no usage | ||||
| // subjectPhrase = "Diagnostic message from server" | ||||
| ) | ||||
|  | ||||
| // smtpWriter implements LoggerInterface and is used to send emails via given SMTP-server. | ||||
| type SmtpWriter struct { | ||||
| 	Username           string   `json:"Username"` | ||||
| 	Password           string   `json:"password"` | ||||
| 	Host               string   `json:"Host"` | ||||
| 	Subject            string   `json:"subject"` | ||||
| 	FromAddress        string   `json:"fromAddress"` | ||||
| 	RecipientAddresses []string `json:"sendTos"` | ||||
| 	Level              int      `json:"level"` | ||||
| } | ||||
|  | ||||
| // create smtp writer. | ||||
| func NewSmtpWriter() LoggerInterface { | ||||
| 	return &SmtpWriter{Level: LevelTrace} | ||||
| } | ||||
|  | ||||
| // init smtp writer with json config. | ||||
| // config like: | ||||
| //	{ | ||||
| //		"Username":"example@gmail.com", | ||||
| //		"password:"password", | ||||
| //		"host":"smtp.gmail.com:465", | ||||
| //		"subject":"email title", | ||||
| //		"fromAddress":"from@example.com", | ||||
| //		"sendTos":["email1","email2"], | ||||
| //		"level":LevelError | ||||
| //	} | ||||
| func (s *SmtpWriter) Init(jsonconfig string) error { | ||||
| 	err := json.Unmarshal([]byte(jsonconfig), s) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *SmtpWriter) GetSmtpAuth(host string) smtp.Auth { | ||||
| 	if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return smtp.PlainAuth( | ||||
| 		"", | ||||
| 		s.Username, | ||||
| 		s.Password, | ||||
| 		host, | ||||
| 	) | ||||
| } | ||||
|  | ||||
| func (s *SmtpWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error { | ||||
| 	client, err := smtp.Dial(hostAddressWithPort) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	host, _, _ := net.SplitHostPort(hostAddressWithPort) | ||||
| 	tlsConn := &tls.Config{ | ||||
| 		InsecureSkipVerify: true, | ||||
| 		ServerName:         host, | ||||
| 	} | ||||
| 	if err = client.StartTLS(tlsConn); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if auth != nil { | ||||
| 		if err = client.Auth(auth); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err = client.Mail(fromAddress); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, rec := range recipients { | ||||
| 		if err = client.Rcpt(rec); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	w, err := client.Data() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	_, err = w.Write([]byte(msgContent)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = w.Close() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = client.Quit() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // write message in smtp writer. | ||||
| // it will send an email with subject and only this message. | ||||
| func (s *SmtpWriter) WriteMsg(msg string, level int) error { | ||||
| 	if level > s.Level { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	hp := strings.Split(s.Host, ":") | ||||
|  | ||||
| 	// Set up authentication information. | ||||
| 	auth := s.GetSmtpAuth(hp[0]) | ||||
|  | ||||
| 	// Connect to the server, authenticate, set the sender and recipient, | ||||
| 	// and send the email all in one step. | ||||
| 	content_type := "Content-Type: text/plain" + "; charset=UTF-8" | ||||
| 	mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress + | ||||
| 		">\r\nSubject: " + s.Subject + "\r\n" + content_type + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg) | ||||
|  | ||||
| 	return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg) | ||||
| } | ||||
|  | ||||
| // implementing method. empty. | ||||
| func (s *SmtpWriter) Flush() { | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // implementing method. empty. | ||||
| func (s *SmtpWriter) Destroy() { | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	Register("smtp", NewSmtpWriter) | ||||
| } | ||||
							
								
								
									
										19
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/utils/captcha/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								Godeps/_workspace/src/github.com/astaxie/beego/utils/captcha/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,19 +0,0 @@ | ||||
| Copyright (c) 2011-2014 Dmitry Chestnykh <dmitry@codingrobots.com> | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
							
								
								
									
										25
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,25 +0,0 @@ | ||||
| # Compiled Object files, Static and Dynamic libs (Shared Objects) | ||||
| *.o | ||||
| *.a | ||||
| *.so | ||||
|  | ||||
| # Folders | ||||
| _obj | ||||
| _test | ||||
|  | ||||
| # Architecture specific extensions/prefixes | ||||
| *.[568vq] | ||||
| [568vq].out | ||||
|  | ||||
| *.cgo1.go | ||||
| *.cgo2.c | ||||
| _cgo_defun.c | ||||
| _cgo_gotypes.go | ||||
| _cgo_export.* | ||||
|  | ||||
| _testmain.go | ||||
|  | ||||
| *.exe | ||||
|  | ||||
| # coverage droppings | ||||
| profile.cov | ||||
							
								
								
									
										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 | ||||
							
								
								
									
										20
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								Godeps/_workspace/src/github.com/docopt/docopt-go/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,20 +0,0 @@ | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2013 Keith Batten | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | ||||
| this software and associated documentation files (the "Software"), to deal in | ||||
| the Software without restriction, including without limitation the rights to | ||||
| use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | ||||
| the Software, and to permit persons to whom the Software is furnished to do so, | ||||
| subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | ||||
| FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | ||||
| IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||||
| CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
							
								
								
									
										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} | ||||
							
								
								
									
										14
									
								
								Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,14 +0,0 @@ | ||||
| Copyright (c) 2013 Vaughan Newton | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated | ||||
| documentation files (the "Software"), to deal in the Software without restriction, including without limitation the | ||||
| rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit | ||||
| persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the | ||||
| Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | ||||
| WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | ||||
| COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||||
| OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
							
								
								
									
										70
									
								
								Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										70
									
								
								Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,70 +0,0 @@ | ||||
| go-ini | ||||
| ====== | ||||
|  | ||||
| INI parsing library for Go (golang). | ||||
|  | ||||
| View the API documentation [here](http://godoc.org/github.com/vaughan0/go-ini). | ||||
|  | ||||
| Usage | ||||
| ----- | ||||
|  | ||||
| Parse an INI file: | ||||
|  | ||||
| ```go | ||||
| import "github.com/vaughan0/go-ini" | ||||
|  | ||||
| file, err := ini.LoadFile("myfile.ini") | ||||
| ``` | ||||
|  | ||||
| Get data from the parsed file: | ||||
|  | ||||
| ```go | ||||
| name, ok := file.Get("person", "name") | ||||
| if !ok { | ||||
|   panic("'name' variable missing from 'person' section") | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Iterate through values in a section: | ||||
|  | ||||
| ```go | ||||
| for key, value := range file["mysection"] { | ||||
|   fmt.Printf("%s => %s\n", key, value) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Iterate through sections in a file: | ||||
|  | ||||
| ```go | ||||
| for name, section := range file { | ||||
|   fmt.Printf("Section name: %s\n", name) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| File Format | ||||
| ----------- | ||||
|  | ||||
| INI files are parsed by go-ini line-by-line. Each line may be one of the following: | ||||
|  | ||||
|   * A section definition: [section-name] | ||||
|   * A property: key = value | ||||
|   * A comment: #blahblah _or_ ;blahblah | ||||
|   * Blank. The line will be ignored. | ||||
|  | ||||
| Properties defined before any section headers are placed in the default section, which has | ||||
| the empty string as it's key. | ||||
|  | ||||
| Example: | ||||
|  | ||||
| ```ini | ||||
| # I am a comment | ||||
| ; So am I! | ||||
|  | ||||
| [apples] | ||||
| colour = red or green | ||||
| shape = applish | ||||
|  | ||||
| [oranges] | ||||
| shape = square | ||||
| colour = blue | ||||
| ``` | ||||
							
								
								
									
										123
									
								
								Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										123
									
								
								Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,123 +0,0 @@ | ||||
| // Package ini provides functions for parsing INI configuration files. | ||||
| package ini | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	sectionRegex = regexp.MustCompile(`^\[(.*)\]$`) | ||||
| 	assignRegex  = regexp.MustCompile(`^([^=]+)=(.*)$`) | ||||
| ) | ||||
|  | ||||
| // ErrSyntax is returned when there is a syntax error in an INI file. | ||||
| type ErrSyntax struct { | ||||
| 	Line   int | ||||
| 	Source string // The contents of the erroneous line, without leading or trailing whitespace | ||||
| } | ||||
|  | ||||
| func (e ErrSyntax) Error() string { | ||||
| 	return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source) | ||||
| } | ||||
|  | ||||
| // A File represents a parsed INI file. | ||||
| type File map[string]Section | ||||
|  | ||||
| // A Section represents a single section of an INI file. | ||||
| type Section map[string]string | ||||
|  | ||||
| // Returns a named Section. A Section will be created if one does not already exist for the given name. | ||||
| func (f File) Section(name string) Section { | ||||
| 	section := f[name] | ||||
| 	if section == nil { | ||||
| 		section = make(Section) | ||||
| 		f[name] = section | ||||
| 	} | ||||
| 	return section | ||||
| } | ||||
|  | ||||
| // Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup. | ||||
| func (f File) Get(section, key string) (value string, ok bool) { | ||||
| 	if s := f[section]; s != nil { | ||||
| 		value, ok = s[key] | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Loads INI data from a reader and stores the data in the File. | ||||
| func (f File) Load(in io.Reader) (err error) { | ||||
| 	bufin, ok := in.(*bufio.Reader) | ||||
| 	if !ok { | ||||
| 		bufin = bufio.NewReader(in) | ||||
| 	} | ||||
| 	return parseFile(bufin, f) | ||||
| } | ||||
|  | ||||
| // Loads INI data from a named file and stores the data in the File. | ||||
| func (f File) LoadFile(file string) (err error) { | ||||
| 	in, err := os.Open(file) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer in.Close() | ||||
| 	return f.Load(in) | ||||
| } | ||||
|  | ||||
| func parseFile(in *bufio.Reader, file File) (err error) { | ||||
| 	section := "" | ||||
| 	lineNum := 0 | ||||
| 	for done := false; !done; { | ||||
| 		var line string | ||||
| 		if line, err = in.ReadString('\n'); err != nil { | ||||
| 			if err == io.EOF { | ||||
| 				done = true | ||||
| 			} else { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		lineNum++ | ||||
| 		line = strings.TrimSpace(line) | ||||
| 		if len(line) == 0 { | ||||
| 			// Skip blank lines | ||||
| 			continue | ||||
| 		} | ||||
| 		if line[0] == ';' || line[0] == '#' { | ||||
| 			// Skip comments | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if groups := assignRegex.FindStringSubmatch(line); groups != nil { | ||||
| 			key, val := groups[1], groups[2] | ||||
| 			key, val = strings.TrimSpace(key), strings.TrimSpace(val) | ||||
| 			file.Section(section)[key] = val | ||||
| 		} else if groups := sectionRegex.FindStringSubmatch(line); groups != nil { | ||||
| 			name := strings.TrimSpace(groups[1]) | ||||
| 			section = name | ||||
| 			// Create the section if it does not exist | ||||
| 			file.Section(section) | ||||
| 		} else { | ||||
| 			return ErrSyntax{lineNum, line} | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Loads and returns a File from a reader. | ||||
| func Load(in io.Reader) (File, error) { | ||||
| 	file := make(File) | ||||
| 	err := file.Load(in) | ||||
| 	return file, err | ||||
| } | ||||
|  | ||||
| // Loads and returns an INI File from a file on disk. | ||||
| func LoadFile(filename string) (File, error) { | ||||
| 	file := make(File) | ||||
| 	err := file.LoadFile(filename) | ||||
| 	return file, err | ||||
| } | ||||
							
								
								
									
										2
									
								
								Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,2 +0,0 @@ | ||||
| [default] | ||||
| stuff = things | ||||
							
								
								
									
										46
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,21 +1,47 @@ | ||||
| export PATH := $(GOPATH)/bin:$(PATH) | ||||
| export NEW_GOPATH := $(shell pwd) | ||||
| export GO111MODULE=on | ||||
| LDFLAGS := -s -w | ||||
|  | ||||
| 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 | ||||
| 	env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps | ||||
|  | ||||
| frpc: | ||||
| 	GOPATH=$(NEW_GOPATH) godep go build -o bin/frpc ./src/frp/cmd/frpc | ||||
| 	env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -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 ./cmd/... | ||||
| 	go test -v --cover ./client/... | ||||
| 	go test -v --cover ./server/... | ||||
| 	go test -v --cover ./pkg/... | ||||
|  | ||||
| ci: | ||||
| 	go test -count=1 -p=1 -v ./tests/... | ||||
|  | ||||
| e2e: | ||||
| 	./hack/run-e2e.sh | ||||
|  | ||||
| alltest: gotest ci e2e | ||||
| 	 | ||||
| clean: | ||||
| 	rm -f ./bin/frpc | ||||
| 	rm -f ./bin/frps | ||||
|   | ||||
							
								
								
									
										25
									
								
								Makefile.cross-compiles
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Makefile.cross-compiles
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| export PATH := $(GOPATH)/bin:$(PATH) | ||||
| export GO111MODULE=on | ||||
| LDFLAGS := -s -w | ||||
|  | ||||
| os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm linux:arm64 windows:386 windows:amd64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat | ||||
|  | ||||
| all: build | ||||
|  | ||||
| build: app | ||||
|  | ||||
| app: | ||||
| 	@$(foreach n, $(os-archs),\ | ||||
| 		os=$(shell echo "$(n)" | cut -d : -f 1);\ | ||||
| 		arch=$(shell echo "$(n)" | cut -d : -f 2);\ | ||||
| 		gomips=$(shell echo "$(n)" | cut -d : -f 3);\ | ||||
| 		target_suffix=$${os}_$${arch};\ | ||||
| 		echo "Build $${os}-$${arch}...";\ | ||||
| 		env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_$${target_suffix} ./cmd/frpc;\ | ||||
| 		env CGO_ENABLED=0 GOOS=$${os} GOARCH=$${arch} GOMIPS=$${gomips} go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_$${target_suffix} ./cmd/frps;\ | ||||
| 		echo "Build $${os}-$${arch} done";\ | ||||
| 	) | ||||
| 	@mv ./release/frpc_windows_386 ./release/frpc_windows_386.exe | ||||
| 	@mv ./release/frps_windows_386 ./release/frps_windows_386.exe | ||||
| 	@mv ./release/frpc_windows_amd64 ./release/frpc_windows_amd64.exe | ||||
| 	@mv ./release/frps_windows_amd64 ./release/frps_windows_amd64.exe | ||||
							
								
								
									
										65
									
								
								README_zh.md
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								README_zh.md
									
									
									
									
									
								
							| @@ -1,40 +1,65 @@ | ||||
| # frp | ||||
|  | ||||
| [](https://travis-ci.org/fatedier/frp) | ||||
| [](https://travis-ci.org/fatedier/frp) | ||||
| [](https://github.com/fatedier/frp/releases) | ||||
|  | ||||
| [README](README.md) | [中文文档](README_zh.md) | ||||
|  | ||||
| >frp 是一个高性能的反向代理应用,可以帮助你轻松的进行内网穿透,对外网提供服务。 | ||||
| frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 | ||||
|  | ||||
| ## 为什么使用 frp ? | ||||
|  | ||||
| 通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括: | ||||
|  | ||||
| * 客户端服务端通信支持 TCP、KCP 以及 Websocket 等多种协议。 | ||||
| * 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间。 | ||||
| * 代理组间的负载均衡。 | ||||
| * 端口复用,多个服务通过同一个服务端端口暴露。 | ||||
| * 多个原生支持的客户端插件(静态文件查看,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。 | ||||
| * 高度扩展性的服务端插件系统,方便结合自身需求进行功能扩展。 | ||||
| * 服务端和客户端 UI 页面。 | ||||
|  | ||||
| ## 开发状态 | ||||
|  | ||||
| frp 目前正在前期开发阶段,master分支用于发布稳定版本,dev分支用于开发,您可以尝试下载最新的 release 版本进行测试。 | ||||
| frp 目前已被很多公司广泛用于测试、生产环境。 | ||||
|  | ||||
| **在 1.x 版本以前,交互协议都可能会被改变,不能保证向后兼容。** | ||||
| master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。 | ||||
|  | ||||
| ## 快速开始 | ||||
| ## 文档 | ||||
|  | ||||
| [QuickStart](doc/quick_start_en.md) | [使用文档](doc/quick_start_zh.md) | ||||
| 完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。 | ||||
|  | ||||
| ## 架构 | ||||
| ## 为 frp 做贡献 | ||||
|  | ||||
|  | ||||
| frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进步贡献力量。 | ||||
|  | ||||
| ## frp 的作用? | ||||
| * 在使用过程中出现任何问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来反馈。 | ||||
| * Bug 的修复可以直接提交 Pull Request 到 dev 分支。 | ||||
| * 如果是增加新的功能特性,请先创建一个 issue 并做简单描述以及大致的实现方法,提议被采纳后,就可以创建一个实现新特性的 Pull Request。 | ||||
| * 欢迎对说明文档做出改善,帮助更多的人使用 frp,特别是英文文档。 | ||||
| * 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。 | ||||
| * 如果你有任何其他方面的问题或合作,欢迎发送邮件至 fatedier@gmail.com 。 | ||||
|  | ||||
| * 利用处于内网或防火墙后的机器,对外网环境提供http服务。(针对http的优化正在开发中) | ||||
| * 利用处于内网或防火墙后的机器,对外网环境提供tcp服务。 | ||||
| * 可查看通过代理的所有http请求和响应信息。(待开发) | ||||
| **提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。** | ||||
|  | ||||
| ## 贡献代码 | ||||
| ## 捐助 | ||||
|  | ||||
| 如果您对这个项目感兴趣,并且想要参与其中,我们非常欢迎! | ||||
| 如果您觉得 frp 对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。 | ||||
|  | ||||
| * 如果您需要提交问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来完成。 | ||||
| * 如果您有新的功能需求,可以反馈至 fatedier@gmail.com 共同讨论。 | ||||
| ### 知识星球 | ||||
|  | ||||
| ## 贡献者 | ||||
| 如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群: | ||||
|  | ||||
| * [fatedier](https://github.com/fatedier) | ||||
| * [Hurricanezwf](https://github.com/Hurricanezwf) | ||||
| * [vashstorm](https://github.com/vashstorm) | ||||
|  | ||||
|  | ||||
| ### 支付宝扫码捐赠 | ||||
|  | ||||
|  | ||||
|  | ||||
| ### 微信支付捐赠 | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Paypal 捐赠 | ||||
|  | ||||
| 海外用户推荐通过 [Paypal](https://www.paypal.me/fatedier) 向我的账户 **fatedier@gmail.com** 进行捐赠。 | ||||
|   | ||||
							
								
								
									
										0
									
								
								Release.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								Release.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										77
									
								
								assets/assets.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								assets/assets.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| // 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 | ||||
| 		} | ||||
| 		defer file.Close() | ||||
| 		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 | ||||
| 		} | ||||
| 		defer file.Close() | ||||
| 		buf, err := ioutil.ReadAll(file) | ||||
| 		if err != nil { | ||||
| 			return content, err | ||||
| 		} | ||||
| 		content = string(buf) | ||||
| 	} | ||||
| 	return content, err | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								assets/frpc/static/535877f50039c0cb49a6196a5b7517cd.woff
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/frpc/static/535877f50039c0cb49a6196a5b7517cd.woff
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/frpc/static/732389ded34cb9c52dd88271f1345af9.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/frpc/static/732389ded34cb9c52dd88271f1345af9.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?5a0eb52788515d02ca46"></script><script type="text/javascript" src="vendor.js?3e5221f064f1295497bf"></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,u,c){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 u)Object.prototype.hasOwnProperty.call(u,i)&&(e[i]=u[i]);for(r&&r(t,u,c);s.length;)s.shift()();if(c)for(l=0;l<c.length;l++)f=n(n.s=c[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 u=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=u;var c=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:"3e5221f064f1295497bf"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,c.appendChild(i),u},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/535877f50039c0cb49a6196a5b7517cd.woff
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/frps/static/535877f50039c0cb49a6196a5b7517cd.woff
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								assets/frps/static/732389ded34cb9c52dd88271f1345af9.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/frps/static/732389ded34cb9c52dd88271f1345af9.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?41dbccdbc87e6bcbd79c"></script><script type="text/javascript" src="vendor.js?d7109b07f8f86bab2eeb"></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,u,c){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 u)Object.prototype.hasOwnProperty.call(u,i)&&(e[i]=u[i]);for(r&&r(t,u,c);s.length;)s.shift()();if(c)for(l=0;l<c.length;l++)f=n(n.s=c[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 u=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=u;var c=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:"d7109b07f8f86bab2eeb"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,c.appendChild(i),u},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
											
										
									
								
							
							
								
								
									
										69
									
								
								client/admin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								client/admin.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| // 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 ( | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/assets" | ||||
| 	frpNet "github.com/fatedier/frp/pkg/util/net" | ||||
|  | ||||
| 	"github.com/gorilla/mux" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	httpServerReadTimeout  = 10 * time.Second | ||||
| 	httpServerWriteTimeout = 10 * time.Second | ||||
| ) | ||||
|  | ||||
| func (svr *Service) RunAdminServer(address string) (err error) { | ||||
| 	// url router | ||||
| 	router := mux.NewRouter() | ||||
|  | ||||
| 	user, passwd := svr.cfg.AdminUser, svr.cfg.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) | ||||
| 	}) | ||||
|  | ||||
| 	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 | ||||
| } | ||||
							
								
								
									
										317
									
								
								client/admin_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										317
									
								
								client/admin_api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,317 @@ | ||||
| // 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/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/util/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("api request [/api/reload]") | ||||
| 	defer func() { | ||||
| 		log.Info("api response [/api/reload], code [%d]", res.Code) | ||||
| 		w.WriteHeader(res.Code) | ||||
| 		if len(res.Msg) > 0 { | ||||
| 			w.Write([]byte(res.Msg)) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	_, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(svr.cfgFile) | ||||
| 	if err != nil { | ||||
| 		res.Code = 400 | ||||
| 		res.Msg = err.Error() | ||||
| 		log.Warn("reload frpc proxy config error: %s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err = svr.ReloadConf(pxyCfgs, visitorCfgs); 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"` | ||||
| 	SUDP  []ProxyStatusResp `json:"sudp"` | ||||
| } | ||||
|  | ||||
| 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.WorkingStatus, serverAddr string) ProxyStatusResp { | ||||
| 	psr := ProxyStatusResp{ | ||||
| 		Name:   status.Name, | ||||
| 		Type:   status.Type, | ||||
| 		Status: status.Phase, | ||||
| 		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", serverAddr, cfg.RemotePort) | ||||
| 		} else { | ||||
| 			psr.RemoteAddr = 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", serverAddr, cfg.RemotePort) | ||||
| 		} else { | ||||
| 			psr.RemoteAddr = 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 | ||||
| 	case *config.SUDPProxyConf: | ||||
| 		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) | ||||
| 	res.SUDP = 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, svr.cfg.ServerAddr)) | ||||
| 		case "udp": | ||||
| 			res.UDP = append(res.UDP, NewProxyStatusResp(status, svr.cfg.ServerAddr)) | ||||
| 		case "http": | ||||
| 			res.HTTP = append(res.HTTP, NewProxyStatusResp(status, svr.cfg.ServerAddr)) | ||||
| 		case "https": | ||||
| 			res.HTTPS = append(res.HTTPS, NewProxyStatusResp(status, svr.cfg.ServerAddr)) | ||||
| 		case "stcp": | ||||
| 			res.STCP = append(res.STCP, NewProxyStatusResp(status, svr.cfg.ServerAddr)) | ||||
| 		case "xtcp": | ||||
| 			res.XTCP = append(res.XTCP, NewProxyStatusResp(status, svr.cfg.ServerAddr)) | ||||
| 		case "sudp": | ||||
| 			res.SUDP = append(res.SUDP, NewProxyStatusResp(status, svr.cfg.ServerAddr)) | ||||
| 		} | ||||
| 	} | ||||
| 	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)) | ||||
| 	sort.Sort(ByProxyStatusResp(res.SUDP)) | ||||
| 	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 svr.cfgFile == "" { | ||||
| 		res.Code = 400 | ||||
| 		res.Msg = "frpc has no config file path" | ||||
| 		log.Warn("%s", res.Msg) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	content, err := config.GetRenderedConfFromFile(svr.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(string(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(svr.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) | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		newRows = tmpRows | ||||
| 	} | ||||
| 	content = strings.Join(newRows, "\n") | ||||
|  | ||||
| 	err = ioutil.WriteFile(svr.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 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										383
									
								
								client/control.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										383
									
								
								client/control.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,383 @@ | ||||
| // 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 ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"runtime/debug" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/client/proxy" | ||||
| 	"github.com/fatedier/frp/pkg/auth" | ||||
| 	"github.com/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/msg" | ||||
| 	"github.com/fatedier/frp/pkg/transport" | ||||
| 	frpNet "github.com/fatedier/frp/pkg/util/net" | ||||
| 	"github.com/fatedier/frp/pkg/util/xlog" | ||||
|  | ||||
| 	"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.Manager | ||||
|  | ||||
| 	// manage all visitors | ||||
| 	vm *VisitorManager | ||||
|  | ||||
| 	// control connection | ||||
| 	conn net.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 | ||||
|  | ||||
| 	// The client configuration | ||||
| 	clientCfg config.ClientCommonConf | ||||
|  | ||||
| 	readerShutdown     *shutdown.Shutdown | ||||
| 	writerShutdown     *shutdown.Shutdown | ||||
| 	msgHandlerShutdown *shutdown.Shutdown | ||||
|  | ||||
| 	// The UDP port that the server is listening on | ||||
| 	serverUDPPort int | ||||
|  | ||||
| 	mu sync.RWMutex | ||||
|  | ||||
| 	xl *xlog.Logger | ||||
|  | ||||
| 	// service context | ||||
| 	ctx context.Context | ||||
|  | ||||
| 	// sets authentication based on selected method | ||||
| 	authSetter auth.Setter | ||||
| } | ||||
|  | ||||
| func NewControl(ctx context.Context, runID string, conn net.Conn, session *fmux.Session, | ||||
| 	clientCfg config.ClientCommonConf, | ||||
| 	pxyCfgs map[string]config.ProxyConf, | ||||
| 	visitorCfgs map[string]config.VisitorConf, | ||||
| 	serverUDPPort int, | ||||
| 	authSetter auth.Setter) *Control { | ||||
|  | ||||
| 	// new xlog instance | ||||
| 	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{}), | ||||
| 		clientCfg:          clientCfg, | ||||
| 		readerShutdown:     shutdown.New(), | ||||
| 		writerShutdown:     shutdown.New(), | ||||
| 		msgHandlerShutdown: shutdown.New(), | ||||
| 		serverUDPPort:      serverUDPPort, | ||||
| 		xl:                 xlog.FromContextSafe(ctx), | ||||
| 		ctx:                ctx, | ||||
| 		authSetter:         authSetter, | ||||
| 	} | ||||
| 	ctl.pm = proxy.NewManager(ctl.ctx, ctl.sendCh, clientCfg, serverUDPPort) | ||||
|  | ||||
| 	ctl.vm = NewVisitorManager(ctl.ctx, 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) { | ||||
| 	xl := ctl.xl | ||||
| 	workConn, err := ctl.connectServer() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	m := &msg.NewWorkConn{ | ||||
| 		RunID: ctl.runID, | ||||
| 	} | ||||
| 	if err = ctl.authSetter.SetNewWorkConn(m); err != nil { | ||||
| 		xl.Warn("error during NewWorkConn authentication: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	if err = msg.WriteMsg(workConn, m); err != nil { | ||||
| 		xl.Warn("work connection write to server error: %v", err) | ||||
| 		workConn.Close() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var startMsg msg.StartWorkConn | ||||
| 	if err = msg.ReadMsgInto(workConn, &startMsg); err != nil { | ||||
| 		xl.Error("work connection closed before response StartWorkConn message: %v", err) | ||||
| 		workConn.Close() | ||||
| 		return | ||||
| 	} | ||||
| 	if startMsg.Error != "" { | ||||
| 		xl.Error("StartWorkConn contains error: %s", startMsg.Error) | ||||
| 		workConn.Close() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// dispatch this work connection to related proxy | ||||
| 	ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg) | ||||
| } | ||||
|  | ||||
| func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) { | ||||
| 	xl := ctl.xl | ||||
| 	// 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 { | ||||
| 		xl.Warn("[%s] start error: %v", inMsg.ProxyName, err) | ||||
| 	} else { | ||||
| 		xl.Info("[%s] start proxy success", inMsg.ProxyName) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (ctl *Control) Close() error { | ||||
| 	ctl.pm.Close() | ||||
| 	ctl.conn.Close() | ||||
| 	ctl.vm.Close() | ||||
| 	if ctl.session != nil { | ||||
| 		ctl.session.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 net.Conn, err error) { | ||||
| 	xl := ctl.xl | ||||
| 	if ctl.clientCfg.TCPMux { | ||||
| 		stream, errRet := ctl.session.OpenStream() | ||||
| 		if errRet != nil { | ||||
| 			err = errRet | ||||
| 			xl.Warn("start new connection to server error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 		conn = stream | ||||
| 	} else { | ||||
| 		var tlsConfig *tls.Config | ||||
| 		sn := ctl.clientCfg.TLSServerName | ||||
| 		if sn == "" { | ||||
| 			sn = ctl.clientCfg.ServerAddr | ||||
| 		} | ||||
|  | ||||
| 		if ctl.clientCfg.TLSEnable { | ||||
| 			tlsConfig, err = transport.NewClientTLSConfig( | ||||
| 				ctl.clientCfg.TLSCertFile, | ||||
| 				ctl.clientCfg.TLSKeyFile, | ||||
| 				ctl.clientCfg.TLSTrustedCaFile, | ||||
| 				sn) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				xl.Warn("fail to build tls configuration when connecting to server, err: %v", err) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		address := net.JoinHostPort(ctl.clientCfg.ServerAddr, strconv.Itoa(ctl.clientCfg.ServerPort)) | ||||
| 		conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HTTPProxy, ctl.clientCfg.Protocol, address, tlsConfig) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			xl.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() { | ||||
| 	xl := ctl.xl | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			xl.Error("panic error: %v", err) | ||||
| 			xl.Error(string(debug.Stack())) | ||||
| 		} | ||||
| 	}() | ||||
| 	defer ctl.readerShutdown.Done() | ||||
| 	defer close(ctl.closedCh) | ||||
|  | ||||
| 	encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token)) | ||||
| 	for { | ||||
| 		m, err := msg.ReadMsg(encReader) | ||||
| 		if err != nil { | ||||
| 			if err == io.EOF { | ||||
| 				xl.Debug("read from control connection EOF") | ||||
| 				return | ||||
| 			} | ||||
| 			xl.Warn("read error: %v", err) | ||||
| 			ctl.conn.Close() | ||||
| 			return | ||||
| 		} | ||||
| 		ctl.readCh <- m | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // writer writes messages got from sendCh to frps | ||||
| func (ctl *Control) writer() { | ||||
| 	xl := ctl.xl | ||||
| 	defer ctl.writerShutdown.Done() | ||||
| 	encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token)) | ||||
| 	if err != nil { | ||||
| 		xl.Error("crypto new writer error: %v", err) | ||||
| 		ctl.conn.Close() | ||||
| 		return | ||||
| 	} | ||||
| 	for { | ||||
| 		m, ok := <-ctl.sendCh | ||||
| 		if !ok { | ||||
| 			xl.Info("control writer is closing") | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if err := msg.WriteMsg(encWriter, m); err != nil { | ||||
| 			xl.Warn("write message to control connection error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // msgHandler handles all channel events and do corresponding operations. | ||||
| func (ctl *Control) msgHandler() { | ||||
| 	xl := ctl.xl | ||||
| 	defer func() { | ||||
| 		if err := recover(); err != nil { | ||||
| 			xl.Error("panic error: %v", err) | ||||
| 			xl.Error(string(debug.Stack())) | ||||
| 		} | ||||
| 	}() | ||||
| 	defer ctl.msgHandlerShutdown.Done() | ||||
|  | ||||
| 	hbSend := time.NewTicker(time.Duration(ctl.clientCfg.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 | ||||
| 			xl.Debug("send heartbeat to server") | ||||
| 			pingMsg := &msg.Ping{} | ||||
| 			if err := ctl.authSetter.SetPing(pingMsg); err != nil { | ||||
| 				xl.Warn("error during ping authentication: %v", err) | ||||
| 				return | ||||
| 			} | ||||
| 			ctl.sendCh <- pingMsg | ||||
| 		case <-hbCheck.C: | ||||
| 			if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartbeatTimeout)*time.Second { | ||||
| 				xl.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: | ||||
| 				if m.Error != "" { | ||||
| 					xl.Error("Pong contains error: %s", m.Error) | ||||
| 					ctl.conn.Close() | ||||
| 					return | ||||
| 				} | ||||
| 				ctl.lastPong = time.Now() | ||||
| 				xl.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) | ||||
| 		if ctl.session != nil { | ||||
| 			ctl.session.Close() | ||||
| 		} | ||||
| 		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/pkg/msg" | ||||
| ) | ||||
|  | ||||
| type Type int | ||||
|  | ||||
| const ( | ||||
| 	EvStartProxy Type = iota | ||||
| 	EvCloseProxy | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrPayloadType = errors.New("error payload type") | ||||
| ) | ||||
|  | ||||
| type Handler func(evType Type, payload interface{}) error | ||||
|  | ||||
| type StartProxyPayload struct { | ||||
| 	NewProxyMsg *msg.NewProxy | ||||
| } | ||||
|  | ||||
| type CloseProxyPayload struct { | ||||
| 	CloseProxyMsg *msg.CloseProxy | ||||
| } | ||||
							
								
								
									
										171
									
								
								client/health/health.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								client/health/health.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,171 @@ | ||||
| // 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" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/pkg/util/xlog" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrHealthCheckType = errors.New("error health check type") | ||||
| ) | ||||
|  | ||||
| type Monitor 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 | ||||
| } | ||||
|  | ||||
| func NewMonitor(ctx context.Context, checkType string, | ||||
| 	intervalS int, timeoutS int, maxFailedTimes int, | ||||
| 	addr string, url string, | ||||
| 	statusNormalFn func(), statusFailedFn func()) *Monitor { | ||||
|  | ||||
| 	if intervalS <= 0 { | ||||
| 		intervalS = 10 | ||||
| 	} | ||||
| 	if timeoutS <= 0 { | ||||
| 		timeoutS = 3 | ||||
| 	} | ||||
| 	if maxFailedTimes <= 0 { | ||||
| 		maxFailedTimes = 1 | ||||
| 	} | ||||
| 	newctx, cancel := context.WithCancel(ctx) | ||||
| 	return &Monitor{ | ||||
| 		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:            newctx, | ||||
| 		cancel:         cancel, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (monitor *Monitor) Start() { | ||||
| 	go monitor.checkWorker() | ||||
| } | ||||
|  | ||||
| func (monitor *Monitor) Stop() { | ||||
| 	monitor.cancel() | ||||
| } | ||||
|  | ||||
| func (monitor *Monitor) checkWorker() { | ||||
| 	xl := xlog.FromContextSafe(monitor.ctx) | ||||
| 	for { | ||||
| 		doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout)) | ||||
| 		err := monitor.doCheck(doCtx) | ||||
|  | ||||
| 		// check if this monitor has been closed | ||||
| 		select { | ||||
| 		case <-monitor.ctx.Done(): | ||||
| 			cancel() | ||||
| 			return | ||||
| 		default: | ||||
| 			cancel() | ||||
| 		} | ||||
|  | ||||
| 		if err == nil { | ||||
| 			xl.Trace("do one health check success") | ||||
| 			if !monitor.statusOK && monitor.statusNormalFn != nil { | ||||
| 				xl.Info("health check status change to success") | ||||
| 				monitor.statusOK = true | ||||
| 				monitor.statusNormalFn() | ||||
| 			} | ||||
| 		} else { | ||||
| 			xl.Warn("do one health check failed: %v", err) | ||||
| 			monitor.failedTimes++ | ||||
| 			if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil { | ||||
| 				xl.Warn("health check status change to failed") | ||||
| 				monitor.statusOK = false | ||||
| 				monitor.statusFailedFn() | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		time.Sleep(monitor.interval) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (monitor *Monitor) 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 *Monitor) 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 *Monitor) 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 | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
| 	io.Copy(ioutil.Discard, resp.Body) | ||||
|  | ||||
| 	if resp.StatusCode/100 != 2 { | ||||
| 		return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										810
									
								
								client/proxy/proxy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										810
									
								
								client/proxy/proxy.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,810 @@ | ||||
| // 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" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/msg" | ||||
| 	plugin "github.com/fatedier/frp/pkg/plugin/client" | ||||
| 	"github.com/fatedier/frp/pkg/proto/udp" | ||||
| 	"github.com/fatedier/frp/pkg/util/limit" | ||||
| 	frpNet "github.com/fatedier/frp/pkg/util/net" | ||||
| 	"github.com/fatedier/frp/pkg/util/xlog" | ||||
|  | ||||
| 	"github.com/fatedier/golib/errors" | ||||
| 	frpIo "github.com/fatedier/golib/io" | ||||
| 	"github.com/fatedier/golib/pool" | ||||
| 	fmux "github.com/hashicorp/yamux" | ||||
| 	pp "github.com/pires/go-proxyproto" | ||||
| 	"golang.org/x/time/rate" | ||||
| ) | ||||
|  | ||||
| // Proxy defines how to handle work connections for different proxy type. | ||||
| type Proxy interface { | ||||
| 	Run() error | ||||
|  | ||||
| 	// InWorkConn accept work connections registered to server. | ||||
| 	InWorkConn(net.Conn, *msg.StartWorkConn) | ||||
|  | ||||
| 	Close() | ||||
| } | ||||
|  | ||||
| func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) { | ||||
| 	var limiter *rate.Limiter | ||||
| 	limitBytes := pxyConf.GetBaseInfo().BandwidthLimit.Bytes() | ||||
| 	if limitBytes > 0 { | ||||
| 		limiter = rate.NewLimiter(rate.Limit(float64(limitBytes)), int(limitBytes)) | ||||
| 	} | ||||
|  | ||||
| 	baseProxy := BaseProxy{ | ||||
| 		clientCfg:     clientCfg, | ||||
| 		serverUDPPort: serverUDPPort, | ||||
| 		limiter:       limiter, | ||||
| 		xl:            xlog.FromContextSafe(ctx), | ||||
| 		ctx:           ctx, | ||||
| 	} | ||||
| 	switch cfg := pxyConf.(type) { | ||||
| 	case *config.TCPProxyConf: | ||||
| 		pxy = &TCPProxy{ | ||||
| 			BaseProxy: &baseProxy, | ||||
| 			cfg:       cfg, | ||||
| 		} | ||||
| 	case *config.TCPMuxProxyConf: | ||||
| 		pxy = &TCPMuxProxy{ | ||||
| 			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, | ||||
| 		} | ||||
| 	case *config.SUDPProxyConf: | ||||
| 		pxy = &SUDPProxy{ | ||||
| 			BaseProxy: &baseProxy, | ||||
| 			cfg:       cfg, | ||||
| 			closeCh:   make(chan struct{}), | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type BaseProxy struct { | ||||
| 	closed        bool | ||||
| 	clientCfg     config.ClientCommonConf | ||||
| 	serverUDPPort int | ||||
| 	limiter       *rate.Limiter | ||||
|  | ||||
| 	mu  sync.RWMutex | ||||
| 	xl  *xlog.Logger | ||||
| 	ctx context.Context | ||||
| } | ||||
|  | ||||
| // 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 net.Conn, m *msg.StartWorkConn) { | ||||
| 	HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, | ||||
| 		conn, []byte(pxy.clientCfg.Token), m) | ||||
| } | ||||
|  | ||||
| // TCP Multiplexer | ||||
| type TCPMuxProxy struct { | ||||
| 	*BaseProxy | ||||
|  | ||||
| 	cfg         *config.TCPMuxProxyConf | ||||
| 	proxyPlugin plugin.Plugin | ||||
| } | ||||
|  | ||||
| func (pxy *TCPMuxProxy) 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 *TCPMuxProxy) Close() { | ||||
| 	if pxy.proxyPlugin != nil { | ||||
| 		pxy.proxyPlugin.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pxy *TCPMuxProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { | ||||
| 	HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, | ||||
| 		conn, []byte(pxy.clientCfg.Token), m) | ||||
| } | ||||
|  | ||||
| // 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 net.Conn, m *msg.StartWorkConn) { | ||||
| 	HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, | ||||
| 		conn, []byte(pxy.clientCfg.Token), m) | ||||
| } | ||||
|  | ||||
| // 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 net.Conn, m *msg.StartWorkConn) { | ||||
| 	HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, | ||||
| 		conn, []byte(pxy.clientCfg.Token), m) | ||||
| } | ||||
|  | ||||
| // 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 net.Conn, m *msg.StartWorkConn) { | ||||
| 	HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, | ||||
| 		conn, []byte(pxy.clientCfg.Token), m) | ||||
| } | ||||
|  | ||||
| // 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 net.Conn, m *msg.StartWorkConn) { | ||||
| 	xl := pxy.xl | ||||
| 	defer conn.Close() | ||||
| 	var natHoleSidMsg msg.NatHoleSid | ||||
| 	err := msg.ReadMsgInto(conn, &natHoleSidMsg) | ||||
| 	if err != nil { | ||||
| 		xl.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", pxy.clientCfg.ServerAddr, pxy.serverUDPPort)) | ||||
| 	clientConn, err := net.DialUDP("udp", nil, raddr) | ||||
| 	if err != nil { | ||||
| 		xl.Error("dial server udp addr error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer clientConn.Close() | ||||
|  | ||||
| 	err = msg.WriteMsg(clientConn, natHoleClientMsg) | ||||
| 	if err != nil { | ||||
| 		xl.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 { | ||||
| 		xl.Error("get natHoleRespMsg error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg) | ||||
| 	if err != nil { | ||||
| 		xl.Error("get natHoleRespMsg error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	clientConn.SetReadDeadline(time.Time{}) | ||||
| 	clientConn.Close() | ||||
|  | ||||
| 	if natHoleRespMsg.Error != "" { | ||||
| 		xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr) | ||||
|  | ||||
| 	// Send detect message | ||||
| 	array := strings.Split(natHoleRespMsg.VisitorAddr, ":") | ||||
| 	if len(array) <= 1 { | ||||
| 		xl.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr) | ||||
| 	} | ||||
| 	laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String()) | ||||
| 	/* | ||||
| 		for i := 1000; i < 65000; i++ { | ||||
| 			pxy.sendDetectMsg(array[0], int64(i), laddr, "a") | ||||
| 		} | ||||
| 	*/ | ||||
| 	port, err := strconv.ParseInt(array[1], 10, 64) | ||||
| 	if err != nil { | ||||
| 		xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr) | ||||
| 		return | ||||
| 	} | ||||
| 	pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid)) | ||||
| 	xl.Trace("send all detect msg done") | ||||
|  | ||||
| 	msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{}) | ||||
|  | ||||
| 	// Listen for clientConn's address and wait for visitor connection | ||||
| 	lConn, err := net.ListenUDP("udp", laddr) | ||||
| 	if err != nil { | ||||
| 		xl.Error("listen on visitorConn's local adress error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer lConn.Close() | ||||
|  | ||||
| 	lConn.SetReadDeadline(time.Now().Add(8 * time.Second)) | ||||
| 	sidBuf := pool.GetBuf(1024) | ||||
| 	var uAddr *net.UDPAddr | ||||
| 	n, uAddr, err = lConn.ReadFromUDP(sidBuf) | ||||
| 	if err != nil { | ||||
| 		xl.Warn("get sid from visitor error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	lConn.SetReadDeadline(time.Time{}) | ||||
| 	if string(sidBuf[:n]) != natHoleRespMsg.Sid { | ||||
| 		xl.Warn("incorrect sid from visitor") | ||||
| 		return | ||||
| 	} | ||||
| 	pool.PutBuf(sidBuf) | ||||
| 	xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid) | ||||
|  | ||||
| 	lConn.WriteToUDP(sidBuf[:n], uAddr) | ||||
|  | ||||
| 	kcpConn, err := frpNet.NewKCPConnFromUDP(lConn, false, uAddr.String()) | ||||
| 	if err != nil { | ||||
| 		xl.Error("create kcp connection from udp connection error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	fmuxCfg := fmux.DefaultConfig() | ||||
| 	fmuxCfg.KeepAliveInterval = 5 * time.Second | ||||
| 	fmuxCfg.LogOutput = ioutil.Discard | ||||
| 	sess, err := fmux.Server(kcpConn, fmuxCfg) | ||||
| 	if err != nil { | ||||
| 		xl.Error("create yamux server from kcp connection error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer sess.Close() | ||||
| 	muxConn, err := sess.Accept() | ||||
| 	if err != nil { | ||||
| 		xl.Error("accept for yamux connection error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, | ||||
| 		muxConn, []byte(pxy.cfg.Sk), m) | ||||
| } | ||||
|  | ||||
| func (pxy *XTCPProxy) 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 | ||||
| } | ||||
|  | ||||
| // 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 net.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 net.Conn, m *msg.StartWorkConn) { | ||||
| 	xl := pxy.xl | ||||
| 	xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String()) | ||||
| 	// close resources releated with old workConn | ||||
| 	pxy.Close() | ||||
|  | ||||
| 	var rwc io.ReadWriteCloser = conn | ||||
| 	var err error | ||||
| 	if pxy.limiter != nil { | ||||
| 		rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error { | ||||
| 			return conn.Close() | ||||
| 		}) | ||||
| 	} | ||||
| 	if pxy.cfg.UseEncryption { | ||||
| 		rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token)) | ||||
| 		if err != nil { | ||||
| 			conn.Close() | ||||
| 			xl.Error("create encryption stream error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if pxy.cfg.UseCompression { | ||||
| 		rwc = frpIo.WithCompression(rwc) | ||||
| 	} | ||||
| 	conn = frpNet.WrapReadWriteCloserToConn(rwc, conn) | ||||
|  | ||||
| 	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 { | ||||
| 				xl.Warn("read from workConn for udp error: %v", errRet) | ||||
| 				return | ||||
| 			} | ||||
| 			if errRet := errors.PanicToError(func() { | ||||
| 				xl.Trace("get udp package from workConn: %s", udpMsg.Content) | ||||
| 				readCh <- &udpMsg | ||||
| 			}); errRet != nil { | ||||
| 				xl.Info("reader goroutine for udp work connection closed: %v", errRet) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) { | ||||
| 		defer func() { | ||||
| 			xl.Info("writer goroutine for udp work connection closed") | ||||
| 		}() | ||||
| 		var errRet error | ||||
| 		for rawMsg := range sendCh { | ||||
| 			switch m := rawMsg.(type) { | ||||
| 			case *msg.UDPPacket: | ||||
| 				xl.Trace("send udp package to workConn: %s", m.Content) | ||||
| 			case *msg.Ping: | ||||
| 				xl.Trace("send ping message to udp workConn") | ||||
| 			} | ||||
| 			if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil { | ||||
| 				xl.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 { | ||||
| 				xl.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, int(pxy.clientCfg.UDPPacketSize)) | ||||
| } | ||||
|  | ||||
| type SUDPProxy struct { | ||||
| 	*BaseProxy | ||||
|  | ||||
| 	cfg *config.SUDPProxyConf | ||||
|  | ||||
| 	localAddr *net.UDPAddr | ||||
|  | ||||
| 	closeCh chan struct{} | ||||
| } | ||||
|  | ||||
| func (pxy *SUDPProxy) 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 *SUDPProxy) Close() { | ||||
| 	pxy.mu.Lock() | ||||
| 	defer pxy.mu.Unlock() | ||||
| 	select { | ||||
| 	case <-pxy.closeCh: | ||||
| 		return | ||||
| 	default: | ||||
| 		close(pxy.closeCh) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pxy *SUDPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { | ||||
| 	xl := pxy.xl | ||||
| 	xl.Info("incoming a new work connection for sudp proxy, %s", conn.RemoteAddr().String()) | ||||
|  | ||||
| 	var rwc io.ReadWriteCloser = conn | ||||
| 	var err error | ||||
| 	if pxy.limiter != nil { | ||||
| 		rwc = frpIo.WrapReadWriteCloser(limit.NewReader(conn, pxy.limiter), limit.NewWriter(conn, pxy.limiter), func() error { | ||||
| 			return conn.Close() | ||||
| 		}) | ||||
| 	} | ||||
| 	if pxy.cfg.UseEncryption { | ||||
| 		rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.clientCfg.Token)) | ||||
| 		if err != nil { | ||||
| 			conn.Close() | ||||
| 			xl.Error("create encryption stream error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if pxy.cfg.UseCompression { | ||||
| 		rwc = frpIo.WithCompression(rwc) | ||||
| 	} | ||||
| 	conn = frpNet.WrapReadWriteCloserToConn(rwc, conn) | ||||
|  | ||||
| 	workConn := conn | ||||
| 	readCh := make(chan *msg.UDPPacket, 1024) | ||||
| 	sendCh := make(chan msg.Message, 1024) | ||||
| 	isClose := false | ||||
|  | ||||
| 	mu := &sync.Mutex{} | ||||
|  | ||||
| 	closeFn := func() { | ||||
| 		mu.Lock() | ||||
| 		defer mu.Unlock() | ||||
| 		if isClose { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		isClose = true | ||||
| 		if workConn != nil { | ||||
| 			workConn.Close() | ||||
| 		} | ||||
| 		close(readCh) | ||||
| 		close(sendCh) | ||||
| 	} | ||||
|  | ||||
| 	// udp service <- frpc <- frps <- frpc visitor <- user | ||||
| 	workConnReaderFn := func(conn net.Conn, readCh chan *msg.UDPPacket) { | ||||
| 		defer closeFn() | ||||
|  | ||||
| 		for { | ||||
| 			// first to check sudp proxy is closed or not | ||||
| 			select { | ||||
| 			case <-pxy.closeCh: | ||||
| 				xl.Trace("frpc sudp proxy is closed") | ||||
| 				return | ||||
| 			default: | ||||
| 			} | ||||
|  | ||||
| 			var udpMsg msg.UDPPacket | ||||
| 			if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil { | ||||
| 				xl.Warn("read from workConn for sudp error: %v", errRet) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			if errRet := errors.PanicToError(func() { | ||||
| 				readCh <- &udpMsg | ||||
| 			}); errRet != nil { | ||||
| 				xl.Warn("reader goroutine for sudp work connection closed: %v", errRet) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// udp service -> frpc -> frps -> frpc visitor -> user | ||||
| 	workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) { | ||||
| 		defer func() { | ||||
| 			closeFn() | ||||
| 			xl.Info("writer goroutine for sudp work connection closed") | ||||
| 		}() | ||||
|  | ||||
| 		var errRet error | ||||
| 		for rawMsg := range sendCh { | ||||
| 			switch m := rawMsg.(type) { | ||||
| 			case *msg.UDPPacket: | ||||
| 				xl.Trace("frpc send udp package to frpc visitor, [udp local: %v, remote: %v], [tcp work conn local: %v, remote: %v]", | ||||
| 					m.LocalAddr.String(), m.RemoteAddr.String(), conn.LocalAddr().String(), conn.RemoteAddr().String()) | ||||
| 			case *msg.Ping: | ||||
| 				xl.Trace("frpc send ping message to frpc visitor") | ||||
| 			} | ||||
|  | ||||
| 			if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil { | ||||
| 				xl.Error("sudp work write error: %v", errRet) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	heartbeatFn := func(conn net.Conn, sendCh chan msg.Message) { | ||||
| 		ticker := time.NewTicker(30 * time.Second) | ||||
| 		defer func() { | ||||
| 			ticker.Stop() | ||||
| 			closeFn() | ||||
| 		}() | ||||
|  | ||||
| 		var errRet error | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-ticker.C: | ||||
| 				if errRet = errors.PanicToError(func() { | ||||
| 					sendCh <- &msg.Ping{} | ||||
| 				}); errRet != nil { | ||||
| 					xl.Warn("heartbeat goroutine for sudp work connection closed") | ||||
| 					return | ||||
| 				} | ||||
| 			case <-pxy.closeCh: | ||||
| 				xl.Trace("frpc sudp proxy is closed") | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	go workConnSenderFn(workConn, sendCh) | ||||
| 	go workConnReaderFn(workConn, readCh) | ||||
| 	go heartbeatFn(workConn, sendCh) | ||||
|  | ||||
| 	udp.Forwarder(pxy.localAddr, readCh, sendCh, int(pxy.clientCfg.UDPPacketSize)) | ||||
| } | ||||
|  | ||||
| // Common handler for tcp work connections. | ||||
| func HandleTCPWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin, | ||||
| 	baseInfo *config.BaseProxyConf, limiter *rate.Limiter, workConn net.Conn, encKey []byte, m *msg.StartWorkConn) { | ||||
| 	xl := xlog.FromContextSafe(ctx) | ||||
| 	var ( | ||||
| 		remote io.ReadWriteCloser | ||||
| 		err    error | ||||
| 	) | ||||
| 	remote = workConn | ||||
| 	if limiter != nil { | ||||
| 		remote = frpIo.WrapReadWriteCloser(limit.NewReader(workConn, limiter), limit.NewWriter(workConn, limiter), func() error { | ||||
| 			return workConn.Close() | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t", | ||||
| 		baseInfo.UseEncryption, baseInfo.UseCompression) | ||||
| 	if baseInfo.UseEncryption { | ||||
| 		remote, err = frpIo.WithEncryption(remote, encKey) | ||||
| 		if err != nil { | ||||
| 			workConn.Close() | ||||
| 			xl.Error("create encryption stream error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if baseInfo.UseCompression { | ||||
| 		remote = frpIo.WithCompression(remote) | ||||
| 	} | ||||
|  | ||||
| 	// check if we need to send proxy protocol info | ||||
| 	var extraInfo []byte | ||||
| 	if baseInfo.ProxyProtocolVersion != "" { | ||||
| 		if m.SrcAddr != "" && m.SrcPort != 0 { | ||||
| 			if m.DstAddr == "" { | ||||
| 				m.DstAddr = "127.0.0.1" | ||||
| 			} | ||||
| 			h := &pp.Header{ | ||||
| 				Command:            pp.PROXY, | ||||
| 				SourceAddress:      net.ParseIP(m.SrcAddr), | ||||
| 				SourcePort:         m.SrcPort, | ||||
| 				DestinationAddress: net.ParseIP(m.DstAddr), | ||||
| 				DestinationPort:    m.DstPort, | ||||
| 			} | ||||
|  | ||||
| 			if strings.Contains(m.SrcAddr, ".") { | ||||
| 				h.TransportProtocol = pp.TCPv4 | ||||
| 			} else { | ||||
| 				h.TransportProtocol = pp.TCPv6 | ||||
| 			} | ||||
|  | ||||
| 			if baseInfo.ProxyProtocolVersion == "v1" { | ||||
| 				h.Version = 1 | ||||
| 			} else if baseInfo.ProxyProtocolVersion == "v2" { | ||||
| 				h.Version = 2 | ||||
| 			} | ||||
|  | ||||
| 			buf := bytes.NewBuffer(nil) | ||||
| 			h.WriteTo(buf) | ||||
| 			extraInfo = buf.Bytes() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if proxyPlugin != nil { | ||||
| 		// if plugin is set, let plugin handle connections first | ||||
| 		xl.Debug("handle by plugin: %s", proxyPlugin.Name()) | ||||
| 		proxyPlugin.Handle(remote, workConn, extraInfo) | ||||
| 		xl.Debug("handle by plugin finished") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIP, localInfo.LocalPort)) | ||||
| 	if err != nil { | ||||
| 		workConn.Close() | ||||
| 		xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIP, localInfo.LocalPort, err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	xl.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()) | ||||
|  | ||||
| 	if len(extraInfo) > 0 { | ||||
| 		localConn.Write(extraInfo) | ||||
| 	} | ||||
|  | ||||
| 	frpIo.Join(localConn, remote) | ||||
| 	xl.Debug("join connections closed") | ||||
| } | ||||
							
								
								
									
										146
									
								
								client/proxy/proxy_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								client/proxy/proxy_manager.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/fatedier/frp/client/event" | ||||
| 	"github.com/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/msg" | ||||
| 	"github.com/fatedier/frp/pkg/util/xlog" | ||||
|  | ||||
| 	"github.com/fatedier/golib/errors" | ||||
| ) | ||||
|  | ||||
| type Manager struct { | ||||
| 	sendCh  chan (msg.Message) | ||||
| 	proxies map[string]*Wrapper | ||||
|  | ||||
| 	closed bool | ||||
| 	mu     sync.RWMutex | ||||
|  | ||||
| 	clientCfg config.ClientCommonConf | ||||
|  | ||||
| 	// The UDP port that the server is listening on | ||||
| 	serverUDPPort int | ||||
|  | ||||
| 	ctx context.Context | ||||
| } | ||||
|  | ||||
| func NewManager(ctx context.Context, msgSendCh chan (msg.Message), clientCfg config.ClientCommonConf, serverUDPPort int) *Manager { | ||||
| 	return &Manager{ | ||||
| 		sendCh:        msgSendCh, | ||||
| 		proxies:       make(map[string]*Wrapper), | ||||
| 		closed:        false, | ||||
| 		clientCfg:     clientCfg, | ||||
| 		serverUDPPort: serverUDPPort, | ||||
| 		ctx:           ctx, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pm *Manager) 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 *Manager) Close() { | ||||
| 	pm.mu.Lock() | ||||
| 	defer pm.mu.Unlock() | ||||
| 	for _, pxy := range pm.proxies { | ||||
| 		pxy.Stop() | ||||
| 	} | ||||
| 	pm.proxies = make(map[string]*Wrapper) | ||||
| } | ||||
|  | ||||
| func (pm *Manager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWorkConn) { | ||||
| 	pm.mu.RLock() | ||||
| 	pw, ok := pm.proxies[name] | ||||
| 	pm.mu.RUnlock() | ||||
| 	if ok { | ||||
| 		pw.InWorkConn(workConn, m) | ||||
| 	} else { | ||||
| 		workConn.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pm *Manager) HandleEvent(evType event.Type, 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 *Manager) GetAllProxyStatus() []*WorkingStatus { | ||||
| 	ps := make([]*WorkingStatus, 0) | ||||
| 	pm.mu.RLock() | ||||
| 	defer pm.mu.RUnlock() | ||||
| 	for _, pxy := range pm.proxies { | ||||
| 		ps = append(ps, pxy.GetStatus()) | ||||
| 	} | ||||
| 	return ps | ||||
| } | ||||
|  | ||||
| func (pm *Manager) Reload(pxyCfgs map[string]config.ProxyConf) { | ||||
| 	xl := xlog.FromContextSafe(pm.ctx) | ||||
| 	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 { | ||||
| 		xl.Info("proxy removed: %v", delPxyNames) | ||||
| 	} | ||||
|  | ||||
| 	addPxyNames := make([]string, 0) | ||||
| 	for name, cfg := range pxyCfgs { | ||||
| 		if _, ok := pm.proxies[name]; !ok { | ||||
| 			pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.serverUDPPort) | ||||
| 			pm.proxies[name] = pxy | ||||
| 			addPxyNames = append(addPxyNames, name) | ||||
|  | ||||
| 			pxy.Start() | ||||
| 		} | ||||
| 	} | ||||
| 	if len(addPxyNames) > 0 { | ||||
| 		xl.Info("proxy added: %v", addPxyNames) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										250
									
								
								client/proxy/proxy_wrapper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								client/proxy/proxy_wrapper.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,250 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/client/event" | ||||
| 	"github.com/fatedier/frp/client/health" | ||||
| 	"github.com/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/msg" | ||||
| 	"github.com/fatedier/frp/pkg/util/xlog" | ||||
|  | ||||
| 	"github.com/fatedier/golib/errors" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	ProxyPhaseNew         = "new" | ||||
| 	ProxyPhaseWaitStart   = "wait start" | ||||
| 	ProxyPhaseStartErr    = "start error" | ||||
| 	ProxyPhaseRunning     = "running" | ||||
| 	ProxyPhaseCheckFailed = "check failed" | ||||
| 	ProxyPhaseClosed      = "closed" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	statusCheckInterval time.Duration = 3 * time.Second | ||||
| 	waitResponseTimeout               = 20 * time.Second | ||||
| 	startErrTimeout                   = 30 * time.Second | ||||
| ) | ||||
|  | ||||
| type WorkingStatus struct { | ||||
| 	Name  string           `json:"name"` | ||||
| 	Type  string           `json:"type"` | ||||
| 	Phase string           `json:"status"` | ||||
| 	Err   string           `json:"err"` | ||||
| 	Cfg   config.ProxyConf `json:"cfg"` | ||||
|  | ||||
| 	// Got from server. | ||||
| 	RemoteAddr string `json:"remote_addr"` | ||||
| } | ||||
|  | ||||
| type Wrapper struct { | ||||
| 	WorkingStatus | ||||
|  | ||||
| 	// underlying proxy | ||||
| 	pxy Proxy | ||||
|  | ||||
| 	// if ProxyConf has healcheck config | ||||
| 	// monitor will watch if it is alive | ||||
| 	monitor *health.Monitor | ||||
|  | ||||
| 	// event handler | ||||
| 	handler event.Handler | ||||
|  | ||||
| 	health           uint32 | ||||
| 	lastSendStartMsg time.Time | ||||
| 	lastStartErr     time.Time | ||||
| 	closeCh          chan struct{} | ||||
| 	healthNotifyCh   chan struct{} | ||||
| 	mu               sync.RWMutex | ||||
|  | ||||
| 	xl  *xlog.Logger | ||||
| 	ctx context.Context | ||||
| } | ||||
|  | ||||
| func NewWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.Handler, serverUDPPort int) *Wrapper { | ||||
| 	baseInfo := cfg.GetBaseInfo() | ||||
| 	xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName) | ||||
| 	pw := &Wrapper{ | ||||
| 		WorkingStatus: WorkingStatus{ | ||||
| 			Name:  baseInfo.ProxyName, | ||||
| 			Type:  baseInfo.ProxyType, | ||||
| 			Phase: ProxyPhaseNew, | ||||
| 			Cfg:   cfg, | ||||
| 		}, | ||||
| 		closeCh:        make(chan struct{}), | ||||
| 		healthNotifyCh: make(chan struct{}), | ||||
| 		handler:        eventHandler, | ||||
| 		xl:             xl, | ||||
| 		ctx:            xlog.NewContext(ctx, xl), | ||||
| 	} | ||||
|  | ||||
| 	if baseInfo.HealthCheckType != "" { | ||||
| 		pw.health = 1 // means failed | ||||
| 		pw.monitor = health.NewMonitor(pw.ctx, baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS, | ||||
| 			baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr, | ||||
| 			baseInfo.HealthCheckURL, pw.statusNormalCallback, pw.statusFailedCallback) | ||||
| 		xl.Trace("enable health check monitor") | ||||
| 	} | ||||
|  | ||||
| 	pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, serverUDPPort) | ||||
| 	return pw | ||||
| } | ||||
|  | ||||
| func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error { | ||||
| 	pw.mu.Lock() | ||||
| 	defer pw.mu.Unlock() | ||||
| 	if pw.Phase != ProxyPhaseWaitStart { | ||||
| 		return fmt.Errorf("status not wait start, ignore start message") | ||||
| 	} | ||||
|  | ||||
| 	pw.RemoteAddr = remoteAddr | ||||
| 	if respErr != "" { | ||||
| 		pw.Phase = ProxyPhaseStartErr | ||||
| 		pw.Err = respErr | ||||
| 		pw.lastStartErr = time.Now() | ||||
| 		return fmt.Errorf(pw.Err) | ||||
| 	} | ||||
|  | ||||
| 	if err := pw.pxy.Run(); err != nil { | ||||
| 		pw.close() | ||||
| 		pw.Phase = ProxyPhaseStartErr | ||||
| 		pw.Err = err.Error() | ||||
| 		pw.lastStartErr = time.Now() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	pw.Phase = ProxyPhaseRunning | ||||
| 	pw.Err = "" | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (pw *Wrapper) Start() { | ||||
| 	go pw.checkWorker() | ||||
| 	if pw.monitor != nil { | ||||
| 		go pw.monitor.Start() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pw *Wrapper) 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.Phase = ProxyPhaseClosed | ||||
| 	pw.close() | ||||
| } | ||||
|  | ||||
| func (pw *Wrapper) close() { | ||||
| 	pw.handler(event.EvCloseProxy, &event.CloseProxyPayload{ | ||||
| 		CloseProxyMsg: &msg.CloseProxy{ | ||||
| 			ProxyName: pw.Name, | ||||
| 		}, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (pw *Wrapper) checkWorker() { | ||||
| 	xl := pw.xl | ||||
| 	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.Phase == ProxyPhaseNew || | ||||
| 				pw.Phase == ProxyPhaseCheckFailed || | ||||
| 				(pw.Phase == ProxyPhaseWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) || | ||||
| 				(pw.Phase == ProxyPhaseStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) { | ||||
|  | ||||
| 				xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseWaitStart) | ||||
| 				pw.Phase = ProxyPhaseWaitStart | ||||
|  | ||||
| 				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.Phase == ProxyPhaseRunning || pw.Phase == ProxyPhaseWaitStart { | ||||
| 				pw.close() | ||||
| 				xl.Trace("change status from [%s] to [%s]", pw.Phase, ProxyPhaseCheckFailed) | ||||
| 				pw.Phase = ProxyPhaseCheckFailed | ||||
| 			} | ||||
| 			pw.mu.Unlock() | ||||
| 		} | ||||
|  | ||||
| 		select { | ||||
| 		case <-pw.closeCh: | ||||
| 			return | ||||
| 		case <-time.After(statusCheckInterval): | ||||
| 		case <-pw.healthNotifyCh: | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pw *Wrapper) statusNormalCallback() { | ||||
| 	xl := pw.xl | ||||
| 	atomic.StoreUint32(&pw.health, 0) | ||||
| 	errors.PanicToError(func() { | ||||
| 		select { | ||||
| 		case pw.healthNotifyCh <- struct{}{}: | ||||
| 		default: | ||||
| 		} | ||||
| 	}) | ||||
| 	xl.Info("health check success") | ||||
| } | ||||
|  | ||||
| func (pw *Wrapper) statusFailedCallback() { | ||||
| 	xl := pw.xl | ||||
| 	atomic.StoreUint32(&pw.health, 1) | ||||
| 	errors.PanicToError(func() { | ||||
| 		select { | ||||
| 		case pw.healthNotifyCh <- struct{}{}: | ||||
| 		default: | ||||
| 		} | ||||
| 	}) | ||||
| 	xl.Info("health check failed") | ||||
| } | ||||
|  | ||||
| func (pw *Wrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) { | ||||
| 	xl := pw.xl | ||||
| 	pw.mu.RLock() | ||||
| 	pxy := pw.pxy | ||||
| 	pw.mu.RUnlock() | ||||
| 	if pxy != nil && pw.Phase == ProxyPhaseRunning { | ||||
| 		xl.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String()) | ||||
| 		go pxy.InWorkConn(workConn, m) | ||||
| 	} else { | ||||
| 		workConn.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pw *Wrapper) GetStatus() *WorkingStatus { | ||||
| 	pw.mu.RLock() | ||||
| 	defer pw.mu.RUnlock() | ||||
| 	ps := &WorkingStatus{ | ||||
| 		Name:       pw.Name, | ||||
| 		Type:       pw.Type, | ||||
| 		Phase:      pw.Phase, | ||||
| 		Err:        pw.Err, | ||||
| 		Cfg:        pw.Cfg, | ||||
| 		RemoteAddr: pw.RemoteAddr, | ||||
| 	} | ||||
| 	return ps | ||||
| } | ||||
							
								
								
									
										323
									
								
								client/service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								client/service.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 client | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| 	"runtime" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/assets" | ||||
| 	"github.com/fatedier/frp/pkg/auth" | ||||
| 	"github.com/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/msg" | ||||
| 	"github.com/fatedier/frp/pkg/transport" | ||||
| 	"github.com/fatedier/frp/pkg/util/log" | ||||
| 	frpNet "github.com/fatedier/frp/pkg/util/net" | ||||
| 	"github.com/fatedier/frp/pkg/util/version" | ||||
| 	"github.com/fatedier/frp/pkg/util/xlog" | ||||
|  | ||||
| 	fmux "github.com/hashicorp/yamux" | ||||
| ) | ||||
|  | ||||
| // Service is a client service. | ||||
| type Service struct { | ||||
| 	// uniq id got from frps, attach it in loginMsg | ||||
| 	runID string | ||||
|  | ||||
| 	// manager control connection with server | ||||
| 	ctl   *Control | ||||
| 	ctlMu sync.RWMutex | ||||
|  | ||||
| 	// Sets authentication based on selected method | ||||
| 	authSetter auth.Setter | ||||
|  | ||||
| 	cfg         config.ClientCommonConf | ||||
| 	pxyCfgs     map[string]config.ProxyConf | ||||
| 	visitorCfgs map[string]config.VisitorConf | ||||
| 	cfgMu       sync.RWMutex | ||||
|  | ||||
| 	// The configuration file used to initialize this client, or an empty | ||||
| 	// string if no configuration file was used. | ||||
| 	cfgFile string | ||||
|  | ||||
| 	// This is configured by the login response from frps | ||||
| 	serverUDPPort int | ||||
|  | ||||
| 	exit uint32 // 0 means not exit | ||||
|  | ||||
| 	// service context | ||||
| 	ctx context.Context | ||||
| 	// call cancel to stop service | ||||
| 	cancel context.CancelFunc | ||||
| } | ||||
|  | ||||
| func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) { | ||||
|  | ||||
| 	ctx, cancel := context.WithCancel(context.Background()) | ||||
| 	svr = &Service{ | ||||
| 		authSetter:  auth.NewAuthSetter(cfg.ClientConfig), | ||||
| 		cfg:         cfg, | ||||
| 		cfgFile:     cfgFile, | ||||
| 		pxyCfgs:     pxyCfgs, | ||||
| 		visitorCfgs: visitorCfgs, | ||||
| 		exit:        0, | ||||
| 		ctx:         xlog.NewContext(ctx, xlog.New()), | ||||
| 		cancel:      cancel, | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (svr *Service) GetController() *Control { | ||||
| 	svr.ctlMu.RLock() | ||||
| 	defer svr.ctlMu.RUnlock() | ||||
| 	return svr.ctl | ||||
| } | ||||
|  | ||||
| func (svr *Service) Run() error { | ||||
| 	xl := xlog.FromContextSafe(svr.ctx) | ||||
|  | ||||
| 	// login to frps | ||||
| 	for { | ||||
| 		conn, session, err := svr.login() | ||||
| 		if err != nil { | ||||
| 			xl.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 svr.cfg.LoginFailExit { | ||||
| 				return err | ||||
| 			} | ||||
| 			time.Sleep(10 * time.Second) | ||||
| 		} else { | ||||
| 			// login success | ||||
| 			ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter) | ||||
| 			ctl.Run() | ||||
| 			svr.ctlMu.Lock() | ||||
| 			svr.ctl = ctl | ||||
| 			svr.ctlMu.Unlock() | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	go svr.keepControllerWorking() | ||||
|  | ||||
| 	if svr.cfg.AdminPort != 0 { | ||||
| 		// Init admin server assets | ||||
| 		err := assets.Load(svr.cfg.AssetsDir) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("Load assets error: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		address := net.JoinHostPort(svr.cfg.AdminAddr, strconv.Itoa(svr.cfg.AdminPort)) | ||||
| 		err = svr.RunAdminServer(address) | ||||
| 		if err != nil { | ||||
| 			log.Warn("run admin server error: %v", err) | ||||
| 		} | ||||
| 		log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort) | ||||
| 	} | ||||
| 	<-svr.ctx.Done() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (svr *Service) keepControllerWorking() { | ||||
| 	xl := xlog.FromContextSafe(svr.ctx) | ||||
| 	maxDelayTime := 20 * time.Second | ||||
| 	delayTime := time.Second | ||||
|  | ||||
| 	// if frpc reconnect frps, we need to limit retry times in 1min | ||||
| 	// current retry logic is sleep 0s, 0s, 0s, 1s, 2s, 4s, 8s, ... | ||||
| 	// when exceed 1min, we will reset delay and counts | ||||
| 	cutoffTime := time.Now().Add(time.Minute) | ||||
| 	reconnectDelay := time.Second | ||||
| 	reconnectCounts := 1 | ||||
|  | ||||
| 	for { | ||||
| 		<-svr.ctl.ClosedDoneCh() | ||||
| 		if atomic.LoadUint32(&svr.exit) != 0 { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// the first three retry with no delay | ||||
| 		if reconnectCounts > 3 { | ||||
| 			time.Sleep(reconnectDelay) | ||||
| 			reconnectDelay *= 2 | ||||
| 		} | ||||
| 		reconnectCounts++ | ||||
|  | ||||
| 		now := time.Now() | ||||
| 		if now.After(cutoffTime) { | ||||
| 			// reset | ||||
| 			cutoffTime = now.Add(time.Minute) | ||||
| 			reconnectDelay = time.Second | ||||
| 			reconnectCounts = 1 | ||||
| 		} | ||||
|  | ||||
| 		for { | ||||
| 			xl.Info("try to reconnect to server...") | ||||
| 			conn, session, err := svr.login() | ||||
| 			if err != nil { | ||||
| 				xl.Warn("reconnect to server error: %v", err) | ||||
| 				time.Sleep(delayTime) | ||||
|  | ||||
| 				opErr := &net.OpError{} | ||||
| 				// quick retry for dial error | ||||
| 				if errors.As(err, &opErr) && opErr.Op == "dial" { | ||||
| 					delayTime = 2 * time.Second | ||||
| 				} else { | ||||
| 					delayTime = delayTime * 2 | ||||
| 					if delayTime > maxDelayTime { | ||||
| 						delayTime = maxDelayTime | ||||
| 					} | ||||
| 				} | ||||
| 				continue | ||||
| 			} | ||||
| 			// reconnect success, init delayTime | ||||
| 			delayTime = time.Second | ||||
|  | ||||
| 			ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter) | ||||
| 			ctl.Run() | ||||
| 			svr.ctlMu.Lock() | ||||
| 			if svr.ctl != nil { | ||||
| 				svr.ctl.Close() | ||||
| 			} | ||||
| 			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 net.Conn, session *fmux.Session, err error) { | ||||
| 	xl := xlog.FromContextSafe(svr.ctx) | ||||
| 	var tlsConfig *tls.Config | ||||
| 	if svr.cfg.TLSEnable { | ||||
| 		sn := svr.cfg.TLSServerName | ||||
| 		if sn == "" { | ||||
| 			sn = svr.cfg.ServerAddr | ||||
| 		} | ||||
|  | ||||
| 		tlsConfig, err = transport.NewClientTLSConfig( | ||||
| 			svr.cfg.TLSCertFile, | ||||
| 			svr.cfg.TLSKeyFile, | ||||
| 			svr.cfg.TLSTrustedCaFile, | ||||
| 			sn) | ||||
| 		if err != nil { | ||||
| 			xl.Warn("fail to build tls configuration when service login, err: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	address := net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort)) | ||||
| 	conn, err = frpNet.ConnectServerByProxyWithTLS(svr.cfg.HTTPProxy, svr.cfg.Protocol, address, tlsConfig) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	defer func() { | ||||
| 		if err != nil { | ||||
| 			conn.Close() | ||||
| 			if session != nil { | ||||
| 				session.Close() | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	if svr.cfg.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 = stream | ||||
| 	} | ||||
|  | ||||
| 	loginMsg := &msg.Login{ | ||||
| 		Arch:      runtime.GOARCH, | ||||
| 		Os:        runtime.GOOS, | ||||
| 		PoolCount: svr.cfg.PoolCount, | ||||
| 		User:      svr.cfg.User, | ||||
| 		Version:   version.Full(), | ||||
| 		Timestamp: time.Now().Unix(), | ||||
| 		RunID:     svr.runID, | ||||
| 		Metas:     svr.cfg.Metas, | ||||
| 	} | ||||
|  | ||||
| 	// Add auth | ||||
| 	if err = svr.authSetter.SetLogin(loginMsg); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 		xl.Error("%s", loginRespMsg.Error) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	svr.runID = loginRespMsg.RunID | ||||
| 	xl.ResetPrefixes() | ||||
| 	xl.AppendPrefix(svr.runID) | ||||
|  | ||||
| 	svr.serverUDPPort = loginRespMsg.ServerUDPPort | ||||
| 	xl.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) | ||||
| 	if svr.ctl != nil { | ||||
| 		svr.ctl.Close() | ||||
| 	} | ||||
| 	svr.cancel() | ||||
| } | ||||
							
								
								
									
										554
									
								
								client/visitor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										554
									
								
								client/visitor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,554 @@ | ||||
| // 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" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/msg" | ||||
| 	"github.com/fatedier/frp/pkg/proto/udp" | ||||
| 	frpNet "github.com/fatedier/frp/pkg/util/net" | ||||
| 	"github.com/fatedier/frp/pkg/util/util" | ||||
| 	"github.com/fatedier/frp/pkg/util/xlog" | ||||
|  | ||||
| 	"github.com/fatedier/golib/errors" | ||||
| 	frpIo "github.com/fatedier/golib/io" | ||||
| 	"github.com/fatedier/golib/pool" | ||||
| 	fmux "github.com/hashicorp/yamux" | ||||
| ) | ||||
|  | ||||
| // Visitor is used for forward traffics from local port tot remote service. | ||||
| type Visitor interface { | ||||
| 	Run() error | ||||
| 	Close() | ||||
| } | ||||
|  | ||||
| func NewVisitor(ctx context.Context, ctl *Control, cfg config.VisitorConf) (visitor Visitor) { | ||||
| 	xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseInfo().ProxyName) | ||||
| 	baseVisitor := BaseVisitor{ | ||||
| 		ctl: ctl, | ||||
| 		ctx: xlog.NewContext(ctx, xl), | ||||
| 	} | ||||
| 	switch cfg := cfg.(type) { | ||||
| 	case *config.STCPVisitorConf: | ||||
| 		visitor = &STCPVisitor{ | ||||
| 			BaseVisitor: &baseVisitor, | ||||
| 			cfg:         cfg, | ||||
| 		} | ||||
| 	case *config.XTCPVisitorConf: | ||||
| 		visitor = &XTCPVisitor{ | ||||
| 			BaseVisitor: &baseVisitor, | ||||
| 			cfg:         cfg, | ||||
| 		} | ||||
| 	case *config.SUDPVisitorConf: | ||||
| 		visitor = &SUDPVisitor{ | ||||
| 			BaseVisitor:  &baseVisitor, | ||||
| 			cfg:          cfg, | ||||
| 			checkCloseCh: make(chan struct{}), | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type BaseVisitor struct { | ||||
| 	ctl    *Control | ||||
| 	l      net.Listener | ||||
| 	closed bool | ||||
|  | ||||
| 	mu  sync.RWMutex | ||||
| 	ctx context.Context | ||||
| } | ||||
|  | ||||
| type STCPVisitor struct { | ||||
| 	*BaseVisitor | ||||
|  | ||||
| 	cfg *config.STCPVisitorConf | ||||
| } | ||||
|  | ||||
| func (sv *STCPVisitor) Run() (err error) { | ||||
| 	sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", 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() { | ||||
| 	xl := xlog.FromContextSafe(sv.ctx) | ||||
| 	for { | ||||
| 		conn, err := sv.l.Accept() | ||||
| 		if err != nil { | ||||
| 			xl.Warn("stcp local listener closed") | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		go sv.handleConn(conn) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sv *STCPVisitor) handleConn(userConn net.Conn) { | ||||
| 	xl := xlog.FromContextSafe(sv.ctx) | ||||
| 	defer userConn.Close() | ||||
|  | ||||
| 	xl.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 { | ||||
| 		xl.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 { | ||||
| 		xl.Warn("get newVisitorConnRespMsg error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	visitorConn.SetReadDeadline(time.Time{}) | ||||
|  | ||||
| 	if newVisitorConnRespMsg.Error != "" { | ||||
| 		xl.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 { | ||||
| 			xl.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 = net.Listen("tcp", fmt.Sprintf("%s:%d", 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() { | ||||
| 	xl := xlog.FromContextSafe(sv.ctx) | ||||
| 	for { | ||||
| 		conn, err := sv.l.Accept() | ||||
| 		if err != nil { | ||||
| 			xl.Warn("xtcp local listener closed") | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		go sv.handleConn(conn) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sv *XTCPVisitor) handleConn(userConn net.Conn) { | ||||
| 	xl := xlog.FromContextSafe(sv.ctx) | ||||
| 	defer userConn.Close() | ||||
|  | ||||
| 	xl.Debug("get a new xtcp user connection") | ||||
| 	if sv.ctl.serverUDPPort == 0 { | ||||
| 		xl.Error("xtcp is not supported by server") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	raddr, err := net.ResolveUDPAddr("udp", | ||||
| 		fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort)) | ||||
| 	if err != nil { | ||||
| 		xl.Error("resolve server UDP addr error") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	visitorConn, err := net.DialUDP("udp", nil, raddr) | ||||
| 	if err != nil { | ||||
| 		xl.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 { | ||||
| 		xl.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 { | ||||
| 		xl.Warn("get natHoleRespMsg error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg) | ||||
| 	if err != nil { | ||||
| 		xl.Warn("get natHoleRespMsg error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	visitorConn.SetReadDeadline(time.Time{}) | ||||
| 	pool.PutBuf(buf) | ||||
|  | ||||
| 	if natHoleRespMsg.Error != "" { | ||||
| 		xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	xl.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr) | ||||
|  | ||||
| 	// Close visitorConn, so we can use it's local address. | ||||
| 	visitorConn.Close() | ||||
|  | ||||
| 	// send sid message to client | ||||
| 	laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String()) | ||||
| 	daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr) | ||||
| 	if err != nil { | ||||
| 		xl.Error("resolve client udp address error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	lConn, err := net.DialUDP("udp", laddr, daddr) | ||||
| 	if err != nil { | ||||
| 		xl.Error("dial client udp address error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer lConn.Close() | ||||
|  | ||||
| 	lConn.Write([]byte(natHoleRespMsg.Sid)) | ||||
|  | ||||
| 	// read ack sid from client | ||||
| 	sidBuf := pool.GetBuf(1024) | ||||
| 	lConn.SetReadDeadline(time.Now().Add(8 * time.Second)) | ||||
| 	n, err = lConn.Read(sidBuf) | ||||
| 	if err != nil { | ||||
| 		xl.Warn("get sid from client error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	lConn.SetReadDeadline(time.Time{}) | ||||
| 	if string(sidBuf[:n]) != natHoleRespMsg.Sid { | ||||
| 		xl.Warn("incorrect sid from client") | ||||
| 		return | ||||
| 	} | ||||
| 	pool.PutBuf(sidBuf) | ||||
|  | ||||
| 	xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid) | ||||
|  | ||||
| 	// wrap kcp connection | ||||
| 	var remote io.ReadWriteCloser | ||||
| 	remote, err = frpNet.NewKCPConnFromUDP(lConn, true, natHoleRespMsg.ClientAddr) | ||||
| 	if err != nil { | ||||
| 		xl.Error("create kcp connection from udp connection error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	fmuxCfg := fmux.DefaultConfig() | ||||
| 	fmuxCfg.KeepAliveInterval = 5 * time.Second | ||||
| 	fmuxCfg.LogOutput = ioutil.Discard | ||||
| 	sess, err := fmux.Client(remote, fmuxCfg) | ||||
| 	if err != nil { | ||||
| 		xl.Error("create yamux session error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer sess.Close() | ||||
| 	muxConn, err := sess.Open() | ||||
| 	if err != nil { | ||||
| 		xl.Error("open yamux stream error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var muxConnRWCloser io.ReadWriteCloser = muxConn | ||||
| 	if sv.cfg.UseEncryption { | ||||
| 		muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk)) | ||||
| 		if err != nil { | ||||
| 			xl.Error("create encryption stream error: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if sv.cfg.UseCompression { | ||||
| 		muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser) | ||||
| 	} | ||||
|  | ||||
| 	frpIo.Join(userConn, muxConnRWCloser) | ||||
| 	xl.Debug("join connections closed") | ||||
| } | ||||
|  | ||||
| type SUDPVisitor struct { | ||||
| 	*BaseVisitor | ||||
|  | ||||
| 	checkCloseCh chan struct{} | ||||
| 	// udpConn is the listener of udp packet | ||||
| 	udpConn *net.UDPConn | ||||
| 	readCh  chan *msg.UDPPacket | ||||
| 	sendCh  chan *msg.UDPPacket | ||||
|  | ||||
| 	cfg *config.SUDPVisitorConf | ||||
| } | ||||
|  | ||||
| // SUDP Run start listen a udp port | ||||
| func (sv *SUDPVisitor) Run() (err error) { | ||||
| 	xl := xlog.FromContextSafe(sv.ctx) | ||||
|  | ||||
| 	addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("sudp ResolveUDPAddr error: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	sv.udpConn, err = net.ListenUDP("udp", addr) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("listen udp port %s error: %v", addr.String(), err) | ||||
| 	} | ||||
|  | ||||
| 	sv.sendCh = make(chan *msg.UDPPacket, 1024) | ||||
| 	sv.readCh = make(chan *msg.UDPPacket, 1024) | ||||
|  | ||||
| 	xl.Info("sudp start to work, listen on %s", addr) | ||||
|  | ||||
| 	go sv.dispatcher() | ||||
| 	go udp.ForwardUserConn(sv.udpConn, sv.readCh, sv.sendCh, int(sv.ctl.clientCfg.UDPPacketSize)) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (sv *SUDPVisitor) dispatcher() { | ||||
| 	xl := xlog.FromContextSafe(sv.ctx) | ||||
|  | ||||
| 	for { | ||||
| 		// loop for get frpc to frps tcp conn | ||||
| 		// setup worker | ||||
| 		// wait worker to finished | ||||
| 		// retry or exit | ||||
| 		visitorConn, err := sv.getNewVisitorConn() | ||||
| 		if err != nil { | ||||
| 			// check if proxy is closed | ||||
| 			// if checkCloseCh is close, we will return, other case we will continue to reconnect | ||||
| 			select { | ||||
| 			case <-sv.checkCloseCh: | ||||
| 				xl.Info("frpc sudp visitor proxy is closed") | ||||
| 				return | ||||
| 			default: | ||||
| 			} | ||||
|  | ||||
| 			time.Sleep(3 * time.Second) | ||||
|  | ||||
| 			xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		sv.worker(visitorConn) | ||||
|  | ||||
| 		select { | ||||
| 		case <-sv.checkCloseCh: | ||||
| 			return | ||||
| 		default: | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (sv *SUDPVisitor) worker(workConn net.Conn) { | ||||
| 	xl := xlog.FromContextSafe(sv.ctx) | ||||
| 	xl.Debug("starting sudp proxy worker") | ||||
|  | ||||
| 	wg := &sync.WaitGroup{} | ||||
| 	wg.Add(2) | ||||
| 	closeCh := make(chan struct{}) | ||||
|  | ||||
| 	// udp service -> frpc -> frps -> frpc visitor -> user | ||||
| 	workConnReaderFn := func(conn net.Conn) { | ||||
| 		defer func() { | ||||
| 			conn.Close() | ||||
| 			close(closeCh) | ||||
| 			wg.Done() | ||||
| 		}() | ||||
|  | ||||
| 		for { | ||||
| 			var ( | ||||
| 				rawMsg msg.Message | ||||
| 				errRet error | ||||
| 			) | ||||
|  | ||||
| 			// frpc will send heartbeat in workConn to frpc visitor for keeping alive | ||||
| 			conn.SetReadDeadline(time.Now().Add(60 * time.Second)) | ||||
| 			if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil { | ||||
| 				xl.Warn("read from workconn for user udp conn error: %v", errRet) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			conn.SetReadDeadline(time.Time{}) | ||||
| 			switch m := rawMsg.(type) { | ||||
| 			case *msg.Ping: | ||||
| 				xl.Debug("frpc visitor get ping message from frpc") | ||||
| 				continue | ||||
| 			case *msg.UDPPacket: | ||||
| 				if errRet := errors.PanicToError(func() { | ||||
| 					sv.readCh <- m | ||||
| 					xl.Trace("frpc visitor get udp packet from workConn: %s", m.Content) | ||||
| 				}); errRet != nil { | ||||
| 					xl.Info("reader goroutine for udp work connection closed") | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// udp service <- frpc <- frps <- frpc visitor <- user | ||||
| 	workConnSenderFn := func(conn net.Conn) { | ||||
| 		defer func() { | ||||
| 			conn.Close() | ||||
| 			wg.Done() | ||||
| 		}() | ||||
|  | ||||
| 		var errRet error | ||||
| 		for { | ||||
| 			select { | ||||
| 			case udpMsg, ok := <-sv.sendCh: | ||||
| 				if !ok { | ||||
| 					xl.Info("sender goroutine for udp work connection closed") | ||||
| 					return | ||||
| 				} | ||||
|  | ||||
| 				if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil { | ||||
| 					xl.Warn("sender goroutine for udp work connection closed: %v", errRet) | ||||
| 					return | ||||
| 				} | ||||
| 				xl.Trace("send udp package to workConn: %s", udpMsg.Content) | ||||
| 			case <-closeCh: | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	go workConnReaderFn(workConn) | ||||
| 	go workConnSenderFn(workConn) | ||||
|  | ||||
| 	wg.Wait() | ||||
| 	xl.Info("sudp worker is closed") | ||||
| } | ||||
|  | ||||
| func (sv *SUDPVisitor) getNewVisitorConn() (net.Conn, error) { | ||||
| 	xl := xlog.FromContextSafe(sv.ctx) | ||||
| 	visitorConn, err := sv.ctl.connectServer() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("frpc connect frps error: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	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 { | ||||
| 		return nil, fmt.Errorf("frpc send newVisitorConnMsg to frps error: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	var newVisitorConnRespMsg msg.NewVisitorConnResp | ||||
| 	visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second)) | ||||
| 	err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("frpc read newVisitorConnRespMsg error: %v", err) | ||||
| 	} | ||||
| 	visitorConn.SetReadDeadline(time.Time{}) | ||||
|  | ||||
| 	if newVisitorConnRespMsg.Error != "" { | ||||
| 		return nil, fmt.Errorf("start new visitor connection error: %s", newVisitorConnRespMsg.Error) | ||||
| 	} | ||||
|  | ||||
| 	var remote io.ReadWriteCloser | ||||
| 	remote = visitorConn | ||||
| 	if sv.cfg.UseEncryption { | ||||
| 		remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk)) | ||||
| 		if err != nil { | ||||
| 			xl.Error("create encryption stream error: %v", err) | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	if sv.cfg.UseCompression { | ||||
| 		remote = frpIo.WithCompression(remote) | ||||
| 	} | ||||
| 	return frpNet.WrapReadWriteCloserToConn(remote, visitorConn), nil | ||||
| } | ||||
|  | ||||
| func (sv *SUDPVisitor) Close() { | ||||
| 	sv.mu.Lock() | ||||
| 	defer sv.mu.Unlock() | ||||
|  | ||||
| 	select { | ||||
| 	case <-sv.checkCloseCh: | ||||
| 		return | ||||
| 	default: | ||||
| 		close(sv.checkCloseCh) | ||||
| 	} | ||||
| 	if sv.udpConn != nil { | ||||
| 		sv.udpConn.Close() | ||||
| 	} | ||||
| 	close(sv.readCh) | ||||
| 	close(sv.sendCh) | ||||
| } | ||||
							
								
								
									
										146
									
								
								client/visitor_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								client/visitor_manager.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | ||||
| // 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 ( | ||||
| 	"context" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/util/xlog" | ||||
| ) | ||||
|  | ||||
| type VisitorManager struct { | ||||
| 	ctl *Control | ||||
|  | ||||
| 	cfgs     map[string]config.VisitorConf | ||||
| 	visitors map[string]Visitor | ||||
|  | ||||
| 	checkInterval time.Duration | ||||
|  | ||||
| 	mu  sync.Mutex | ||||
| 	ctx context.Context | ||||
|  | ||||
| 	stopCh chan struct{} | ||||
| } | ||||
|  | ||||
| func NewVisitorManager(ctx context.Context, ctl *Control) *VisitorManager { | ||||
| 	return &VisitorManager{ | ||||
| 		ctl:           ctl, | ||||
| 		cfgs:          make(map[string]config.VisitorConf), | ||||
| 		visitors:      make(map[string]Visitor), | ||||
| 		checkInterval: 10 * time.Second, | ||||
| 		ctx:           ctx, | ||||
| 		stopCh:        make(chan struct{}), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (vm *VisitorManager) Run() { | ||||
| 	xl := xlog.FromContextSafe(vm.ctx) | ||||
|  | ||||
| 	ticker := time.NewTicker(vm.checkInterval) | ||||
| 	defer ticker.Stop() | ||||
|  | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-vm.stopCh: | ||||
| 			xl.Info("gracefully shutdown visitor manager") | ||||
| 			return | ||||
| 		case <-ticker.C: | ||||
| 			vm.mu.Lock() | ||||
| 			for _, cfg := range vm.cfgs { | ||||
| 				name := cfg.GetBaseInfo().ProxyName | ||||
| 				if _, exist := vm.visitors[name]; !exist { | ||||
| 					xl.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) { | ||||
| 	xl := xlog.FromContextSafe(vm.ctx) | ||||
| 	name := cfg.GetBaseInfo().ProxyName | ||||
| 	visitor := NewVisitor(vm.ctx, vm.ctl, cfg) | ||||
| 	err = visitor.Run() | ||||
| 	if err != nil { | ||||
| 		xl.Warn("start error: %v", err) | ||||
| 	} else { | ||||
| 		vm.visitors[name] = visitor | ||||
| 		xl.Info("start visitor success") | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) { | ||||
| 	xl := xlog.FromContextSafe(vm.ctx) | ||||
| 	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 { | ||||
| 		xl.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 { | ||||
| 		xl.Info("visitor added: %v", addNames) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (vm *VisitorManager) Close() { | ||||
| 	vm.mu.Lock() | ||||
| 	defer vm.mu.Unlock() | ||||
| 	for _, v := range vm.visitors { | ||||
| 		v.Close() | ||||
| 	} | ||||
| 	select { | ||||
| 	case <-vm.stopCh: | ||||
| 	default: | ||||
| 		close(vm.stopCh) | ||||
| 	} | ||||
| } | ||||
| @@ -12,36 +12,21 @@ | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package pcrypto | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 	"math/rand" | ||||
| 	"time" | ||||
| 
 | ||||
| 	_ "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" | ||||
| 	rand.Seed(time.Now().UnixNano()) | ||||
| 
 | ||||
| 	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() | ||||
| } | ||||
							
								
								
									
										90
									
								
								cmd/frpc/sub/http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								cmd/frpc/sub/http.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| // 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/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/consts" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	RegisterCommonFlags(httpCmd) | ||||
|  | ||||
| 	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 { | ||||
| 		clientCfg, err := parseClientCommonCfgFromCmd() | ||||
| 		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(clientCfg, proxyConfs, nil, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										82
									
								
								cmd/frpc/sub/https.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								cmd/frpc/sub/https.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| // 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/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/consts" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	RegisterCommonFlags(httpsCmd) | ||||
|  | ||||
| 	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 { | ||||
| 		clientCfg, err := parseClientCommonCfgFromCmd() | ||||
| 		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(clientCfg, proxyConfs, nil, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										84
									
								
								cmd/frpc/sub/reload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								cmd/frpc/sub/reload.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| // 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/fatedier/frp/pkg/config" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	rootCmd.AddCommand(reloadCmd) | ||||
| } | ||||
|  | ||||
| var reloadCmd = &cobra.Command{ | ||||
| 	Use:   "reload", | ||||
| 	Short: "Hot-Reload frpc configuration", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		cfg, _, _, err := config.ParseClientConfig(cfgFile) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		err = reload(cfg) | ||||
| 		if err != nil { | ||||
| 			fmt.Printf("frpc reload error: %v\n", err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		fmt.Printf("reload success\n") | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func reload(clientCfg config.ClientCommonConf) error { | ||||
| 	if clientCfg.AdminPort == 0 { | ||||
| 		return fmt.Errorf("admin_port shoud be set if you want to use reload feature") | ||||
| 	} | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", "http://"+ | ||||
| 		clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/reload", nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+ | ||||
| 		clientCfg.AdminPwd)) | ||||
|  | ||||
| 	req.Header.Add("Authorization", authStr) | ||||
| 	resp, err := http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	if resp.StatusCode == 200 { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	body, err := ioutil.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body))) | ||||
| } | ||||
							
								
								
									
										215
									
								
								cmd/frpc/sub/root.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								cmd/frpc/sub/root.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,215 @@ | ||||
| // 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/fatedier/frp/client" | ||||
| 	"github.com/fatedier/frp/pkg/auth" | ||||
| 	"github.com/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/util/log" | ||||
| 	"github.com/fatedier/frp/pkg/util/version" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	CfgFileTypeIni = iota | ||||
| 	CfgFileTypeCmd | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	cfgFile     string | ||||
| 	showVersion bool | ||||
|  | ||||
| 	serverAddr      string | ||||
| 	user            string | ||||
| 	protocol        string | ||||
| 	token           string | ||||
| 	logLevel        string | ||||
| 	logFile         string | ||||
| 	logMaxDays      int | ||||
| 	disableLogColor bool | ||||
|  | ||||
| 	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 | ||||
| 	multiplexer       string | ||||
| 	serverName        string | ||||
| 	bindAddr          string | ||||
| 	bindPort          int | ||||
|  | ||||
| 	tlsEnable bool | ||||
|  | ||||
| 	kcpDoneCh chan struct{} | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc") | ||||
| 	rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc") | ||||
|  | ||||
| 	kcpDoneCh = make(chan struct{}) | ||||
| } | ||||
|  | ||||
| func RegisterCommonFlags(cmd *cobra.Command) { | ||||
| 	cmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address") | ||||
| 	cmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user") | ||||
| 	cmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket") | ||||
| 	cmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token") | ||||
| 	cmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") | ||||
| 	cmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path") | ||||
| 	cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days") | ||||
| 	cmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console") | ||||
| 	cmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", false, "enable frpc tls") | ||||
| } | ||||
|  | ||||
| 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 parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) { | ||||
| 	cfg = config.GetDefaultClientConf() | ||||
|  | ||||
| 	ipStr, portStr, err := net.SplitHostPort(serverAddr) | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("invalid server_addr: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	cfg.ServerAddr = ipStr | ||||
| 	cfg.ServerPort, err = strconv.Atoi(portStr) | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("invalid server_addr: %v", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	cfg.User = user | ||||
| 	cfg.Protocol = protocol | ||||
| 	cfg.LogLevel = logLevel | ||||
| 	cfg.LogFile = logFile | ||||
| 	cfg.LogMaxDays = int64(logMaxDays) | ||||
| 	cfg.DisableLogColor = disableLogColor | ||||
|  | ||||
| 	// Only token authentication is supported in cmd mode | ||||
| 	cfg.ClientConfig = auth.GetDefaultClientConf() | ||||
| 	cfg.Token = token | ||||
| 	cfg.TLSEnable = tlsEnable | ||||
|  | ||||
| 	cfg.Complete() | ||||
| 	if err = cfg.Validate(); err != nil { | ||||
| 		err = fmt.Errorf("Parse config error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func runClient(cfgFilePath string) error { | ||||
| 	cfg, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(cfgFilePath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath) | ||||
| } | ||||
|  | ||||
| func startService( | ||||
| 	cfg config.ClientCommonConf, | ||||
| 	pxyCfgs map[string]config.ProxyConf, | ||||
| 	visitorCfgs map[string]config.VisitorConf, | ||||
| 	cfgFile string, | ||||
| ) (err error) { | ||||
|  | ||||
| 	log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, | ||||
| 		cfg.LogMaxDays, cfg.DisableLogColor) | ||||
|  | ||||
| 	if cfg.DNSServer != "" { | ||||
| 		s := cfg.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(cfg, pxyCfgs, visitorCfgs, cfgFile) | ||||
| 	if errRet != nil { | ||||
| 		err = errRet | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Capture the exit signal if we use kcp. | ||||
| 	if cfg.Protocol == "kcp" { | ||||
| 		go handleSignal(svr) | ||||
| 	} | ||||
|  | ||||
| 	err = svr.Run() | ||||
| 	if err == nil && cfg.Protocol == "kcp" { | ||||
| 		<-kcpDoneCh | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										147
									
								
								cmd/frpc/sub/status.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								cmd/frpc/sub/status.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,147 @@ | ||||
| // 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/fatedier/frp/client" | ||||
| 	"github.com/fatedier/frp/pkg/config" | ||||
|  | ||||
| 	"github.com/rodaine/table" | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| 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 { | ||||
| 		cfg, _, _, err := config.ParseClientConfig(cfgFile) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		if err = status(cfg); err != nil { | ||||
| 			fmt.Printf("frpc get status error: %v\n", err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func status(clientCfg config.ClientCommonConf) error { | ||||
| 	if clientCfg.AdminPort == 0 { | ||||
| 		return fmt.Errorf("admin_port shoud be set if you want to get proxy status") | ||||
| 	} | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", "http://"+ | ||||
| 		clientCfg.AdminAddr+":"+fmt.Sprintf("%d", clientCfg.AdminPort)+"/api/status", nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(clientCfg.AdminUser+":"+ | ||||
| 		clientCfg.AdminPwd)) | ||||
|  | ||||
| 	req.Header.Add("Authorization", authStr) | ||||
| 	resp, err := http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	if resp.StatusCode != 200 { | ||||
| 		return fmt.Errorf("admin api status code [%d]", resp.StatusCode) | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| } | ||||
							
								
								
									
										107
									
								
								cmd/frpc/sub/stcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								cmd/frpc/sub/stcp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| // 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/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/consts" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	RegisterCommonFlags(stcpCmd) | ||||
|  | ||||
| 	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 { | ||||
| 		clientCfg, err := parseClientCommonCfgFromCmd() | ||||
| 		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(clientCfg, proxyConfs, visitorConfs, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										106
									
								
								cmd/frpc/sub/sudp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								cmd/frpc/sub/sudp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | ||||
| // 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/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/consts" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	RegisterCommonFlags(sudpCmd) | ||||
|  | ||||
| 	sudpCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") | ||||
| 	sudpCmd.PersistentFlags().StringVarP(&role, "role", "", "server", "role") | ||||
| 	sudpCmd.PersistentFlags().StringVarP(&sk, "sk", "", "", "secret key") | ||||
| 	sudpCmd.PersistentFlags().StringVarP(&serverName, "server_name", "", "", "server name") | ||||
| 	sudpCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip") | ||||
| 	sudpCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") | ||||
| 	sudpCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "", "bind addr") | ||||
| 	sudpCmd.PersistentFlags().IntVarP(&bindPort, "bind_port", "", 0, "bind port") | ||||
| 	sudpCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") | ||||
| 	sudpCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") | ||||
|  | ||||
| 	rootCmd.AddCommand(sudpCmd) | ||||
| } | ||||
|  | ||||
| var sudpCmd = &cobra.Command{ | ||||
| 	Use:   "sudp", | ||||
| 	Short: "Run frpc with a single sudp proxy", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		clientCfg, err := parseClientCommonCfgFromCmd() | ||||
| 		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.SUDPProxyConf{} | ||||
| 			cfg.ProxyName = prefix + proxyName | ||||
| 			cfg.ProxyType = consts.SUDPProxy | ||||
| 			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.SUDPVisitorConf{} | ||||
| 			cfg.ProxyName = prefix + proxyName | ||||
| 			cfg.ProxyType = consts.SUDPProxy | ||||
| 			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(clientCfg, proxyConfs, visitorConfs, "") | ||||
| 		if err != nil { | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										79
									
								
								cmd/frpc/sub/tcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								cmd/frpc/sub/tcp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| // 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/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/consts" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	RegisterCommonFlags(tcpCmd) | ||||
|  | ||||
| 	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 { | ||||
| 		clientCfg, err := parseClientCommonCfgFromCmd() | ||||
| 		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(clientCfg, proxyConfs, nil, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										84
									
								
								cmd/frpc/sub/tcpmux.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								cmd/frpc/sub/tcpmux.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| // Copyright 2020 guylewin, guy@lewin.co.il | ||||
| // | ||||
| // 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/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/consts" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	RegisterCommonFlags(tcpMuxCmd) | ||||
|  | ||||
| 	tcpMuxCmd.PersistentFlags().StringVarP(&proxyName, "proxy_name", "n", "", "proxy name") | ||||
| 	tcpMuxCmd.PersistentFlags().StringVarP(&localIP, "local_ip", "i", "127.0.0.1", "local ip") | ||||
| 	tcpMuxCmd.PersistentFlags().IntVarP(&localPort, "local_port", "l", 0, "local port") | ||||
| 	tcpMuxCmd.PersistentFlags().StringVarP(&customDomains, "custom_domain", "d", "", "custom domain") | ||||
| 	tcpMuxCmd.PersistentFlags().StringVarP(&subDomain, "sd", "", "", "sub domain") | ||||
| 	tcpMuxCmd.PersistentFlags().StringVarP(&multiplexer, "mux", "", "", "multiplexer") | ||||
| 	tcpMuxCmd.PersistentFlags().BoolVarP(&useEncryption, "ue", "", false, "use encryption") | ||||
| 	tcpMuxCmd.PersistentFlags().BoolVarP(&useCompression, "uc", "", false, "use compression") | ||||
|  | ||||
| 	rootCmd.AddCommand(tcpMuxCmd) | ||||
| } | ||||
|  | ||||
| var tcpMuxCmd = &cobra.Command{ | ||||
| 	Use:   "tcpmux", | ||||
| 	Short: "Run frpc with a single tcpmux proxy", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		clientCfg, err := parseClientCommonCfgFromCmd() | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		cfg := &config.TCPMuxProxyConf{} | ||||
| 		var prefix string | ||||
| 		if user != "" { | ||||
| 			prefix = user + "." | ||||
| 		} | ||||
| 		cfg.ProxyName = prefix + proxyName | ||||
| 		cfg.ProxyType = consts.TCPMuxProxy | ||||
| 		cfg.LocalIP = localIP | ||||
| 		cfg.LocalPort = localPort | ||||
| 		cfg.CustomDomains = strings.Split(customDomains, ",") | ||||
| 		cfg.SubDomain = subDomain | ||||
| 		cfg.Multiplexer = multiplexer | ||||
| 		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(clientCfg, proxyConfs, nil, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										79
									
								
								cmd/frpc/sub/udp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								cmd/frpc/sub/udp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| // 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/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/consts" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	RegisterCommonFlags(udpCmd) | ||||
|  | ||||
| 	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 { | ||||
| 		clientCfg, err := parseClientCommonCfgFromCmd() | ||||
| 		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(clientCfg, proxyConfs, nil, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										43
									
								
								cmd/frpc/sub/verify.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								cmd/frpc/sub/verify.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| // Copyright 2021 The frp Authors | ||||
| // | ||||
| // 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/fatedier/frp/pkg/config" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	rootCmd.AddCommand(verifyCmd) | ||||
| } | ||||
|  | ||||
| var verifyCmd = &cobra.Command{ | ||||
| 	Use:   "verify", | ||||
| 	Short: "Verify that the configures is valid", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		_, _, _, err := config.ParseClientConfig(cfgFile) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		fmt.Printf("frpc: the configuration file %s syntax is ok\n", cfgFile) | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										107
									
								
								cmd/frpc/sub/xtcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								cmd/frpc/sub/xtcp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| // 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/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/consts" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	RegisterCommonFlags(xtcpCmd) | ||||
|  | ||||
| 	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 { | ||||
| 		clientCfg, err := parseClientCommonCfgFromCmd() | ||||
| 		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.XTCPProxy | ||||
| 			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.XTCPProxy | ||||
| 			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(clientCfg, proxyConfs, visitorConfs, "") | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										32
									
								
								cmd/frps/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								cmd/frps/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| // 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 ( | ||||
| 	"math/rand" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/fatedier/golib/crypto" | ||||
|  | ||||
| 	_ "github.com/fatedier/frp/assets/frps/statik" | ||||
| 	_ "github.com/fatedier/frp/pkg/metrics" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	crypto.DefaultSalt = "frp" | ||||
| 	rand.Seed(time.Now().UnixNano()) | ||||
|  | ||||
| 	Execute() | ||||
| } | ||||
							
								
								
									
										212
									
								
								cmd/frps/root.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								cmd/frps/root.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,212 @@ | ||||
| // 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/fatedier/frp/pkg/auth" | ||||
| 	"github.com/fatedier/frp/pkg/config" | ||||
| 	"github.com/fatedier/frp/pkg/util/log" | ||||
| 	"github.com/fatedier/frp/pkg/util/util" | ||||
| 	"github.com/fatedier/frp/pkg/util/version" | ||||
| 	"github.com/fatedier/frp/server" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| 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 | ||||
| 	enablePrometheus  bool | ||||
| 	assetsDir         string | ||||
| 	logFile           string | ||||
| 	logLevel          string | ||||
| 	logMaxDays        int64 | ||||
| 	disableLogColor   bool | ||||
| 	token             string | ||||
| 	subDomainHost     string | ||||
| 	tcpMux            bool | ||||
| 	allowPorts        string | ||||
| 	maxPoolCount      int64 | ||||
| 	maxPortsPerClient int64 | ||||
| 	tlsOnly           bool | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps") | ||||
| 	rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frps") | ||||
|  | ||||
| 	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().BoolVarP(&enablePrometheus, "enable_prometheus", "", false, "enable prometheus dashboard") | ||||
| 	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().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console") | ||||
|  | ||||
| 	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") | ||||
| 	rootCmd.PersistentFlags().BoolVarP(&tlsOnly, "tls_only", "", false, "frps tls only") | ||||
| } | ||||
|  | ||||
| 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 cfg config.ServerCommonConf | ||||
| 		var err error | ||||
| 		if cfgFile != "" { | ||||
| 			var content []byte | ||||
| 			content, err = config.GetRenderedConfFromFile(cfgFile) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			cfg, err = parseServerCommonCfg(CfgFileTypeIni, content) | ||||
| 		} else { | ||||
| 			cfg, err = parseServerCommonCfg(CfgFileTypeCmd, nil) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		err = runServer(cfg) | ||||
| 		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, source []byte) (cfg config.ServerCommonConf, err error) { | ||||
| 	if fileType == CfgFileTypeIni { | ||||
| 		cfg, err = config.UnmarshalServerConfFromIni(source) | ||||
| 	} else if fileType == CfgFileTypeCmd { | ||||
| 		cfg, err = parseServerCommonCfgFromCmd() | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	cfg.Complete() | ||||
| 	err = cfg.Validate() | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("Parse config error: %v", err) | ||||
| 		return | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) { | ||||
| 	cfg = config.GetDefaultServerConf() | ||||
|  | ||||
| 	cfg.BindAddr = bindAddr | ||||
| 	cfg.BindPort = bindPort | ||||
| 	cfg.BindUDPPort = bindUDPPort | ||||
| 	cfg.KCPBindPort = kcpBindPort | ||||
| 	cfg.ProxyBindAddr = proxyBindAddr | ||||
| 	cfg.VhostHTTPPort = vhostHTTPPort | ||||
| 	cfg.VhostHTTPSPort = vhostHTTPSPort | ||||
| 	cfg.VhostHTTPTimeout = vhostHTTPTimeout | ||||
| 	cfg.DashboardAddr = dashboardAddr | ||||
| 	cfg.DashboardPort = dashboardPort | ||||
| 	cfg.DashboardUser = dashboardUser | ||||
| 	cfg.DashboardPwd = dashboardPwd | ||||
| 	cfg.EnablePrometheus = enablePrometheus | ||||
| 	cfg.LogFile = logFile | ||||
| 	cfg.LogLevel = logLevel | ||||
| 	cfg.LogMaxDays = logMaxDays | ||||
| 	cfg.SubDomainHost = subDomainHost | ||||
| 	cfg.TLSOnly = tlsOnly | ||||
|  | ||||
| 	// Only token authentication is supported in cmd mode | ||||
| 	cfg.ServerConfig = auth.GetDefaultServerConf() | ||||
| 	cfg.Token = token | ||||
| 	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 { | ||||
| 			cfg.AllowPorts[int(port)] = struct{}{} | ||||
| 		} | ||||
| 	} | ||||
| 	cfg.MaxPortsPerClient = maxPortsPerClient | ||||
| 	cfg.DisableLogColor = disableLogColor | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func runServer(cfg config.ServerCommonConf) (err error) { | ||||
| 	log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor) | ||||
|  | ||||
| 	if cfgFile != "" { | ||||
| 		log.Info("frps uses config file: %s", cfgFile) | ||||
| 	} else { | ||||
| 		log.Info("frps uses command line arguments for config") | ||||
| 	} | ||||
|  | ||||
| 	svr, err := server.NewService(cfg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	log.Info("frps started successfully") | ||||
| 	svr.Run() | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										53
									
								
								cmd/frps/verify.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								cmd/frps/verify.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // Copyright 2021 The frp Authors | ||||
| // | ||||
| // 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/fatedier/frp/pkg/config" | ||||
|  | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	rootCmd.AddCommand(verifyCmd) | ||||
| } | ||||
|  | ||||
| var verifyCmd = &cobra.Command{ | ||||
| 	Use:   "verify", | ||||
| 	Short: "Verify that the configures is valid", | ||||
| 	RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 		if cfgFile == "" { | ||||
| 			fmt.Println("no config file is specified") | ||||
| 			return nil | ||||
| 		} | ||||
| 		iniContent, err := config.GetRenderedConfFromFile(cfgFile) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		_, err = parseServerCommonCfg(CfgFileTypeIni, iniContent) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		fmt.Printf("frps: the configuration file %s syntax is ok\n", cfgFile) | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										320
									
								
								conf/frpc_full.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								conf/frpc_full.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,320 @@ | ||||
| # [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" | ||||
| # For single "server_addr" field, no need square brackets, like "server_addr = ::". | ||||
| server_addr = 0.0.0.0 | ||||
| server_port = 7000 | ||||
|  | ||||
| # if you want to connect frps by http proxy or socks5 proxy or ntlm 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 | ||||
| # http_proxy = ntlm://user:passwd@192.168.1.128:2080 | ||||
|  | ||||
| # console or real logFile path like ./frpc.log | ||||
| log_file = ./frpc.log | ||||
|  | ||||
| # trace, debug, info, warn, error | ||||
| log_level = info | ||||
|  | ||||
| log_max_days = 3 | ||||
|  | ||||
| # disable log colors when log_file is console, default is false | ||||
| disable_log_color = false | ||||
|  | ||||
| # for authentication, should be same as your frps.ini | ||||
| # authenticate_heartbeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false. | ||||
| authenticate_heartbeats = false | ||||
|  | ||||
| # authenticate_new_work_conns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false. | ||||
| authenticate_new_work_conns = false | ||||
|  | ||||
| # auth token | ||||
| token = 12345678 | ||||
|  | ||||
| # oidc_client_id specifies the client ID to use to get a token in OIDC authentication if AuthenticationMethod == "oidc". | ||||
| # By default, this value is "". | ||||
| oidc_client_id = | ||||
|  | ||||
| # oidc_client_secret specifies the client secret to use to get a token in OIDC authentication if AuthenticationMethod == "oidc". | ||||
| # By default, this value is "". | ||||
| oidc_client_secret = | ||||
|  | ||||
| # oidc_audience specifies the audience of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "". | ||||
| oidc_audience = | ||||
|  | ||||
| # oidc_token_endpoint_url specifies the URL which implements OIDC Token Endpoint. | ||||
| # It will be used to get an OIDC token if AuthenticationMethod == "oidc". By default, this value is "". | ||||
| oidc_token_endpoint_url = | ||||
|  | ||||
| # 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 | ||||
| # Admin assets directory. By default, these assets are bundled with frpc. | ||||
| # assets_dir = ./static | ||||
|  | ||||
| # 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, kcp and websocket, default is tcp | ||||
| protocol = tcp | ||||
|  | ||||
| # if tls_enable is true, frpc will connect frps by tls | ||||
| tls_enable = true | ||||
|  | ||||
| # tls_cert_file = client.crt | ||||
| # tls_key_file = client.key | ||||
| # tls_trusted_ca_file = ca.crt | ||||
| # tls_server_name = example.com | ||||
|  | ||||
| # 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 seperated 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 | ||||
|  | ||||
| # additional meta info for client | ||||
| meta_var1 = 123 | ||||
| meta_var2 = 234 | ||||
|  | ||||
| # specify udp packet size, unit is byte. If not set, the default value is 1500. | ||||
| # This parameter should be same between client and server. | ||||
| # It affects the udp and sudp proxy. | ||||
| udp_packet_size = 1500 | ||||
|  | ||||
| # include other config files for proxies. | ||||
| # includes = ./confd/*.ini | ||||
|  | ||||
| # '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 | ||||
| # limit bandwidth for this proxy, unit is KB and MB | ||||
| bandwidth_limit = 1MB | ||||
| # 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 | ||||
| # additional meta info for each proxy | ||||
| meta_var1 = 123 | ||||
| meta_var2 = 234 | ||||
|  | ||||
| [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 | ||||
| # if not empty, frpc will use proxy protocol to transfer connection info to your local service | ||||
| # v1 or v2 or empty | ||||
| proxy_protocol_version = v2 | ||||
|  | ||||
| [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 | ||||
|  | ||||
| [plugin_https2http] | ||||
| type = https | ||||
| custom_domains = test.yourdomain.com | ||||
| plugin = https2http | ||||
| plugin_local_addr = 127.0.0.1:80 | ||||
| plugin_crt_path = ./server.crt | ||||
| plugin_key_path = ./server.key | ||||
| plugin_host_header_rewrite = 127.0.0.1 | ||||
| plugin_header_X-From-Where = frp | ||||
|  | ||||
| [plugin_https2https] | ||||
| type = https | ||||
| custom_domains = test.yourdomain.com | ||||
| plugin = https2https | ||||
| plugin_local_addr = 127.0.0.1:443 | ||||
| plugin_crt_path = ./server.crt | ||||
| plugin_key_path = ./server.key | ||||
| plugin_host_header_rewrite = 127.0.0.1 | ||||
| plugin_header_X-From-Where = frp | ||||
|  | ||||
| [plugin_http2https] | ||||
| type = http | ||||
| custom_domains = test.yourdomain.com | ||||
| plugin = http2https | ||||
| plugin_local_addr = 127.0.0.1:443 | ||||
| plugin_host_header_rewrite = 127.0.0.1 | ||||
| plugin_header_X-From-Where = frp | ||||
|  | ||||
| [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 | ||||
|  | ||||
| [tcpmuxhttpconnect] | ||||
| type = tcpmux | ||||
| multiplexer = httpconnect | ||||
| local_ip = 127.0.0.1 | ||||
| local_port = 10701 | ||||
| custom_domains = tunnel1 | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										141
									
								
								conf/frps_full.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								conf/frps_full.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,141 @@ | ||||
| # [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" | ||||
| # For single "bind_addr" field, no need square brackets, like "bind_addr = ::". | ||||
| 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 | ||||
|  | ||||
| # tcpmux_httpconnect_port specifies the port that the server listens for TCP | ||||
| # HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP | ||||
| # requests on one single port. If it's not - it will listen on this value for | ||||
| # HTTP CONNECT requests. By default, this value is 0. | ||||
| # tcpmux_httpconnect_port = 1337 | ||||
|  | ||||
| # 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 | ||||
|  | ||||
| # enable_prometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port} in /metrics api. | ||||
| enable_prometheus = true | ||||
|  | ||||
| # 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 | ||||
|  | ||||
| # disable log colors when log_file is console, default is false | ||||
| disable_log_color = false | ||||
|  | ||||
| # DetailedErrorsToClient defines whether to send the specific error (with debug info) to frpc. By default, this value is true. | ||||
| detailed_errors_to_client = true | ||||
|  | ||||
| # authentication_method specifies what authentication method to use authenticate frpc with frps. | ||||
| # If "token" is specified - token will be read into login message. | ||||
| # If "oidc" is specified - OIDC (Open ID Connect) token will be issued using OIDC settings. By default, this value is "token". | ||||
| authentication_method = token | ||||
|  | ||||
| # authenticate_heartbeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false. | ||||
| authenticate_heartbeats = false | ||||
|  | ||||
| # AuthenticateNewWorkConns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false. | ||||
| authenticate_new_work_conns = false | ||||
|  | ||||
| # auth token | ||||
| token = 12345678 | ||||
|  | ||||
| # oidc_issuer specifies the issuer to verify OIDC tokens with. | ||||
| # By default, this value is "". | ||||
| oidc_issuer = | ||||
|  | ||||
| # oidc_audience specifies the audience OIDC tokens should contain when validated. | ||||
| # By default, this value is "". | ||||
| oidc_audience = | ||||
|  | ||||
| # oidc_skip_expiry_check specifies whether to skip checking if the OIDC token is expired. | ||||
| # By default, this value is false. | ||||
| oidc_skip_expiry_check = false | ||||
|  | ||||
|  | ||||
| # oidc_skip_issuer_check specifies whether to skip checking if the OIDC token's issuer claim matches the issuer specified in OidcIssuer. | ||||
| # By default, this value is false. | ||||
| oidc_skip_issuer_check = false | ||||
|  | ||||
| # heartbeat configure, it's not recommended to modify the default value | ||||
| # the default value of heartbeat_timeout is 90 | ||||
| # heartbeat_timeout = 90 | ||||
|  | ||||
| # user_conn_timeout configure, it's not recommended to modify the default value | ||||
| # the default value of user_conn_timeout is 10 | ||||
| # user_conn_timeout = 10 | ||||
|  | ||||
| # 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 | ||||
|  | ||||
| # tls_only specifies whether to only accept TLS-encrypted connections. By default, the value is false. | ||||
| tls_only = false | ||||
|  | ||||
| # tls_cert_file = server.crt | ||||
| # tls_key_file = server.key | ||||
| # tls_trusted_ca_file = ca.crt | ||||
|  | ||||
| # 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 | ||||
|  | ||||
| # custom 404 page for HTTP requests | ||||
| # custom_404_page = /path/to/404.html | ||||
|  | ||||
| # specify udp packet size, unit is byte. If not set, the default value is 1500. | ||||
| # This parameter should be same between client and server. | ||||
| # It affects the udp and sudp proxy. | ||||
| udp_packet_size = 1500 | ||||
|  | ||||
| [plugin.user-manager] | ||||
| addr = 127.0.0.1:9000 | ||||
| path = /handler | ||||
| ops = Login | ||||
|  | ||||
| [plugin.port-manager] | ||||
| addr = 127.0.0.1:9001 | ||||
| path = /handler | ||||
| ops = NewProxy | ||||
							
								
								
									
										14
									
								
								conf/systemd/frpc.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								conf/systemd/frpc.service
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| [Unit] | ||||
| Description=Frp Client Service | ||||
| After=network.target | ||||
|  | ||||
| [Service] | ||||
| Type=simple | ||||
| User=nobody | ||||
| Restart=on-failure | ||||
| RestartSec=5s | ||||
| ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini | ||||
| ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
							
								
								
									
										14
									
								
								conf/systemd/frpc@.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								conf/systemd/frpc@.service
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| [Unit] | ||||
| Description=Frp Client Service | ||||
| After=network.target | ||||
|  | ||||
| [Service] | ||||
| Type=idle | ||||
| User=nobody | ||||
| Restart=on-failure | ||||
| RestartSec=5s | ||||
| ExecStart=/usr/bin/frpc -c /etc/frp/%i.ini | ||||
| ExecReload=/usr/bin/frpc reload -c /etc/frp/%i.ini | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
							
								
								
									
										13
									
								
								conf/systemd/frps.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								conf/systemd/frps.service
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| [Unit] | ||||
| Description=Frp Server Service | ||||
| After=network.target | ||||
|  | ||||
| [Service] | ||||
| Type=simple | ||||
| User=nobody | ||||
| Restart=on-failure | ||||
| RestartSec=5s | ||||
| ExecStart=/usr/bin/frps -c /etc/frp/frps.ini | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
							
								
								
									
										13
									
								
								conf/systemd/frps@.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								conf/systemd/frps@.service
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| [Unit] | ||||
| Description=Frp Server Service | ||||
| After=network.target | ||||
|  | ||||
| [Service] | ||||
| Type=simple | ||||
| User=nobody | ||||
| Restart=on-failure | ||||
| RestartSec=5s | ||||
| ExecStart=/usr/bin/frps -c /etc/frp/%i.ini | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
							
								
								
									
										
											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 | ||||
| ``` | ||||
							
								
								
									
										241
									
								
								doc/server_plugin.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								doc/server_plugin.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,241 @@ | ||||
| ### Server Plugin | ||||
|  | ||||
| frp server plugin is aimed to extend frp's ability without modifying the Golang code. | ||||
|  | ||||
| An external server should run in a different process receiving RPC calls from frps. | ||||
| Before frps is doing some operations, it will send RPC requests to notify the external RPC server and act according to its response. | ||||
|  | ||||
| ### RPC request | ||||
|  | ||||
| RPC requests are based on JSON over HTTP. | ||||
|  | ||||
| When a server plugin accepts an operation request, it can respond with three different responses: | ||||
|  | ||||
| * Reject operation and return a reason. | ||||
| * Allow operation and keep original content. | ||||
| * Allow operation and return modified content. | ||||
|  | ||||
| ### Interface | ||||
|  | ||||
| HTTP path can be configured for each manage plugin in frps. We'll assume for this example that it's `/handler`. | ||||
|  | ||||
| A request to the RPC server will look like: | ||||
|  | ||||
| ``` | ||||
| POST /handler?version=0.1.0&op=Login | ||||
| { | ||||
|     "version": "0.1.0", | ||||
|     "op": "Login", | ||||
|     "content": { | ||||
|         ... // Operation info | ||||
|     } | ||||
| } | ||||
|  | ||||
| Request Header: | ||||
| X-Frp-Reqid: for tracing | ||||
| ``` | ||||
|  | ||||
| The response can look like any of the following: | ||||
|  | ||||
| * Non-200 HTTP response status code (this will automatically tell frps that the request should fail) | ||||
|  | ||||
| * Reject operation: | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "reject": true, | ||||
|     "reject_reason": "invalid user" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| * Allow operation and keep original content: | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "reject": false, | ||||
|     "unchange": true | ||||
| } | ||||
| ``` | ||||
|  | ||||
| * Allow operation and modify content | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "unchange": "false", | ||||
|     "content": { | ||||
|         ... // Replaced content | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Operation | ||||
|  | ||||
| Currently `Login`, `NewProxy`, `Ping`, `NewWorkConn` and `NewUserConn` operations are supported. | ||||
|  | ||||
| #### Login | ||||
|  | ||||
| Client login operation | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "content": { | ||||
|         "version": <string>, | ||||
|         "hostname": <string>, | ||||
|         "os": <string>, | ||||
|         "arch": <string>, | ||||
|         "user": <string>, | ||||
|         "timestamp": <int64>, | ||||
|         "privilege_key": <string>, | ||||
|         "run_id": <string>, | ||||
|         "pool_count": <int>, | ||||
|         "metas": map<string>string | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### NewProxy | ||||
|  | ||||
| Create new proxy | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "content": { | ||||
|         "user": { | ||||
|             "user": <string>, | ||||
|             "metas": map<string>string | ||||
|             "run_id": <string> | ||||
|         }, | ||||
|         "proxy_name": <string>, | ||||
|         "proxy_type": <string>, | ||||
|         "use_encryption": <bool>, | ||||
|         "use_compression": <bool>, | ||||
|         "group": <string>, | ||||
|         "group_key": <string>, | ||||
|  | ||||
|         // tcp and udp only | ||||
|         "remote_port": <int>, | ||||
|  | ||||
|         // http and https only | ||||
|         "custom_domains": []<string>, | ||||
|         "subdomain": <string>, | ||||
|         "locations": <string>, | ||||
|         "http_user": <string>, | ||||
|         "http_pwd": <string>, | ||||
|         "host_header_rewrite": <string>, | ||||
|         "headers": map<string>string, | ||||
|  | ||||
|         // stcp only | ||||
|         "sk": <string>, | ||||
|  | ||||
|         // tcpmux only | ||||
|         "multiplexer": <string> | ||||
|  | ||||
|         "metas": map<string>string | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### Ping | ||||
|  | ||||
| Heartbeat from frpc | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "content": { | ||||
|         "user": { | ||||
|             "user": <string>, | ||||
|             "metas": map<string>string | ||||
|             "run_id": <string> | ||||
|         }, | ||||
|         "timestamp": <int64>, | ||||
|         "privilege_key": <string> | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### NewWorkConn | ||||
|  | ||||
| New work connection received from frpc (RPC sent after `run_id` is matched with an existing frp connection) | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "content": { | ||||
|         "user": { | ||||
|             "user": <string>, | ||||
|             "metas": map<string>string | ||||
|             "run_id": <string> | ||||
|         }, | ||||
|         "run_id": <string> | ||||
|         "timestamp": <int64>, | ||||
|         "privilege_key": <string> | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### NewUserConn | ||||
|  | ||||
| New user connection received from proxy (support `tcp`, `stcp`, `https` and `tcpmux`) . | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "content": { | ||||
|         "user": { | ||||
|             "user": <string>, | ||||
|             "metas": map<string>string | ||||
|             "run_id": <string> | ||||
|         }, | ||||
|         "proxy_name": <string>, | ||||
|         "proxy_type": <string>, | ||||
|         "remote_addr": <string> | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Server Plugin Configuration | ||||
|  | ||||
| ```ini | ||||
| # frps.ini | ||||
| [common] | ||||
| bind_port = 7000 | ||||
|  | ||||
| [plugin.user-manager] | ||||
| addr = 127.0.0.1:9000 | ||||
| path = /handler | ||||
| ops = Login | ||||
|  | ||||
| [plugin.port-manager] | ||||
| addr = 127.0.0.1:9001 | ||||
| path = /handler | ||||
| ops = NewProxy | ||||
| ``` | ||||
|  | ||||
| - addr: the address where the external RPC service listens. Defaults to http. For https, specify the schema: `addr = https://127.0.0.1:9001`. | ||||
| - path: http request url path for the POST request. | ||||
| - ops: operations plugin needs to handle (e.g. "Login", "NewProxy", ...). | ||||
| - tls_verify: When the schema is https, we verify by default. Set this value to false if you want to skip verification. | ||||
|  | ||||
| ### Metadata | ||||
|  | ||||
| Metadata will be sent to the server plugin in each RPC request. | ||||
|  | ||||
| There are 2 types of metadata entries - 1 under `[common]` and the other under each proxy configuration. | ||||
| Metadata entries under `[common]` will be sent in `Login` under the key `metas`, and in any other RPC request under `user.metas`. | ||||
| Metadata entries under each proxy configuration will be sent in `NewProxy` op only, under `metas`. | ||||
|  | ||||
| Metadata entries start with `meta_`. This is an example of metadata entries in `[common]` and under the proxy named `[ssh]`: | ||||
|  | ||||
| ``` | ||||
| # frpc.ini | ||||
| [common] | ||||
| server_addr = 127.0.0.1 | ||||
| server_port = 7000 | ||||
| user = fake | ||||
| meta_token = fake | ||||
| meta_version = 1.0.0 | ||||
|  | ||||
| [ssh] | ||||
| type = tcp | ||||
| local_port = 22 | ||||
| remote_port = 6000 | ||||
| meta_id = 123 | ||||
| ``` | ||||
							
								
								
									
										228
									
								
								doc/server_plugin_zh.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								doc/server_plugin_zh.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,228 @@ | ||||
| ### 服务端管理插件 | ||||
|  | ||||
| frp 管理插件的作用是在不侵入自身代码的前提下,扩展 frp 服务端的能力。 | ||||
|  | ||||
| frp 管理插件会以单独进程的形式运行,并且监听在一个端口上,对外提供 RPC 接口,响应 frps 的请求。 | ||||
|  | ||||
| frps 在执行某些操作前,会根据配置向管理插件发送 RPC 请求,根据管理插件的响应来执行相应的操作。 | ||||
|  | ||||
| ### RPC 请求 | ||||
|  | ||||
| 管理插件接收到操作请求后,可以给出三种回应。 | ||||
|  | ||||
| * 拒绝操作,需要返回拒绝操作的原因。 | ||||
| * 允许操作,不需要修改操作内容。 | ||||
| * 允许操作,对操作请求进行修改后,返回修改后的内容。 | ||||
|  | ||||
| ### 接口 | ||||
|  | ||||
| 接口路径可以在 frps 配置中为每个插件单独配置,这里以 `/handler` 为例。 | ||||
|  | ||||
| Request | ||||
|  | ||||
| ``` | ||||
| POST /handler | ||||
| { | ||||
|     "version": "0.1.0", | ||||
|     "op": "Login", | ||||
|     "content": { | ||||
|         ... // 具体的操作信息 | ||||
|     } | ||||
| } | ||||
|  | ||||
| 请求 Header | ||||
| X-Frp-Reqid: 用于追踪请求 | ||||
| ``` | ||||
|  | ||||
| Response | ||||
|  | ||||
| 非 200 的返回都认为是请求异常。 | ||||
|  | ||||
| 拒绝执行操作 | ||||
|  | ||||
| ``` | ||||
| { | ||||
|    "reject": true, | ||||
|    "reject_reason": "invalid user" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| 允许且内容不需要变动 | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "reject": false, | ||||
|     "unchange": true | ||||
| } | ||||
| ``` | ||||
|  | ||||
| 允许且需要替换操作内容 | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "unchange": "false", | ||||
|     "content": { | ||||
|         ... // 替换后的操作信息,格式必须和请求时的一致 | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 操作类型 | ||||
|  | ||||
| 目前插件支持管理的操作类型有 `Login`、`NewProxy`、`Ping`、`NewWorkConn` 和 `NewUserConn`。 | ||||
|  | ||||
| #### Login | ||||
|  | ||||
| 用户登录操作信息 | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "content": { | ||||
|         "version": <string>, | ||||
|         "hostname": <string>, | ||||
|         "os": <string>, | ||||
|         "arch": <string>, | ||||
|         "user": <string>, | ||||
|         "timestamp": <int64>, | ||||
|         "privilege_key": <string>, | ||||
|         "run_id": <string>, | ||||
|         "pool_count": <int>, | ||||
|         "metas": map<string>string | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### NewProxy | ||||
|  | ||||
| 创建代理的相关信息 | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "content": { | ||||
|         "user": { | ||||
|             "user": <string>, | ||||
|             "metas": map<string>string | ||||
|         }, | ||||
|         "proxy_name": <string>, | ||||
|         "proxy_type": <string>, | ||||
|         "use_encryption": <bool>, | ||||
|         "use_compression": <bool>, | ||||
|         "group": <string>, | ||||
|         "group_key": <string>, | ||||
|  | ||||
|         // tcp and udp only | ||||
|         "remote_port": <int>, | ||||
|  | ||||
|         // http and https only | ||||
|         "custom_domains": []<string>, | ||||
|         "subdomain": <string>, | ||||
|         "locations": <string>, | ||||
|         "http_user": <string>, | ||||
|         "http_pwd": <string>, | ||||
|         "host_header_rewrite": <string>, | ||||
|         "headers": map<string>string, | ||||
|  | ||||
|         "metas": map<string>string | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### Ping | ||||
|  | ||||
| 心跳相关信息 | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "content": { | ||||
|         "user": { | ||||
|             "user": <string>, | ||||
|             "metas": map<string>string | ||||
|             "run_id": <string> | ||||
|         }, | ||||
|         "timestamp": <int64>, | ||||
|         "privilege_key": <string> | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### NewWorkConn | ||||
|  | ||||
| 新增 `frpc` 连接相关信息 | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "content": { | ||||
|         "user": { | ||||
|             "user": <string>, | ||||
|             "metas": map<string>string | ||||
|             "run_id": <string> | ||||
|         }, | ||||
|         "run_id": <string> | ||||
|         "timestamp": <int64>, | ||||
|         "privilege_key": <string> | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| #### NewUserConn | ||||
|  | ||||
| 新增 `proxy` 连接相关信息 (支持 `tcp`、`stcp`、`https` 和 `tcpmux` 协议)。 | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "content": { | ||||
|         "user": { | ||||
|             "user": <string>, | ||||
|             "metas": map<string>string | ||||
|             "run_id": <string> | ||||
|         }, | ||||
|         "proxy_name": <string>, | ||||
|         "proxy_type": <string>, | ||||
|         "remote_addr": <string> | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
|  | ||||
| ### frps 中插件配置 | ||||
|  | ||||
| ```ini | ||||
| [common] | ||||
| bind_port = 7000 | ||||
|  | ||||
| [plugin.user-manager] | ||||
| addr = 127.0.0.1:9000 | ||||
| path = /handler | ||||
| ops = Login | ||||
|  | ||||
| [plugin.port-manager] | ||||
| addr = 127.0.0.1:9001 | ||||
| path = /handler | ||||
| ops = NewProxy | ||||
| ``` | ||||
|  | ||||
| addr: 插件监听的网络地址。 | ||||
| path: 插件监听的 HTTP 请求路径。 | ||||
| ops: 插件需要处理的操作列表,多个 op 以英文逗号分隔。 | ||||
|  | ||||
| ### 元数据 | ||||
|  | ||||
| 为了减少 frps 的代码修改,同时提高管理插件的扩展能力,在 frpc 的配置文件中引入自定义元数据的概念。元数据会在调用 RPC 请求时发送给插件。 | ||||
|  | ||||
| 元数据以 `meta_` 开头,可以配置多个,元数据分为两种,一种配置在 `common` 下,一种配置在各个 proxy 中。 | ||||
|  | ||||
| ``` | ||||
| # frpc.ini | ||||
| [common] | ||||
| server_addr = 127.0.0.1 | ||||
| server_port = 7000 | ||||
| user = fake | ||||
| meta_token = fake | ||||
| meta_version = 1.0.0 | ||||
|  | ||||
| [ssh] | ||||
| type = tcp | ||||
| local_port = 22 | ||||
| remote_port = 6000 | ||||
| meta_id = 123 | ||||
| ``` | ||||
							
								
								
									
										14
									
								
								dockerfiles/Dockerfile-for-frpc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								dockerfiles/Dockerfile-for-frpc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| FROM alpine:3.12.0 AS temp | ||||
|  | ||||
| COPY bin/frpc /tmp | ||||
|  | ||||
| RUN chmod -R 777 /tmp/frpc | ||||
|  | ||||
|  | ||||
| FROM alpine:3.12.0 | ||||
|  | ||||
| WORKDIR /app | ||||
|  | ||||
| COPY --from=temp /tmp/frpc /usr/bin | ||||
|  | ||||
| ENTRYPOINT ["/usr/bin/frpc"] | ||||
							
								
								
									
										14
									
								
								dockerfiles/Dockerfile-for-frps
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								dockerfiles/Dockerfile-for-frps
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| FROM alpine:3.12.0 AS temp | ||||
|  | ||||
| COPY bin/frps /tmp | ||||
|  | ||||
| RUN chmod -R 777 /tmp/frps | ||||
|  | ||||
|  | ||||
| FROM alpine:3.12.0 | ||||
|  | ||||
| WORKDIR /app | ||||
|  | ||||
| COPY --from=temp /tmp/frps /usr/bin | ||||
|  | ||||
| ENTRYPOINT ["/usr/bin/frps"] | ||||
							
								
								
									
										40
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| module github.com/fatedier/frp | ||||
|  | ||||
| go 1.16 | ||||
|  | ||||
| require ( | ||||
| 	github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 | ||||
| 	github.com/coreos/go-oidc v2.2.1+incompatible | ||||
| 	github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb | ||||
| 	github.com/fatedier/golib v0.1.1-0.20200901083111-1f870741e185 | ||||
| 	github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible | ||||
| 	github.com/google/uuid v1.1.1 | ||||
| 	github.com/gorilla/mux v1.7.3 | ||||
| 	github.com/gorilla/websocket v1.4.0 | ||||
| 	github.com/hashicorp/yamux v0.0.0-20210316155119-a95892c5f864 | ||||
| 	github.com/inconshreveable/mousetrap v1.0.0 // indirect | ||||
| 	github.com/klauspost/cpuid v1.2.0 // indirect | ||||
| 	github.com/klauspost/reedsolomon v1.9.1 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.4 // indirect | ||||
| 	github.com/onsi/ginkgo v1.12.3 | ||||
| 	github.com/onsi/gomega v1.10.1 | ||||
| 	github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc | ||||
| 	github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect | ||||
| 	github.com/prometheus/client_golang v1.4.1 | ||||
| 	github.com/rakyll/statik v0.1.1 | ||||
| 	github.com/rodaine/table v1.0.0 | ||||
| 	github.com/smartystreets/goconvey v1.6.4 // indirect | ||||
| 	github.com/spf13/cobra v0.0.3 | ||||
| 	github.com/stretchr/testify v1.4.0 | ||||
| 	github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect | ||||
| 	github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect | ||||
| 	github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect | ||||
| 	github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect | ||||
| 	golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 | ||||
| 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d | ||||
| 	golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect | ||||
| 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 | ||||
| 	gopkg.in/ini.v1 v1.62.0 | ||||
| 	gopkg.in/square/go-jose.v2 v2.4.1 // indirect | ||||
| 	k8s.io/apimachinery v0.18.3 | ||||
| ) | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user