Compare commits
	
		
			993 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					595aba5a9b | ||
| 
						 | 
					679992db25 | ||
| 
						 | 
					5cfbb976f4 | ||
| 
						 | 
					b03f0ad1e6 | ||
| 
						 | 
					804f2910fd | ||
| 
						 | 
					a4189ba474 | ||
| 
						 | 
					e2d28d9929 | ||
| 
						 | 
					9ec84f8143 | ||
| 
						 | 
					7678938c08 | ||
| 
						 | 
					b2e3946800 | ||
| 
						 | 
					af0b7939a7 | ||
| 
						 | 
					2f66dc3e99 | ||
| 
						 | 
					649df8827c | ||
| 
						 | 
					da51adc276 | ||
| 
						 | 
					e5af37bc8c | ||
| 
						 | 
					8ab474cc97 | ||
| 
						 | 
					e8c8d5903a | ||
| 
						 | 
					a301046f3d | ||
| 
						 | 
					34ab6b0e74 | ||
| 
						 | 
					cf66ca10b4 | ||
| 
						 | 
					3fbe6b659e | ||
| 
						 | 
					6a71d71e58 | ||
| 
						 | 
					6ecc97c857 | ||
| 
						 | 
					ba492f07c3 | ||
| 
						 | 
					9d077b02cf | ||
| 
						 | 
					f4e4fbea62 | ||
| 
						 | 
					3e721d122b | ||
| 
						 | 
					1bc899ec12 | ||
| 
						 | 
					6f2571980c | ||
| 
						 | 
					8888610d83 | ||
| 
						 | 
					fa7c05c617 | ||
| 
						 | 
					218b354f82 | ||
| 
						 | 
					c652b8ef07 | ||
| 
						 | 
					5b8b145577 | ||
| 
						 | 
					fe5fb0326b | ||
| 
						 | 
					0711295b0a | ||
| 
						 | 
					4af85da0c2 | ||
| 
						 | 
					bd89eaba2f | ||
| 
						 | 
					a72259c604 | ||
| 
						 | 
					44eb513f05 | ||
| 
						 | 
					eb1e19a821 | ||
| 
						 | 
					6c658586f6 | ||
| 
						 | 
					888ed25314 | ||
| 
						 | 
					21240ed962 | ||
| 
						 | 
					6481870d03 | ||
| 
						 | 
					a7a4ba270d | ||
| 
						 | 
					915d9f4c09 | ||
| 
						 | 
					18a2af4703 | ||
| 
						 | 
					305e40fa8a | ||
| 
						 | 
					10f2620131 | ||
| 
						 | 
					4acae540c8 | ||
| 
						 | 
					11b13533a0 | ||
| 
						 | 
					100d556336 | ||
| 
						 | 
					452fe25cc6 | ||
| 
						 | 
					63efa6b776 | ||
| 
						 | 
					37c27169ac | ||
| 
						 | 
					ce677820c6 | ||
| 
						 | 
					1f88a7a0b8 | ||
| 
						 | 
					eeea7602d9 | ||
| 
						 | 
					bf635c0e90 | ||
| 
						 | 
					cd31359a27 | ||
| 
						 | 
					19739ed31a | ||
| 
						 | 
					10100c28d9 | ||
| 
						 | 
					88fcc079e8 | ||
| 
						 | 
					ddc1e163c4 | ||
| 
						 | 
					d20a6d3d75 | ||
| 
						 | 
					6194273615 | ||
| 
						 | 
					b2311e55e7 | ||
| 
						 | 
					07873d471f | ||
| 
						 | 
					2dab5d0bca | ||
| 
						 | 
					9ca2b586f8 | ||
| 
						 | 
					e59eacb8a2 | ||
| 
						 | 
					0db4fc07fb | ||
| 
						 | 
					70f4caac23 | ||
| 
						 | 
					293003fcdb | ||
| 
						 | 
					4bfc89d988 | ||
| 
						 | 
					22412851b4 | ||
| 
						 | 
					e9775bd70f | ||
| 
						 | 
					ff7b8b0b62 | ||
| 
						 | 
					491c1d7dc4 | ||
| 
						 | 
					ea568e8a4f | ||
| 
						 | 
					0fb6aeef58 | ||
| 
						 | 
					032f33fe5a | ||
| 
						 | 
					bbc8b438d5 | ||
| 
						 | 
					05b1ace21f | ||
| 
						 | 
					cbdd73b94f | ||
| 
						 | 
					bf06e3b107 | ||
| 
						 | 
					143750901e | ||
| 
						 | 
					71489d194c | ||
| 
						 | 
					85aa3df256 | ||
| 
						 | 
					f1a51eba18 | ||
| 
						 | 
					1d26ea440b | ||
| 
						 | 
					998e678a7f | ||
| 
						 | 
					0cee1877e3 | ||
| 
						 | 
					72a7fd948e | ||
| 
						 | 
					357c9b0dcb | ||
| 
						 | 
					14bd0716d0 | ||
| 
						 | 
					2f74f54f18 | ||
| 
						 | 
					a62a9431b1 | ||
| 
						 | 
					42745a3da2 | ||
| 
						 | 
					82f80a22be | ||
| 
						 | 
					f570dcb307 | ||
| 
						 | 
					997d406ec2 | ||
| 
						 | 
					87e60683ed | ||
| 
						 | 
					86b2e686a5 | ||
| 
						 | 
					09f39de74e | ||
| 
						 | 
					2a68c1152f | ||
| 
						 | 
					df5859b5f7 | ||
| 
						 | 
					3dd888a9ea | ||
| 
						 | 
					a51e221db3 | ||
| 
						 | 
					fe4e9b55f3 | ||
| 
						 | 
					3f11b6a082 | ||
| 
						 | 
					8a333c2ae0 | ||
| 
						 | 
					1fd6ba2738 | ||
| 
						 | 
					a98a9616f6 | ||
| 
						 | 
					95cd9ab900 | ||
| 
						 | 
					900454e58b | ||
| 
						 | 
					c7d4637382 | ||
| 
						 | 
					56925961df | ||
| 
						 | 
					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 | ||
| 
						 | 
					d39d745e43 | ||
| 
						 | 
					c10321ead6 | ||
| 
						 | 
					d7797cbd18 | ||
| 
						 | 
					0b9d823168 | ||
| 
						 | 
					deb750652f | ||
| 
						 | 
					14ba38a1b4 | ||
| 
						 | 
					f650d3f330 | 
							
								
								
									
										25
									
								
								.circleci/config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,25 @@
 | 
			
		||||
version: 2
 | 
			
		||||
jobs:
 | 
			
		||||
  go-version-latest:
 | 
			
		||||
    docker:
 | 
			
		||||
      - image: cimg/go:1.19-node
 | 
			
		||||
    resource_class: large
 | 
			
		||||
    steps:
 | 
			
		||||
      - checkout
 | 
			
		||||
      - run: make
 | 
			
		||||
      - run: make alltest
 | 
			
		||||
  go-version-last:
 | 
			
		||||
    docker:
 | 
			
		||||
      - image: cimg/go:1.18-node
 | 
			
		||||
    resource_class: large
 | 
			
		||||
    steps:
 | 
			
		||||
      - checkout
 | 
			
		||||
      - run: make
 | 
			
		||||
      - run: make alltest
 | 
			
		||||
 | 
			
		||||
workflows:
 | 
			
		||||
  version: 2
 | 
			
		||||
  build_and_test:
 | 
			
		||||
    jobs:
 | 
			
		||||
      - go-version-latest
 | 
			
		||||
      - go-version-last
 | 
			
		||||
							
								
								
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
# These are supported funding model platforms
 | 
			
		||||
 | 
			
		||||
github: [fatedier]
 | 
			
		||||
							
								
								
									
										77
									
								
								.github/ISSUE_TEMPLATE/bug_report.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,77 @@
 | 
			
		||||
name: Bug report
 | 
			
		||||
description: Report a bug to help us improve frp
 | 
			
		||||
 | 
			
		||||
body:
 | 
			
		||||
- type: markdown
 | 
			
		||||
  attributes:
 | 
			
		||||
    value: |
 | 
			
		||||
      Thanks for taking the time to fill out this bug report!
 | 
			
		||||
- type: textarea
 | 
			
		||||
  id: bug-description
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Bug Description
 | 
			
		||||
    description: Tell us what issues you ran into
 | 
			
		||||
    placeholder: Include information about what you tried, what you expected to happen, and what actually happened. The more details, the better!
 | 
			
		||||
  validations:
 | 
			
		||||
    required: true
 | 
			
		||||
- type: input
 | 
			
		||||
  id: frpc-version
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: frpc Version
 | 
			
		||||
    description: Include the output of `frpc -v`
 | 
			
		||||
  validations:
 | 
			
		||||
    required: true
 | 
			
		||||
- type: input
 | 
			
		||||
  id: frps-version
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: frps Version
 | 
			
		||||
    description: Include the output of `frps -v`
 | 
			
		||||
  validations:
 | 
			
		||||
    required: true
 | 
			
		||||
- type: input
 | 
			
		||||
  id: system-architecture
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: System Architecture
 | 
			
		||||
    description: Include which architecture you used, such as `linux/amd64`, `windows/amd64`
 | 
			
		||||
  validations:
 | 
			
		||||
    required: true
 | 
			
		||||
- type: textarea
 | 
			
		||||
  id: config
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Configurations
 | 
			
		||||
    description: Include what configurrations you used and ran into this problem
 | 
			
		||||
    placeholder: Pay attention to hiding the token and password in your output
 | 
			
		||||
  validations:
 | 
			
		||||
    required: true
 | 
			
		||||
- type: textarea
 | 
			
		||||
  id: log
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Logs
 | 
			
		||||
    description: Prefer you providing releated error logs here
 | 
			
		||||
    placeholder: Pay attention to hiding your personal informations
 | 
			
		||||
- type: textarea
 | 
			
		||||
  id: steps-to-reproduce
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Steps to reproduce
 | 
			
		||||
    description: How to reproduce it? It's important for us to find the bug
 | 
			
		||||
    value: |
 | 
			
		||||
      1. 
 | 
			
		||||
      2. 
 | 
			
		||||
      3. 
 | 
			
		||||
      ...
 | 
			
		||||
- type: checkboxes
 | 
			
		||||
  id: area
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Affected area
 | 
			
		||||
    options:
 | 
			
		||||
    - label: "Docs"
 | 
			
		||||
    - label: "Installation"
 | 
			
		||||
    - label: "Performance and Scalability"
 | 
			
		||||
    - label: "Security"
 | 
			
		||||
    - label: "User Experience"
 | 
			
		||||
    - label: "Test and Release"
 | 
			
		||||
    - label: "Developer Infrastructure"
 | 
			
		||||
    - label: "Client Plugin"
 | 
			
		||||
    - label: "Server Plugin"
 | 
			
		||||
    - label: "Extensions"
 | 
			
		||||
    - label: "Others"
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
blank_issues_enabled: false
 | 
			
		||||
							
								
								
									
										36
									
								
								.github/ISSUE_TEMPLATE/feature_request.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,36 @@
 | 
			
		||||
name: Feature Request
 | 
			
		||||
description: Suggest an idea to improve frp
 | 
			
		||||
title: "[Feature Request] "
 | 
			
		||||
 | 
			
		||||
body:
 | 
			
		||||
- type: markdown
 | 
			
		||||
  attributes:
 | 
			
		||||
    value: |
 | 
			
		||||
      This is only used to request new product features.
 | 
			
		||||
- type: textarea
 | 
			
		||||
  id: feature-request
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Describe the feature request
 | 
			
		||||
    description: Tell us what's you want and why it should be added in frp.
 | 
			
		||||
  validations:
 | 
			
		||||
    required: true
 | 
			
		||||
- type: textarea
 | 
			
		||||
  id: alternatives
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Describe alternatives you've considered
 | 
			
		||||
- type: checkboxes
 | 
			
		||||
  id: area
 | 
			
		||||
  attributes:
 | 
			
		||||
    label: Affected area
 | 
			
		||||
    options:
 | 
			
		||||
    - label: "Docs"
 | 
			
		||||
    - label: "Installation"
 | 
			
		||||
    - label: "Performance and Scalability"
 | 
			
		||||
    - label: "Security"
 | 
			
		||||
    - label: "User Experience"
 | 
			
		||||
    - label: "Test and Release"
 | 
			
		||||
    - label: "Developer Infrastructure"
 | 
			
		||||
    - label: "Client Plugin"
 | 
			
		||||
    - label: "Server Plugin"
 | 
			
		||||
    - label: "Extensions"
 | 
			
		||||
    - label: "Others"
 | 
			
		||||
							
								
								
									
										83
									
								
								.github/workflows/build-and-push-image.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,83 @@
 | 
			
		||||
name: Build Image and Publish to Dockerhub & GPR
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  release:
 | 
			
		||||
    types: [ created ]
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
    inputs:
 | 
			
		||||
      tag:
 | 
			
		||||
        description: 'Image tag'
 | 
			
		||||
        required: true
 | 
			
		||||
        default: 'test'
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  image:
 | 
			
		||||
    name: Build Image from Dockerfile and binaries
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      # environment
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: '0'
 | 
			
		||||
 | 
			
		||||
      - name: Set up QEMU
 | 
			
		||||
        uses: docker/setup-qemu-action@v2
 | 
			
		||||
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v2
 | 
			
		||||
 | 
			
		||||
      # 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
 | 
			
		||||
      - name: Login to DockerHub
 | 
			
		||||
        uses: docker/login-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          username: ${{ secrets.DOCKERHUB_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.DOCKERHUB_PASSWORD }}
 | 
			
		||||
 | 
			
		||||
      - name: Login to the GPR
 | 
			
		||||
        uses: docker/login-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          registry: ghcr.io
 | 
			
		||||
          username: ${{ github.repository_owner }}
 | 
			
		||||
          password: ${{ secrets.GPR_TOKEN }}
 | 
			
		||||
 | 
			
		||||
      # 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
 | 
			
		||||
 | 
			
		||||
      - name: Build and push frpc
 | 
			
		||||
        uses: docker/build-push-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          file: ./dockerfiles/Dockerfile-for-frpc
 | 
			
		||||
          platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
 | 
			
		||||
          push: true
 | 
			
		||||
          tags: |
 | 
			
		||||
            ${{ env.TAG_FRPC }}
 | 
			
		||||
            ${{ env.TAG_FRPC_GPR }}
 | 
			
		||||
 | 
			
		||||
      - name: Build and push frps
 | 
			
		||||
        uses: docker/build-push-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          context: .
 | 
			
		||||
          file: ./dockerfiles/Dockerfile-for-frps
 | 
			
		||||
          platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
 | 
			
		||||
          push: true
 | 
			
		||||
          tags: |
 | 
			
		||||
            ${{ env.TAG_FRPS }}
 | 
			
		||||
            ${{ env.TAG_FRPS_GPR }}
 | 
			
		||||
							
								
								
									
										41
									
								
								.github/workflows/golangci-lint.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,41 @@
 | 
			
		||||
name: golangci-lint
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
    - master
 | 
			
		||||
    - dev
 | 
			
		||||
  pull_request:
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
  # Optional: allow read access to pull request. Use with `only-new-issues` option.
 | 
			
		||||
  pull-requests: read
 | 
			
		||||
jobs:
 | 
			
		||||
  golangci:
 | 
			
		||||
    name: lint
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/setup-go@v3
 | 
			
		||||
        with:
 | 
			
		||||
          go-version: 1.19
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - name: golangci-lint
 | 
			
		||||
        uses: golangci/golangci-lint-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
 | 
			
		||||
          version: v1.49.0
 | 
			
		||||
 | 
			
		||||
          # Optional: golangci-lint command line arguments.
 | 
			
		||||
          # args: --issues-exit-code=0
 | 
			
		||||
 | 
			
		||||
          # Optional: show only new issues if it's a pull request. The default value is `false`.
 | 
			
		||||
          # only-new-issues: true
 | 
			
		||||
 | 
			
		||||
          # Optional: if set to true then the all caching functionality will be complete disabled,
 | 
			
		||||
          #           takes precedence over all other caching options.
 | 
			
		||||
          # skip-cache: true
 | 
			
		||||
 | 
			
		||||
          # Optional: if set to true then the action don't cache or restore ~/go/pkg.
 | 
			
		||||
          # skip-pkg-cache: true
 | 
			
		||||
 | 
			
		||||
          # Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
 | 
			
		||||
          # skip-build-cache: true
 | 
			
		||||
							
								
								
									
										30
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,30 @@
 | 
			
		||||
name: goreleaser
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  goreleaser:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
 | 
			
		||||
      - name: Set up Go
 | 
			
		||||
        uses: actions/setup-go@v3
 | 
			
		||||
        with:
 | 
			
		||||
          go-version: 1.19
 | 
			
		||||
          
 | 
			
		||||
      - name: Make All
 | 
			
		||||
        run: |
 | 
			
		||||
          ./package.sh
 | 
			
		||||
 | 
			
		||||
      - name: Run GoReleaser
 | 
			
		||||
        uses: goreleaser/goreleaser-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          version: latest
 | 
			
		||||
          args: release --rm-dist --release-notes=./Release.md
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GPR_TOKEN }}
 | 
			
		||||
							
								
								
									
										34
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,34 @@
 | 
			
		||||
name: "Close stale issues"
 | 
			
		||||
on:
 | 
			
		||||
  schedule:
 | 
			
		||||
  - cron: "20 0 * * *"
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
    inputs:
 | 
			
		||||
      debug-only:
 | 
			
		||||
        description: 'In debug mod'
 | 
			
		||||
        required: false
 | 
			
		||||
        default: 'false'
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  stale:
 | 
			
		||||
    permissions:
 | 
			
		||||
      issues: write  # for actions/stale to close stale issues
 | 
			
		||||
      pull-requests: write  # for actions/stale to close stale PRs
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/stale@v6
 | 
			
		||||
      with:
 | 
			
		||||
        repo-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
        stale-issue-message: 'Issues go stale after 30d of inactivity. Stale issues rot after an additional 7d of inactivity and eventually close.'
 | 
			
		||||
        stale-pr-message: "PRs go stale after 30d of inactivity. Stale PRs rot after an additional 7d 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: 30
 | 
			
		||||
        days-before-close: 7
 | 
			
		||||
        debug-only: ${{ github.event.inputs.debug-only }}
 | 
			
		||||
        exempt-all-pr-milestones: true
 | 
			
		||||
        exempt-all-pr-assignees: true
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -26,6 +26,12 @@ _testmain.go
 | 
			
		||||
# Self
 | 
			
		||||
bin/
 | 
			
		||||
packages/
 | 
			
		||||
release/
 | 
			
		||||
test/bin/
 | 
			
		||||
vendor/
 | 
			
		||||
dist/
 | 
			
		||||
.idea/
 | 
			
		||||
.vscode/
 | 
			
		||||
 | 
			
		||||
# Cache
 | 
			
		||||
*.swp
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										140
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,140 @@
 | 
			
		||||
service:
 | 
			
		||||
  # When updating this, also update the version stored in docker/build-tools/Dockerfile in the istio/tools repo.
 | 
			
		||||
  golangci-lint-version: 1.49.x # use the fixed version to not introduce new linters unexpectedly
 | 
			
		||||
  
 | 
			
		||||
run:
 | 
			
		||||
  concurrency: 4
 | 
			
		||||
  # timeout for analysis, e.g. 30s, 5m, default is 1m
 | 
			
		||||
  deadline: 20m
 | 
			
		||||
  build-tags:
 | 
			
		||||
  - integ
 | 
			
		||||
  - integfuzz
 | 
			
		||||
  # which dirs to skip: they won't be analyzed;
 | 
			
		||||
  # can use regexp here: generated.*, regexp is applied on full path;
 | 
			
		||||
  # default value is empty list, but next dirs are always skipped independently
 | 
			
		||||
  # from this option's value:
 | 
			
		||||
  #       vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
 | 
			
		||||
  skip-dirs:
 | 
			
		||||
    - genfiles$
 | 
			
		||||
    - vendor$
 | 
			
		||||
    - bin$
 | 
			
		||||
 | 
			
		||||
  # which files to skip: they will be analyzed, but issues from them
 | 
			
		||||
  # won't be reported. Default value is empty list, but there is
 | 
			
		||||
  # no need to include all autogenerated files, we confidently recognize
 | 
			
		||||
  # autogenerated files. If it's not please let us know.
 | 
			
		||||
  skip-files:
 | 
			
		||||
    - ".*\\.pb\\.go"
 | 
			
		||||
    - ".*\\.gen\\.go"
 | 
			
		||||
 | 
			
		||||
linters:
 | 
			
		||||
  disable-all: true
 | 
			
		||||
  enable:
 | 
			
		||||
  - unused
 | 
			
		||||
  - errcheck
 | 
			
		||||
  - exportloopref
 | 
			
		||||
  - gocritic
 | 
			
		||||
  - gofumpt
 | 
			
		||||
  - goimports
 | 
			
		||||
  - revive
 | 
			
		||||
  - gosimple
 | 
			
		||||
  - govet
 | 
			
		||||
  - ineffassign
 | 
			
		||||
  - lll
 | 
			
		||||
  - misspell
 | 
			
		||||
  - staticcheck
 | 
			
		||||
  - stylecheck
 | 
			
		||||
  - typecheck
 | 
			
		||||
  - unconvert
 | 
			
		||||
  - unparam
 | 
			
		||||
  - gci
 | 
			
		||||
  - gosec
 | 
			
		||||
  - asciicheck
 | 
			
		||||
  - prealloc
 | 
			
		||||
  - predeclared
 | 
			
		||||
  - makezero
 | 
			
		||||
  fast: false
 | 
			
		||||
 | 
			
		||||
linters-settings:
 | 
			
		||||
  errcheck:
 | 
			
		||||
    # report about not checking of errors in type assetions: `a := b.(MyStruct)`;
 | 
			
		||||
    # default is false: such cases aren't reported by default.
 | 
			
		||||
    check-type-assertions: false
 | 
			
		||||
 | 
			
		||||
    # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
 | 
			
		||||
    # default is false: such cases aren't reported by default.
 | 
			
		||||
    check-blank: false
 | 
			
		||||
  govet:
 | 
			
		||||
    # report about shadowed variables
 | 
			
		||||
    check-shadowing: false
 | 
			
		||||
  maligned:
 | 
			
		||||
    # print struct with more effective memory layout or not, false by default
 | 
			
		||||
    suggest-new: true
 | 
			
		||||
  misspell:
 | 
			
		||||
    # Correct spellings using locale preferences for US or UK.
 | 
			
		||||
    # Default is to use a neutral variety of English.
 | 
			
		||||
    # Setting locale to US will correct the British spelling of 'colour' to 'color'.
 | 
			
		||||
    locale: US
 | 
			
		||||
    ignore-words:
 | 
			
		||||
    - cancelled
 | 
			
		||||
    - marshalled
 | 
			
		||||
  lll:
 | 
			
		||||
    # max line length, lines longer will be reported. Default is 120.
 | 
			
		||||
    # '\t' is counted as 1 character by default, and can be changed with the tab-width option
 | 
			
		||||
    line-length: 160
 | 
			
		||||
    # tab width in spaces. Default to 1.
 | 
			
		||||
    tab-width: 1
 | 
			
		||||
  gocritic:
 | 
			
		||||
    disabled-checks:
 | 
			
		||||
    - exitAfterDefer
 | 
			
		||||
  unused:
 | 
			
		||||
    check-exported: false
 | 
			
		||||
  unparam:
 | 
			
		||||
    # Inspect exported functions, default is false. Set to true if no external program/library imports your code.
 | 
			
		||||
    # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
 | 
			
		||||
    # if it's called for subdir of a project it can't find external interfaces. All text editor integrations
 | 
			
		||||
    # with golangci-lint call it on a directory with the changed file.
 | 
			
		||||
    check-exported: false
 | 
			
		||||
  gci:
 | 
			
		||||
    sections:
 | 
			
		||||
    - standard
 | 
			
		||||
    - default
 | 
			
		||||
    - prefix(github.com/fatedier/frp/)
 | 
			
		||||
  gosec:
 | 
			
		||||
    severity: "low"
 | 
			
		||||
    confidence: "low"
 | 
			
		||||
    excludes:
 | 
			
		||||
    - G102
 | 
			
		||||
    - G112
 | 
			
		||||
    - G306
 | 
			
		||||
    - G401
 | 
			
		||||
    - G402
 | 
			
		||||
    - G404
 | 
			
		||||
    - G501
 | 
			
		||||
 | 
			
		||||
issues:
 | 
			
		||||
  # List of regexps of issue texts to exclude, empty list by default.
 | 
			
		||||
  # But independently from this option we use default exclude patterns,
 | 
			
		||||
  # it can be disabled by `exclude-use-default: false`. To list all
 | 
			
		||||
  # excluded by default patterns execute `golangci-lint run --help`
 | 
			
		||||
  # exclude:
 | 
			
		||||
  #  - composite literal uses unkeyed fields
 | 
			
		||||
 | 
			
		||||
  exclude-rules:
 | 
			
		||||
    # Exclude some linters from running on test files.
 | 
			
		||||
    - path: _test\.go$|^tests/|^samples/
 | 
			
		||||
      linters:
 | 
			
		||||
        - errcheck
 | 
			
		||||
        - maligned
 | 
			
		||||
 | 
			
		||||
  # Independently from option `exclude` we use default exclude patterns,
 | 
			
		||||
  # it can be disabled by this option. To list all
 | 
			
		||||
  # excluded by default patterns execute `golangci-lint run --help`.
 | 
			
		||||
  # Default value for this option is true.
 | 
			
		||||
  exclude-use-default: true
 | 
			
		||||
 | 
			
		||||
  # Maximum issues count per one linter. Set to 0 to disable. Default is 50.
 | 
			
		||||
  max-per-linter: 0
 | 
			
		||||
 | 
			
		||||
  # Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
 | 
			
		||||
  max-same-issues: 0
 | 
			
		||||
							
								
								
									
										22
									
								
								.goreleaser.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,22 @@
 | 
			
		||||
builds:
 | 
			
		||||
  - skip: true
 | 
			
		||||
checksum:
 | 
			
		||||
  name_template: '{{ .ProjectName }}_sha256_checksums.txt'
 | 
			
		||||
  algorithm: sha256
 | 
			
		||||
  extra_files:
 | 
			
		||||
  - glob: ./release/packages/*
 | 
			
		||||
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
									
									
									
									
									
								
							
							
						
						@@ -1,12 +0,0 @@
 | 
			
		||||
sudo: false
 | 
			
		||||
language: go
 | 
			
		||||
 | 
			
		||||
go:
 | 
			
		||||
    - 1.4.2
 | 
			
		||||
    - 1.5.3
 | 
			
		||||
 | 
			
		||||
install:
 | 
			
		||||
    - make
 | 
			
		||||
 | 
			
		||||
script:
 | 
			
		||||
    - make test
 | 
			
		||||
							
								
								
									
										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
									
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -1,2 +0,0 @@
 | 
			
		||||
/pkg
 | 
			
		||||
/bin
 | 
			
		||||
							
								
								
									
										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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						
							
								
								
									
										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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -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
									
									
								
							
							
						
						@@ -1,2 +0,0 @@
 | 
			
		||||
[default]
 | 
			
		||||
stuff = things
 | 
			
		||||
							
								
								
									
										47
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						@@ -1,22 +1,47 @@
 | 
			
		||||
export PATH := $(GOPATH)/bin:$(PATH)
 | 
			
		||||
export OLDGOPATH := $(GOPATH)
 | 
			
		||||
export GOPATH := $(shell pwd):$(GOPATH)
 | 
			
		||||
export GO111MODULE=on
 | 
			
		||||
LDFLAGS := -s -w
 | 
			
		||||
 | 
			
		||||
all: build
 | 
			
		||||
all: fmt build
 | 
			
		||||
 | 
			
		||||
build: godep fmt frps frpc
 | 
			
		||||
build: frps frpc
 | 
			
		||||
 | 
			
		||||
godep:
 | 
			
		||||
	GOPATH=$(OLDGOPATH) 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
 | 
			
		||||
 | 
			
		||||
fmt:
 | 
			
		||||
	godep go fmt ./...
 | 
			
		||||
	go fmt ./...
 | 
			
		||||
 | 
			
		||||
vet:
 | 
			
		||||
	go vet ./...
 | 
			
		||||
 | 
			
		||||
frps:
 | 
			
		||||
	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:
 | 
			
		||||
	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:
 | 
			
		||||
	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/...
 | 
			
		||||
 | 
			
		||||
e2e:
 | 
			
		||||
	./hack/run-e2e.sh
 | 
			
		||||
 | 
			
		||||
e2e-trace:
 | 
			
		||||
	DEBUG=true LOG_LEVEL=trace ./hack/run-e2e.sh
 | 
			
		||||
 | 
			
		||||
alltest: vet gotest e2e
 | 
			
		||||
	
 | 
			
		||||
clean:
 | 
			
		||||
	rm -f ./bin/frpc
 | 
			
		||||
	rm -f ./bin/frps
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,25 @@
 | 
			
		||||
export PATH := $(GOPATH)/bin:$(PATH)
 | 
			
		||||
export OLDGOPATH := $(GOPATH)
 | 
			
		||||
export GOPATH := $(shell pwd)/Godeps/_workspace:$(shell pwd):$(GOPATH)
 | 
			
		||||
export OS_TARGETS=linux windows
 | 
			
		||||
export ARCH_TARGETS=386 amd64
 | 
			
		||||
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 linux:riscv64
 | 
			
		||||
 | 
			
		||||
all: build
 | 
			
		||||
 | 
			
		||||
build: godep app 
 | 
			
		||||
 | 
			
		||||
godep:
 | 
			
		||||
	GOPATH=$(OLDGOPATH) go get github.com/mitchellh/gox
 | 
			
		||||
build: app
 | 
			
		||||
 | 
			
		||||
app:
 | 
			
		||||
	gox -os "$(OS_TARGETS)" -arch="$(ARCH_TARGETS)" ./...
 | 
			
		||||
	@$(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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										87
									
								
								README_zh.md
									
									
									
									
									
								
							
							
						
						@@ -1,45 +1,86 @@
 | 
			
		||||
# 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 是一个高性能的反向代理应用,可以帮助你轻松的进行内网穿透,对外网提供服务,对于 http 服务还支持虚拟主机功能,访问80端口,可以根据域名路由到后端不同的 http 服务。
 | 
			
		||||
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。
 | 
			
		||||
 | 
			
		||||
## frp 的作用?
 | 
			
		||||
<h3 align="center">Gold Sponsors</h3>
 | 
			
		||||
<!--gold sponsors start-->
 | 
			
		||||
 | 
			
		||||
* 利用处于内网或防火墙后的机器,对外网环境提供 http 服务。
 | 
			
		||||
* 对于 http 服务支持基于域名的虚拟主机,支持自定义域名绑定,使多个域名可以共用一个80端口。
 | 
			
		||||
* 利用处于内网或防火墙后的机器,对外网环境提供 tcp 服务,例如在家里通过 ssh 访问公司局域网内的主机。
 | 
			
		||||
* 可查看通过代理的所有 http 请求和响应的详细信息。(待开发)
 | 
			
		||||
<p align="center">
 | 
			
		||||
  <a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
 | 
			
		||||
    <img width="300px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
 | 
			
		||||
  </a>
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
<!--gold sponsors end-->
 | 
			
		||||
 | 
			
		||||
<h3 align="center">Silver Sponsors</h3>
 | 
			
		||||
 | 
			
		||||
* Sakura Frp - 欢迎点击 "加入我们"
 | 
			
		||||
 | 
			
		||||
## 为什么使用 frp ?
 | 
			
		||||
 | 
			
		||||
通过在具有公网 IP 的节点上部署 frp 服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:
 | 
			
		||||
 | 
			
		||||
* 客户端服务端通信支持 TCP、KCP 以及 Websocket 等多种协议。
 | 
			
		||||
* 采用 TCP 连接流式复用,在单个连接间承载更多请求,节省连接建立时间。
 | 
			
		||||
* 代理组间的负载均衡。
 | 
			
		||||
* 端口复用,多个服务通过同一个服务端端口暴露。
 | 
			
		||||
* 多个原生支持的客户端插件(静态文件查看,HTTP、SOCK5 代理等),便于独立使用 frp 客户端完成某些工作。
 | 
			
		||||
* 高度扩展性的服务端插件系统,方便结合自身需求进行功能扩展。
 | 
			
		||||
* 服务端和客户端 UI 页面。
 | 
			
		||||
 | 
			
		||||
## 开发状态
 | 
			
		||||
 | 
			
		||||
frp 目前正在前期开发阶段,master分支用于发布稳定版本,dev分支用于开发,您可以尝试下载最新的 release 版本进行测试。
 | 
			
		||||
frp 目前已被很多公司广泛用于测试、生产环境。
 | 
			
		||||
 | 
			
		||||
**在 1.0 版本以前,交互协议都可能会被改变,不能保证向后兼容。**
 | 
			
		||||
master 分支用于发布稳定版本,dev 分支用于开发,您可以尝试下载最新的 release 版本进行测试。
 | 
			
		||||
 | 
			
		||||
## 快速开始
 | 
			
		||||
我们正在进行 v2 大版本的开发,将会尝试在各个方面进行重构和升级,且不会与 v1 版本进行兼容,预计会持续一段时间。
 | 
			
		||||
 | 
			
		||||
[使用文档](/doc/quick_start_zh.md)
 | 
			
		||||
现在的 v0 版本将会在合适的时间切换为 v1 版本并且保证兼容性,后续只做 bug 修复和优化,不再进行大的功能性更新。
 | 
			
		||||
 | 
			
		||||
[tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发)
 | 
			
		||||
## 文档
 | 
			
		||||
 | 
			
		||||
[http 端口转发,自定义域名绑定](/doc/quick_start_zh.md#http-端口转发自定义域名绑定)
 | 
			
		||||
完整文档已经迁移至 [https://gofrp.org](https://gofrp.org/docs)。
 | 
			
		||||
 | 
			
		||||
## 架构
 | 
			
		||||
## 为 frp 做贡献
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进步贡献力量。
 | 
			
		||||
 | 
			
		||||
## 贡献代码
 | 
			
		||||
* 在使用过程中出现任何问题,可以通过 [issues](https://github.com/fatedier/frp/issues) 来反馈。
 | 
			
		||||
* Bug 的修复可以直接提交 Pull Request 到 dev 分支。
 | 
			
		||||
* 如果是增加新的功能特性,请先创建一个 issue 并做简单描述以及大致的实现方法,提议被采纳后,就可以创建一个实现新特性的 Pull Request。
 | 
			
		||||
* 欢迎对说明文档做出改善,帮助更多的人使用 frp,特别是英文文档。
 | 
			
		||||
* 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。
 | 
			
		||||
* 如果你有任何其他方面的问题或合作,欢迎发送邮件至 fatedier@gmail.com 。
 | 
			
		||||
 | 
			
		||||
如果您对这个项目感兴趣,我们非常欢迎您参与其中!
 | 
			
		||||
**提醒:和项目相关的问题最好在 [issues](https://github.com/fatedier/frp/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。**
 | 
			
		||||
 | 
			
		||||
* 如果您需要提交问题,可以通过 [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)
 | 
			
		||||
### GitHub Sponsors
 | 
			
		||||
 | 
			
		||||
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
 | 
			
		||||
 | 
			
		||||
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
 | 
			
		||||
 | 
			
		||||
### 知识星球
 | 
			
		||||
 | 
			
		||||
如果您想学习 frp 相关的知识和技术,或者寻求任何帮助及咨询,都可以通过微信扫描下方的二维码付费加入知识星球的官方社群:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
### 支付宝扫码捐赠
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
### 微信支付捐赠
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								Release.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,4 @@
 | 
			
		||||
### Fix
 | 
			
		||||
 | 
			
		||||
* Server Plugin send incorrect op name for NewWorkConn.
 | 
			
		||||
* QUIC stream leak.
 | 
			
		||||
							
								
								
									
										48
									
								
								assets/assets.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,48 @@
 | 
			
		||||
// 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
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// read-only filesystem created by "embed" for embedded files
 | 
			
		||||
	content fs.FS
 | 
			
		||||
 | 
			
		||||
	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) {
 | 
			
		||||
	prefixPath = path
 | 
			
		||||
	if prefixPath != "" {
 | 
			
		||||
		FileSystem = http.Dir(prefixPath)
 | 
			
		||||
	} else {
 | 
			
		||||
		FileSystem = http.FS(content)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Register(fileSystem fs.FS) {
 | 
			
		||||
	subFs, err := fs.Sub(fileSystem, "static")
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		content = subFs
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								assets/frpc/embed.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,14 @@
 | 
			
		||||
package frpc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"embed"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/frp/assets"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//go:embed static/*
 | 
			
		||||
var content embed.FS
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	assets.Register(content)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								assets/frpc/static/535877f50039c0cb49a6196a5b7517cd.woff
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								assets/frpc/static/732389ded34cb9c52dd88271f1345af9.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								assets/frpc/static/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 9.4 KiB  | 
							
								
								
									
										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?5d5774096cf5c1b4d5af"></script><script type="text/javascript" src="vendor.js?dc42700731a508d39009"></script></body> </html> 
 | 
			
		||||
							
								
								
									
										1
									
								
								assets/frpc/static/manifest.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"dc42700731a508d39009"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
 | 
			
		||||
							
								
								
									
										1
									
								
								assets/frpc/static/vendor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										14
									
								
								assets/frps/embed.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,14 @@
 | 
			
		||||
package frpc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"embed"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/frp/assets"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//go:embed static/*
 | 
			
		||||
var content embed.FS
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	assets.Register(content)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								assets/frps/static/535877f50039c0cb49a6196a5b7517cd.woff
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								assets/frps/static/732389ded34cb9c52dd88271f1345af9.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								assets/frps/static/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 9.4 KiB  | 
							
								
								
									
										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?5d154ba4c6b342d8c0c3"></script><script type="text/javascript" src="vendor.js?ddbd1f69fb6e67be4b78"></script></body> </html> 
 | 
			
		||||
							
								
								
									
										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:"ddbd1f69fb6e67be4b78"}[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
									
								
							
							
						
						
							
								
								
									
										84
									
								
								client/admin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,84 @@
 | 
			
		||||
// 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"
 | 
			
		||||
	"net/http/pprof"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/frp/assets"
 | 
			
		||||
	frpNet "github.com/fatedier/frp/pkg/util/net"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	httpServerReadTimeout  = 60 * time.Second
 | 
			
		||||
	httpServerWriteTimeout = 60 * time.Second
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (svr *Service) RunAdminServer(address string) (err error) {
 | 
			
		||||
	// url router
 | 
			
		||||
	router := mux.NewRouter()
 | 
			
		||||
 | 
			
		||||
	router.HandleFunc("/healthz", svr.healthz)
 | 
			
		||||
 | 
			
		||||
	// debug
 | 
			
		||||
	if svr.cfg.PprofEnable {
 | 
			
		||||
		router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
 | 
			
		||||
		router.HandleFunc("/debug/pprof/profile", pprof.Profile)
 | 
			
		||||
		router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
 | 
			
		||||
		router.HandleFunc("/debug/pprof/trace", pprof.Trace)
 | 
			
		||||
		router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	subRouter := router.NewRoute().Subrouter()
 | 
			
		||||
	user, passwd := svr.cfg.AdminUser, svr.cfg.AdminPwd
 | 
			
		||||
	subRouter.Use(frpNet.NewHTTPAuthMiddleware(user, passwd).Middleware)
 | 
			
		||||
 | 
			
		||||
	// api, see admin_api.go
 | 
			
		||||
	subRouter.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
 | 
			
		||||
	subRouter.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
 | 
			
		||||
	subRouter.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
 | 
			
		||||
	subRouter.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
 | 
			
		||||
 | 
			
		||||
	// view
 | 
			
		||||
	subRouter.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
 | 
			
		||||
	subRouter.PathPrefix("/static/").Handler(frpNet.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
 | 
			
		||||
	subRouter.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 func() {
 | 
			
		||||
		_ = server.Serve(ln)
 | 
			
		||||
	}()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										323
									
								
								client/admin_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,323 @@
 | 
			
		||||
// Copyright 2017 fatedier, fatedier@gmail.com
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package client
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// /healthz
 | 
			
		||||
func (svr *Service) healthz(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	w.WriteHeader(200)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 | 
			
		||||
		}
 | 
			
		||||
		psr.Plugin = cfg.Plugin
 | 
			
		||||
		if status.Err != "" {
 | 
			
		||||
			psr.RemoteAddr = net.JoinHostPort(serverAddr, strconv.Itoa(cfg.RemotePort))
 | 
			
		||||
		} else {
 | 
			
		||||
			psr.RemoteAddr = serverAddr + status.RemoteAddr
 | 
			
		||||
		}
 | 
			
		||||
	case *config.UDPProxyConf:
 | 
			
		||||
		if cfg.LocalPort != 0 {
 | 
			
		||||
			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 | 
			
		||||
		}
 | 
			
		||||
		if status.Err != "" {
 | 
			
		||||
			psr.RemoteAddr = net.JoinHostPort(serverAddr, strconv.Itoa(cfg.RemotePort))
 | 
			
		||||
		} else {
 | 
			
		||||
			psr.RemoteAddr = serverAddr + status.RemoteAddr
 | 
			
		||||
		}
 | 
			
		||||
	case *config.HTTPProxyConf:
 | 
			
		||||
		if cfg.LocalPort != 0 {
 | 
			
		||||
			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 | 
			
		||||
		}
 | 
			
		||||
		psr.Plugin = cfg.Plugin
 | 
			
		||||
		psr.RemoteAddr = status.RemoteAddr
 | 
			
		||||
	case *config.HTTPSProxyConf:
 | 
			
		||||
		if cfg.LocalPort != 0 {
 | 
			
		||||
			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 | 
			
		||||
		}
 | 
			
		||||
		psr.Plugin = cfg.Plugin
 | 
			
		||||
		psr.RemoteAddr = status.RemoteAddr
 | 
			
		||||
	case *config.STCPProxyConf:
 | 
			
		||||
		if cfg.LocalPort != 0 {
 | 
			
		||||
			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 | 
			
		||||
		}
 | 
			
		||||
		psr.Plugin = cfg.Plugin
 | 
			
		||||
	case *config.XTCPProxyConf:
 | 
			
		||||
		if cfg.LocalPort != 0 {
 | 
			
		||||
			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(cfg.LocalPort))
 | 
			
		||||
		}
 | 
			
		||||
		psr.Plugin = cfg.Plugin
 | 
			
		||||
	case *config.SUDPProxyConf:
 | 
			
		||||
		if cfg.LocalPort != 0 {
 | 
			
		||||
			psr.LocalAddr = net.JoinHostPort(cfg.LocalIP, strconv.Itoa(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))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 := io.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 := os.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 = os.WriteFile(svr.cfgFile, []byte(content), 0o644)
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										349
									
								
								client/control.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,349 @@
 | 
			
		||||
// 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"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net"
 | 
			
		||||
	"runtime/debug"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/golib/control/shutdown"
 | 
			
		||||
	"github.com/fatedier/golib/crypto"
 | 
			
		||||
 | 
			
		||||
	"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/util/xlog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
	cm *ConnectionManager
 | 
			
		||||
 | 
			
		||||
	// 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
 | 
			
		||||
 | 
			
		||||
	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, cm *ConnectionManager,
 | 
			
		||||
	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,
 | 
			
		||||
		cm:                 cm,
 | 
			
		||||
		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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
 | 
			
		||||
	xl := ctl.xl
 | 
			
		||||
	workConn, err := ctl.connectServer()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		xl.Warn("start new connection to server error: %v", err)
 | 
			
		||||
		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.Trace("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 {
 | 
			
		||||
	return ctl.GracefulClose(0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ctl *Control) GracefulClose(d time.Duration) error {
 | 
			
		||||
	ctl.pm.Close()
 | 
			
		||||
	ctl.vm.Close()
 | 
			
		||||
 | 
			
		||||
	time.Sleep(d)
 | 
			
		||||
 | 
			
		||||
	ctl.conn.Close()
 | 
			
		||||
	ctl.cm.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) {
 | 
			
		||||
	return ctl.cm.Connect()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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()
 | 
			
		||||
 | 
			
		||||
	var hbSendCh <-chan time.Time
 | 
			
		||||
	// TODO(fatedier): disable heartbeat if TCPMux is enabled.
 | 
			
		||||
	// Just keep it here to keep compatible with old version frps.
 | 
			
		||||
	if ctl.clientCfg.HeartbeatInterval > 0 {
 | 
			
		||||
		hbSend := time.NewTicker(time.Duration(ctl.clientCfg.HeartbeatInterval) * time.Second)
 | 
			
		||||
		defer hbSend.Stop()
 | 
			
		||||
		hbSendCh = hbSend.C
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var hbCheckCh <-chan time.Time
 | 
			
		||||
	// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
 | 
			
		||||
	if ctl.clientCfg.HeartbeatInterval > 0 && ctl.clientCfg.HeartbeatTimeout > 0 && !ctl.clientCfg.TCPMux {
 | 
			
		||||
		hbCheck := time.NewTicker(time.Second)
 | 
			
		||||
		defer hbCheck.Stop()
 | 
			
		||||
		hbCheckCh = hbCheck.C
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctl.lastPong = time.Now()
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-hbSendCh:
 | 
			
		||||
			// 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 <-hbCheckCh:
 | 
			
		||||
			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()
 | 
			
		||||
 | 
			
		||||
	<-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)
 | 
			
		||||
	ctl.cm.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								client/event/event.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,19 @@
 | 
			
		||||
package event
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/frp/pkg/msg"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var ErrPayloadType = errors.New("error payload type")
 | 
			
		||||
 | 
			
		||||
type Handler func(payload interface{}) error
 | 
			
		||||
 | 
			
		||||
type StartProxyPayload struct {
 | 
			
		||||
	NewProxyMsg *msg.NewProxy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CloseProxyPayload struct {
 | 
			
		||||
	CloseProxyMsg *msg.CloseProxy
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										168
									
								
								client/health/health.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,168 @@
 | 
			
		||||
// 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"
 | 
			
		||||
	"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.NewRequestWithContext(ctx, "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(io.Discard, resp.Body)
 | 
			
		||||
 | 
			
		||||
	if resp.StatusCode/100 != 2 {
 | 
			
		||||
		return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										820
									
								
								client/proxy/proxy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,820 @@
 | 
			
		||||
// 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"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/golib/errors"
 | 
			
		||||
	frpIo "github.com/fatedier/golib/io"
 | 
			
		||||
	libdial "github.com/fatedier/golib/net/dial"
 | 
			
		||||
	"github.com/fatedier/golib/pool"
 | 
			
		||||
	fmux "github.com/hashicorp/yamux"
 | 
			
		||||
	pp "github.com/pires/go-proxyproto"
 | 
			
		||||
	"golang.org/x/time/rate"
 | 
			
		||||
 | 
			
		||||
	"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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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",
 | 
			
		||||
		net.JoinHostPort(pxy.clientCfg.ServerAddr, strconv.Itoa(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
 | 
			
		||||
	host, portStr, err := net.SplitHostPort(natHoleRespMsg.VisitorAddr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		xl.Error("get NatHoleResp visitor address [%s] error: %v", natHoleRespMsg.VisitorAddr, err)
 | 
			
		||||
	}
 | 
			
		||||
	laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
 | 
			
		||||
 | 
			
		||||
	port, err := strconv.ParseInt(portStr, 10, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	_ = pxy.sendDetectMsg(host, int(port), laddr, []byte(natHoleRespMsg.Sid))
 | 
			
		||||
	xl.Trace("send all detect msg done")
 | 
			
		||||
 | 
			
		||||
	if err := msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{}); err != nil {
 | 
			
		||||
		xl.Error("write message error: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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 address 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)
 | 
			
		||||
 | 
			
		||||
	if _, err := lConn.WriteToUDP(sidBuf[:n], uAddr); err != nil {
 | 
			
		||||
		xl.Error("write uaddr error: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 = io.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", net.JoinHostPort(addr, strconv.Itoa(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)
 | 
			
		||||
 | 
			
		||||
	if _, err := tConn.Write(content); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return tConn.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(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(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.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", net.JoinHostPort(pxy.cfg.LocalIP, strconv.Itoa(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(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(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"
 | 
			
		||||
			}
 | 
			
		||||
			srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort))))
 | 
			
		||||
			dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort))))
 | 
			
		||||
			h := &pp.Header{
 | 
			
		||||
				Command:         pp.PROXY,
 | 
			
		||||
				SourceAddr:      srcAddr,
 | 
			
		||||
				DestinationAddr: dstAddr,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			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 := libdial.Dial(
 | 
			
		||||
		net.JoinHostPort(localInfo.LocalIP, strconv.Itoa(localInfo.LocalPort)),
 | 
			
		||||
		libdial.WithTimeout(10*time.Second),
 | 
			
		||||
	)
 | 
			
		||||
	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 {
 | 
			
		||||
		if _, err := localConn.Write(extraInfo); err != nil {
 | 
			
		||||
			workConn.Close()
 | 
			
		||||
			xl.Error("write extraInfo to local conn error: %v", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	frpIo.Join(localConn, remote)
 | 
			
		||||
	xl.Debug("join connections closed")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										144
									
								
								client/proxy/proxy_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,144 @@
 | 
			
		||||
package proxy
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/golib/errors"
 | 
			
		||||
 | 
			
		||||
	"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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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(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
									
								
							
							
						
						@@ -0,0 +1,250 @@
 | 
			
		||||
package proxy
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/golib/errors"
 | 
			
		||||
 | 
			
		||||
	"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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ProxyPhaseNew         = "new"
 | 
			
		||||
	ProxyPhaseWaitStart   = "wait start"
 | 
			
		||||
	ProxyPhaseStartErr    = "start error"
 | 
			
		||||
	ProxyPhaseRunning     = "running"
 | 
			
		||||
	ProxyPhaseCheckFailed = "check failed"
 | 
			
		||||
	ProxyPhaseClosed      = "closed"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	statusCheckInterval = 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.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.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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										488
									
								
								client/service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,488 @@
 | 
			
		||||
// 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"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"net"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/golib/crypto"
 | 
			
		||||
	libdial "github.com/fatedier/golib/net/dial"
 | 
			
		||||
	fmux "github.com/hashicorp/yamux"
 | 
			
		||||
	quic "github.com/lucas-clemente/quic-go"
 | 
			
		||||
 | 
			
		||||
	"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/util"
 | 
			
		||||
	"github.com/fatedier/frp/pkg/util/version"
 | 
			
		||||
	"github.com/fatedier/frp/pkg/util/xlog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	crypto.DefaultSalt = "frp"
 | 
			
		||||
	rand.Seed(time.Now().UnixNano())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
 | 
			
		||||
	// set custom DNSServer
 | 
			
		||||
	if svr.cfg.DNSServer != "" {
 | 
			
		||||
		dnsAddr := svr.cfg.DNSServer
 | 
			
		||||
		if !strings.Contains(dnsAddr, ":") {
 | 
			
		||||
			dnsAddr += ":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", dnsAddr)
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// login to frps
 | 
			
		||||
	for {
 | 
			
		||||
		conn, cm, 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
 | 
			
		||||
			}
 | 
			
		||||
			util.RandomSleep(10*time.Second, 0.9, 1.1)
 | 
			
		||||
		} else {
 | 
			
		||||
			// login success
 | 
			
		||||
			ctl := NewControl(svr.ctx, svr.runID, conn, cm, 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
 | 
			
		||||
		assets.Load(svr.cfg.AssetsDir)
 | 
			
		||||
 | 
			
		||||
		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 {
 | 
			
		||||
			util.RandomSleep(reconnectDelay, 0.9, 1.1)
 | 
			
		||||
			xl.Info("wait %v to reconnect", reconnectDelay)
 | 
			
		||||
			reconnectDelay *= 2
 | 
			
		||||
		} else {
 | 
			
		||||
			util.RandomSleep(time.Second, 0, 0.5)
 | 
			
		||||
		}
 | 
			
		||||
		reconnectCounts++
 | 
			
		||||
 | 
			
		||||
		now := time.Now()
 | 
			
		||||
		if now.After(cutoffTime) {
 | 
			
		||||
			// reset
 | 
			
		||||
			cutoffTime = now.Add(time.Minute)
 | 
			
		||||
			reconnectDelay = time.Second
 | 
			
		||||
			reconnectCounts = 1
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for {
 | 
			
		||||
			if atomic.LoadUint32(&svr.exit) != 0 {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			xl.Info("try to reconnect to server...")
 | 
			
		||||
			conn, cm, err := svr.login()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				xl.Warn("reconnect to server error: %v, wait %v for another retry", err, delayTime)
 | 
			
		||||
				util.RandomSleep(delayTime, 0.9, 1.1)
 | 
			
		||||
 | 
			
		||||
				delayTime *= 2
 | 
			
		||||
				if delayTime > maxDelayTime {
 | 
			
		||||
					delayTime = maxDelayTime
 | 
			
		||||
				}
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			// reconnect success, init delayTime
 | 
			
		||||
			delayTime = time.Second
 | 
			
		||||
 | 
			
		||||
			ctl := NewControl(svr.ctx, svr.runID, conn, cm, 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, cm *ConnectionManager, err error) {
 | 
			
		||||
	xl := xlog.FromContextSafe(svr.ctx)
 | 
			
		||||
	cm = NewConnectionManager(svr.ctx, &svr.cfg)
 | 
			
		||||
 | 
			
		||||
	if err = cm.OpenConnection(); err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			cm.Close()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	conn, err = cm.Connect()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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()
 | 
			
		||||
 | 
			
		||||
	svr.ctlMu.RLock()
 | 
			
		||||
	ctl := svr.ctl
 | 
			
		||||
	svr.ctlMu.RUnlock()
 | 
			
		||||
 | 
			
		||||
	if ctl != nil {
 | 
			
		||||
		return svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (svr *Service) Close() {
 | 
			
		||||
	svr.GracefulClose(time.Duration(0))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (svr *Service) GracefulClose(d time.Duration) {
 | 
			
		||||
	atomic.StoreUint32(&svr.exit, 1)
 | 
			
		||||
 | 
			
		||||
	svr.ctlMu.RLock()
 | 
			
		||||
	if svr.ctl != nil {
 | 
			
		||||
		svr.ctl.GracefulClose(d)
 | 
			
		||||
	}
 | 
			
		||||
	svr.ctlMu.RUnlock()
 | 
			
		||||
 | 
			
		||||
	svr.cancel()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ConnectionManager struct {
 | 
			
		||||
	ctx context.Context
 | 
			
		||||
	cfg *config.ClientCommonConf
 | 
			
		||||
 | 
			
		||||
	muxSession *fmux.Session
 | 
			
		||||
	quicConn   quic.Connection
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewConnectionManager(ctx context.Context, cfg *config.ClientCommonConf) *ConnectionManager {
 | 
			
		||||
	return &ConnectionManager{
 | 
			
		||||
		ctx: ctx,
 | 
			
		||||
		cfg: cfg,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cm *ConnectionManager) OpenConnection() error {
 | 
			
		||||
	xl := xlog.FromContextSafe(cm.ctx)
 | 
			
		||||
 | 
			
		||||
	// special for quic
 | 
			
		||||
	if strings.EqualFold(cm.cfg.Protocol, "quic") {
 | 
			
		||||
		var tlsConfig *tls.Config
 | 
			
		||||
		var err error
 | 
			
		||||
		sn := cm.cfg.TLSServerName
 | 
			
		||||
		if sn == "" {
 | 
			
		||||
			sn = cm.cfg.ServerAddr
 | 
			
		||||
		}
 | 
			
		||||
		if cm.cfg.TLSEnable {
 | 
			
		||||
			tlsConfig, err = transport.NewClientTLSConfig(
 | 
			
		||||
				cm.cfg.TLSCertFile,
 | 
			
		||||
				cm.cfg.TLSKeyFile,
 | 
			
		||||
				cm.cfg.TLSTrustedCaFile,
 | 
			
		||||
				sn)
 | 
			
		||||
		} else {
 | 
			
		||||
			tlsConfig, err = transport.NewClientTLSConfig("", "", "", sn)
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			xl.Warn("fail to build tls configuration, err: %v", err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		tlsConfig.NextProtos = []string{"frp"}
 | 
			
		||||
 | 
			
		||||
		conn, err := quic.DialAddr(
 | 
			
		||||
			net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
 | 
			
		||||
			tlsConfig, &quic.Config{
 | 
			
		||||
				MaxIdleTimeout:     time.Duration(cm.cfg.QUICMaxIdleTimeout) * time.Second,
 | 
			
		||||
				MaxIncomingStreams: int64(cm.cfg.QUICMaxIncomingStreams),
 | 
			
		||||
				KeepAlivePeriod:    time.Duration(cm.cfg.QUICKeepalivePeriod) * time.Second,
 | 
			
		||||
			})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		cm.quicConn = conn
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !cm.cfg.TCPMux {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	conn, err := cm.realConnect()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmuxCfg := fmux.DefaultConfig()
 | 
			
		||||
	fmuxCfg.KeepAliveInterval = time.Duration(cm.cfg.TCPMuxKeepaliveInterval) * time.Second
 | 
			
		||||
	fmuxCfg.LogOutput = io.Discard
 | 
			
		||||
	session, err := fmux.Client(conn, fmuxCfg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	cm.muxSession = session
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cm *ConnectionManager) Connect() (net.Conn, error) {
 | 
			
		||||
	if cm.quicConn != nil {
 | 
			
		||||
		stream, err := cm.quicConn.OpenStreamSync(context.Background())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return frpNet.QuicStreamToNetConn(stream, cm.quicConn), nil
 | 
			
		||||
	} else if cm.muxSession != nil {
 | 
			
		||||
		stream, err := cm.muxSession.OpenStream()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return stream, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cm.realConnect()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cm *ConnectionManager) realConnect() (net.Conn, error) {
 | 
			
		||||
	xl := xlog.FromContextSafe(cm.ctx)
 | 
			
		||||
	var tlsConfig *tls.Config
 | 
			
		||||
	var err error
 | 
			
		||||
	if cm.cfg.TLSEnable {
 | 
			
		||||
		sn := cm.cfg.TLSServerName
 | 
			
		||||
		if sn == "" {
 | 
			
		||||
			sn = cm.cfg.ServerAddr
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tlsConfig, err = transport.NewClientTLSConfig(
 | 
			
		||||
			cm.cfg.TLSCertFile,
 | 
			
		||||
			cm.cfg.TLSKeyFile,
 | 
			
		||||
			cm.cfg.TLSTrustedCaFile,
 | 
			
		||||
			sn)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			xl.Warn("fail to build tls configuration, err: %v", err)
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	proxyType, addr, auth, err := libdial.ParseProxyURL(cm.cfg.HTTPProxy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		xl.Error("fail to parse proxy url")
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	dialOptions := []libdial.DialOption{}
 | 
			
		||||
	protocol := cm.cfg.Protocol
 | 
			
		||||
	if protocol == "websocket" {
 | 
			
		||||
		protocol = "tcp"
 | 
			
		||||
		dialOptions = append(dialOptions, libdial.WithAfterHook(libdial.AfterHook{Hook: frpNet.DialHookWebsocket()}))
 | 
			
		||||
	}
 | 
			
		||||
	if cm.cfg.ConnectServerLocalIP != "" {
 | 
			
		||||
		dialOptions = append(dialOptions, libdial.WithLocalAddr(cm.cfg.ConnectServerLocalIP))
 | 
			
		||||
	}
 | 
			
		||||
	dialOptions = append(dialOptions,
 | 
			
		||||
		libdial.WithProtocol(protocol),
 | 
			
		||||
		libdial.WithTimeout(time.Duration(cm.cfg.DialServerTimeout)*time.Second),
 | 
			
		||||
		libdial.WithKeepAlive(time.Duration(cm.cfg.DialServerKeepAlive)*time.Second),
 | 
			
		||||
		libdial.WithProxy(proxyType, addr),
 | 
			
		||||
		libdial.WithProxyAuth(auth),
 | 
			
		||||
		libdial.WithTLSConfig(tlsConfig),
 | 
			
		||||
		libdial.WithAfterHook(libdial.AfterHook{
 | 
			
		||||
			Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, cm.cfg.DisableCustomTLSFirstByte),
 | 
			
		||||
		}),
 | 
			
		||||
	)
 | 
			
		||||
	conn, err := libdial.Dial(
 | 
			
		||||
		net.JoinHostPort(cm.cfg.ServerAddr, strconv.Itoa(cm.cfg.ServerPort)),
 | 
			
		||||
		dialOptions...,
 | 
			
		||||
	)
 | 
			
		||||
	return conn, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cm *ConnectionManager) Close() error {
 | 
			
		||||
	if cm.quicConn != nil {
 | 
			
		||||
		_ = cm.quicConn.CloseWithError(0, "")
 | 
			
		||||
	}
 | 
			
		||||
	if cm.muxSession != nil {
 | 
			
		||||
		_ = cm.muxSession.Close()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										568
									
								
								client/visitor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,568 @@
 | 
			
		||||
// 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"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/golib/errors"
 | 
			
		||||
	frpIo "github.com/fatedier/golib/io"
 | 
			
		||||
	"github.com/fatedier/golib/pool"
 | 
			
		||||
	fmux "github.com/hashicorp/yamux"
 | 
			
		||||
 | 
			
		||||
	"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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
 | 
			
		||||
	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", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(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", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(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",
 | 
			
		||||
		net.JoinHostPort(sv.ctl.clientCfg.ServerAddr, strconv.Itoa(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()
 | 
			
		||||
 | 
			
		||||
	if _, err := lConn.Write([]byte(natHoleRespMsg.Sid)); err != nil {
 | 
			
		||||
		xl.Error("write sid error: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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 = io.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", net.JoinHostPort(sv.cfg.BindAddr, strconv.Itoa(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)
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		visitorConn net.Conn
 | 
			
		||||
		err         error
 | 
			
		||||
 | 
			
		||||
		firstPacket *msg.UDPPacket
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case firstPacket = <-sv.sendCh:
 | 
			
		||||
			if firstPacket == nil {
 | 
			
		||||
				xl.Info("frpc sudp visitor proxy is closed")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		case <-sv.checkCloseCh:
 | 
			
		||||
			xl.Info("frpc sudp visitor proxy is closed")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		visitorConn, err = sv.getNewVisitorConn()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			xl.Warn("newVisitorConn to frps error: %v, try to reconnect", err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// visitorConn always be closed when worker done.
 | 
			
		||||
		sv.worker(visitorConn, firstPacket)
 | 
			
		||||
 | 
			
		||||
		select {
 | 
			
		||||
		case <-sv.checkCloseCh:
 | 
			
		||||
			return
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (sv *SUDPVisitor) worker(workConn net.Conn, firstPacket *msg.UDPPacket) {
 | 
			
		||||
	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
 | 
			
		||||
		if firstPacket != nil {
 | 
			
		||||
			if errRet = msg.WriteMsg(conn, firstPacket); errRet != nil {
 | 
			
		||||
				xl.Warn("sender goroutine for udp work connection closed: %v", errRet)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			xl.Trace("send udp package to workConn: %s", firstPacket.Content)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										143
									
								
								client/visitor_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,143 @@
 | 
			
		||||
// 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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,24 +12,13 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package msg
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
type GeneralRes struct {
 | 
			
		||||
	Code int64  `json:"code"`
 | 
			
		||||
	Msg  string `json:"msg"`
 | 
			
		||||
}
 | 
			
		||||
import (
 | 
			
		||||
	_ "github.com/fatedier/frp/assets/frpc"
 | 
			
		||||
	"github.com/fatedier/frp/cmd/frpc/sub"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// messages between control connection of frpc and frps
 | 
			
		||||
type ControlReq struct {
 | 
			
		||||
	Type          int64  `json:"type"`
 | 
			
		||||
	ProxyName     string `json:"proxy_name,omitempty"`
 | 
			
		||||
	AuthKey       string `json:"auth_key, omitempty"`
 | 
			
		||||
	UseEncryption bool   `json:"use_encryption, omitempty"`
 | 
			
		||||
	Timestamp     int64  `json:"timestamp, omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ControlRes struct {
 | 
			
		||||
	Type int64  `json:"type"`
 | 
			
		||||
	Code int64  `json:"code"`
 | 
			
		||||
	Msg  string `json:"msg"`
 | 
			
		||||
func main() {
 | 
			
		||||
	sub.Execute()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/frp/pkg/config"
 | 
			
		||||
	"github.com/fatedier/frp/pkg/consts"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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
									
								
							
							
						
						@@ -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
									
								
							
							
						
						@@ -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"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/frp/pkg/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	rootCmd.AddCommand(reloadCmd)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var reloadCmd = &cobra.Command{
 | 
			
		||||
	Use:   "reload",
 | 
			
		||||
	Short: "Hot-Reload frpc configuration",
 | 
			
		||||
	RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
		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 := io.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										230
									
								
								cmd/frpc/sub/root.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,230 @@
 | 
			
		||||
// 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"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	CfgFileTypeIni = iota
 | 
			
		||||
	CfgFileTypeCmd
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	cfgFile     string
 | 
			
		||||
	cfgDir      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
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
 | 
			
		||||
	rootCmd.PersistentFlags().StringVarP(&cfgDir, "config_dir", "", "", "config directory, run one frpc service for each file in config directory")
 | 
			
		||||
	rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If cfgDir is not empty, run multiple frpc service for each config file in cfgDir.
 | 
			
		||||
		// Note that it's only designed for testing. It's not guaranteed to be stable.
 | 
			
		||||
		if cfgDir != "" {
 | 
			
		||||
			var wg sync.WaitGroup
 | 
			
		||||
			_ = filepath.WalkDir(cfgDir, func(path string, d fs.DirEntry, err error) error {
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return nil
 | 
			
		||||
				}
 | 
			
		||||
				if d.IsDir() {
 | 
			
		||||
					return nil
 | 
			
		||||
				}
 | 
			
		||||
				wg.Add(1)
 | 
			
		||||
				time.Sleep(time.Millisecond)
 | 
			
		||||
				go func() {
 | 
			
		||||
					defer wg.Done()
 | 
			
		||||
					err := runClient(path)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						fmt.Printf("frpc service error for config file [%s]\n", path)
 | 
			
		||||
					}
 | 
			
		||||
				}()
 | 
			
		||||
				return nil
 | 
			
		||||
			})
 | 
			
		||||
			wg.Wait()
 | 
			
		||||
			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, doneCh chan struct{}) {
 | 
			
		||||
	ch := make(chan os.Signal, 1)
 | 
			
		||||
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
 | 
			
		||||
	<-ch
 | 
			
		||||
	svr.GracefulClose(500 * time.Millisecond)
 | 
			
		||||
	close(doneCh)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 cfgFile != "" {
 | 
			
		||||
		log.Trace("start frpc service for config file [%s]", cfgFile)
 | 
			
		||||
		defer log.Trace("frpc service for config file [%s] stopped", cfgFile)
 | 
			
		||||
	}
 | 
			
		||||
	svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
 | 
			
		||||
	if errRet != nil {
 | 
			
		||||
		err = errRet
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kcpDoneCh := make(chan struct{})
 | 
			
		||||
	// Capture the exit signal if we use kcp.
 | 
			
		||||
	if cfg.Protocol == "kcp" {
 | 
			
		||||
		go handleSignal(svr, kcpDoneCh)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = svr.Run()
 | 
			
		||||
	if err == nil && cfg.Protocol == "kcp" {
 | 
			
		||||
		<-kcpDoneCh
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/rodaine/table"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/frp/client"
 | 
			
		||||
	"github.com/fatedier/frp/pkg/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	rootCmd.AddCommand(statusCmd)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var statusCmd = &cobra.Command{
 | 
			
		||||
	Use:   "status",
 | 
			
		||||
	Short: "Overview of all proxies status",
 | 
			
		||||
	RunE: func(cmd *cobra.Command, args []string) error {
 | 
			
		||||
		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 := io.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.Println("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.Println("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.Println("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.Println("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.Println("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.Println("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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										108
									
								
								cmd/frpc/sub/stcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,108 @@
 | 
			
		||||
// 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(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 + "."
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch role {
 | 
			
		||||
		case "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
 | 
			
		||||
		case "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
 | 
			
		||||
		default:
 | 
			
		||||
			fmt.Println("invalid role")
 | 
			
		||||
			os.Exit(1)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err = startService(clientCfg, proxyConfs, visitorConfs, "")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println(err)
 | 
			
		||||
			os.Exit(1)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								cmd/frpc/sub/sudp.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/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/frp/pkg/config"
 | 
			
		||||
	"github.com/fatedier/frp/pkg/consts"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 + "."
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch role {
 | 
			
		||||
		case "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
 | 
			
		||||
		case "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
 | 
			
		||||
		default:
 | 
			
		||||
			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
									
								
							
							
						
						@@ -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
									
								
							
							
						
						@@ -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
									
								
							
							
						
						@@ -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(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
									
								
							
							
						
						@@ -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/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/frp/pkg/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										108
									
								
								cmd/frpc/sub/xtcp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,108 @@
 | 
			
		||||
// 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(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 + "."
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch role {
 | 
			
		||||
		case "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
 | 
			
		||||
		case "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
 | 
			
		||||
		default:
 | 
			
		||||
			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
									
								
							
							
						
						@@ -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"
 | 
			
		||||
	_ "github.com/fatedier/frp/pkg/metrics"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	crypto.DefaultSalt = "frp"
 | 
			
		||||
	rand.Seed(time.Now().UnixNano())
 | 
			
		||||
 | 
			
		||||
	Execute()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										218
									
								
								cmd/frps/root.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,218 @@
 | 
			
		||||
// Copyright 2018 fatedier, fatedier@gmail.com
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/frp/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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	logFile              string
 | 
			
		||||
	logLevel             string
 | 
			
		||||
	logMaxDays           int64
 | 
			
		||||
	disableLogColor      bool
 | 
			
		||||
	token                string
 | 
			
		||||
	subDomainHost        string
 | 
			
		||||
	allowPorts           string
 | 
			
		||||
	maxPortsPerClient    int64
 | 
			
		||||
	tlsOnly              bool
 | 
			
		||||
	dashboardTLSMode     bool
 | 
			
		||||
	dashboardTLSCertFile string
 | 
			
		||||
	dashboardTLSKeyFile  string
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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")
 | 
			
		||||
	rootCmd.PersistentFlags().BoolVarP(&dashboardTLSMode, "dashboard_tls_mode", "", false, "dashboard tls mode")
 | 
			
		||||
	rootCmd.PersistentFlags().StringVarP(&dashboardTLSCertFile, "dashboard_tls_cert_file", "", "", "dashboard tls cert file")
 | 
			
		||||
	rootCmd.PersistentFlags().StringVarP(&dashboardTLSKeyFile, "dashboard_tls_key_file", "", "", "dashboard tls key file")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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.DashboardTLSCertFile = dashboardTLSCertFile
 | 
			
		||||
	cfg.DashboardTLSKeyFile = dashboardTLSKeyFile
 | 
			
		||||
	cfg.DashboardTLSMode = dashboardTLSMode
 | 
			
		||||
	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
									
								
							
							
						
						@@ -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/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
	"github.com/fatedier/frp/pkg/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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,32 +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 = ./frpc.log
 | 
			
		||||
# debug, info, warn, error
 | 
			
		||||
log_level = info
 | 
			
		||||
log_max_days = 3
 | 
			
		||||
# for authentication
 | 
			
		||||
auth_token = 123
 | 
			
		||||
 | 
			
		||||
# ssh is the proxy name same as server's configuration
 | 
			
		||||
[ssh]
 | 
			
		||||
# tcp | http, default is tcp
 | 
			
		||||
type = tcp
 | 
			
		||||
local_ip = 127.0.0.1
 | 
			
		||||
local_port = 22
 | 
			
		||||
# true or false, if true, messages between frps and frpc will be encrypted, default is false
 | 
			
		||||
use_encryption = true
 | 
			
		||||
 | 
			
		||||
# 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, the domains are set in frps.ini
 | 
			
		||||
[web01]
 | 
			
		||||
type = http
 | 
			
		||||
local_ip = 127.0.0.1
 | 
			
		||||
local_port = 80
 | 
			
		||||
use_encryption = true
 | 
			
		||||
 | 
			
		||||
[web02]
 | 
			
		||||
type = http
 | 
			
		||||
local_ip = 127.0.0.1
 | 
			
		||||
local_port = 8000
 | 
			
		||||
remote_port = 6000
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										363
									
								
								conf/frpc_full.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,363 @@
 | 
			
		||||
# [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
 | 
			
		||||
 | 
			
		||||
# The maximum amount of time a dial to server will wait for a connect to complete. Default value is 10 seconds.
 | 
			
		||||
# dial_server_timeout = 10
 | 
			
		||||
 | 
			
		||||
# dial_server_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
 | 
			
		||||
# If negative, keep-alive probes are disabled.
 | 
			
		||||
# dial_server_keepalive = 7200
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
authentication_method = 
 | 
			
		||||
 | 
			
		||||
# 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_scope specifies the permisssions of the token in OIDC authentication if AuthenticationMethod == "oidc". By default, this value is "".
 | 
			
		||||
oidc_scope =
 | 
			
		||||
 | 
			
		||||
# 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 =
 | 
			
		||||
 | 
			
		||||
# oidc_additional_xxx specifies additional parameters to be sent to the OIDC Token Endpoint.
 | 
			
		||||
# For example, if you want to specify the "audience" parameter, you can set as follow.
 | 
			
		||||
# frp will add "audience=<value>" "var1=<value>" to the additional parameters.
 | 
			
		||||
# oidc_additional_audience = https://dev.auth.com/api/v2/
 | 
			
		||||
# oidc_additional_var1 = foobar
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
# specify keep alive interval for tcp mux.
 | 
			
		||||
# only valid if tcp_mux is true.
 | 
			
		||||
# tcp_mux_keepalive_interval = 60
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
# supports tcp, kcp, quic and websocket now, default is tcp
 | 
			
		||||
protocol = tcp
 | 
			
		||||
 | 
			
		||||
# set client binding ip when connect server, default is empty.
 | 
			
		||||
# only when protocol = tcp or websocket, the value will be used.
 | 
			
		||||
connect_server_local_ip = 0.0.0.0
 | 
			
		||||
 | 
			
		||||
# quic protocol options
 | 
			
		||||
# quic_keepalive_period = 10
 | 
			
		||||
# quic_max_idle_timeout = 30
 | 
			
		||||
# quic_max_incoming_streams = 100000
 | 
			
		||||
 | 
			
		||||
# 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 separated 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. Set negative value
 | 
			
		||||
# to disable it.
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
# By default, frpc will connect frps with first custom byte if tls is enabled.
 | 
			
		||||
# If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
 | 
			
		||||
disable_custom_tls_first_byte = false
 | 
			
		||||
 | 
			
		||||
# Enable golang pprof handlers in admin listener.
 | 
			
		||||
# Admin port must be set first.
 | 
			
		||||
pprof_enable = false
 | 
			
		||||
 | 
			
		||||
# '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://web01.frps.com
 | 
			
		||||
subdomain = web01
 | 
			
		||||
custom_domains = web01.yourdomain.com
 | 
			
		||||
# locations is only available for http type
 | 
			
		||||
locations = /,/pic
 | 
			
		||||
# route requests to this service if http basic auto user is abc
 | 
			
		||||
# route_by_http_user = abc
 | 
			
		||||
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
 | 
			
		||||
# route_by_http_user = user1
 | 
			
		||||
@@ -1,29 +1,2 @@
 | 
			
		||||
# [common] is integral section
 | 
			
		||||
[common]
 | 
			
		||||
bind_addr = 0.0.0.0
 | 
			
		||||
bind_port = 7000
 | 
			
		||||
# optional
 | 
			
		||||
vhost_http_port = 80
 | 
			
		||||
# console or real logFile path like ./frps.log
 | 
			
		||||
log_file = ./frps.log
 | 
			
		||||
# debug, info, warn, error
 | 
			
		||||
log_level = info
 | 
			
		||||
log_max_days = 3
 | 
			
		||||
 | 
			
		||||
# ssh is the proxy name, client will use this name and auth_token to connect to server
 | 
			
		||||
[ssh]
 | 
			
		||||
type = tcp
 | 
			
		||||
auth_token = 123
 | 
			
		||||
bind_addr = 0.0.0.0
 | 
			
		||||
listen_port = 6000
 | 
			
		||||
 | 
			
		||||
[web01]
 | 
			
		||||
type = http
 | 
			
		||||
auth_token = 123
 | 
			
		||||
# if proxy type equals http, custom_domains must be set separated by commas
 | 
			
		||||
custom_domains = web01.yourdomain.com,web01.yourdomain2.com
 | 
			
		||||
 | 
			
		||||
[web02]
 | 
			
		||||
type = http
 | 
			
		||||
auth_token = 123
 | 
			
		||||
custom_domains = web02.yourdomain.com
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										168
									
								
								conf/frps_full.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,168 @@
 | 
			
		||||
# [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
 | 
			
		||||
 | 
			
		||||
# udp port used for quic protocol.
 | 
			
		||||
# if not set, quic is disabled in frps.
 | 
			
		||||
# quic_bind_port = 7002
 | 
			
		||||
# quic protocol options
 | 
			
		||||
# quic_keepalive_period = 10
 | 
			
		||||
# quic_max_idle_timeout = 30
 | 
			
		||||
# quic_max_incoming_streams = 100000
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
# If tcpmux_passthrough is true, frps won't do any update on traffic.
 | 
			
		||||
# tcpmux_passthrough = false
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
dashboard_user = admin
 | 
			
		||||
dashboard_pwd = admin
 | 
			
		||||
 | 
			
		||||
# dashboard TLS mode
 | 
			
		||||
dashboard_tls_mode = false
 | 
			
		||||
# dashboard_tls_cert_file = server.crt
 | 
			
		||||
# dashboard_tls_key_file = server.key
 | 
			
		||||
 | 
			
		||||
# 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. Set negative value to disable it.
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
# specify keep alive interval for tcp mux.
 | 
			
		||||
# only valid if tcp_mux is true.
 | 
			
		||||
# tcp_mux_keepalive_interval = 60
 | 
			
		||||
 | 
			
		||||
# tcp_keepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
 | 
			
		||||
# If negative, keep-alive probes are disabled.
 | 
			
		||||
# tcp_keepalive = 7200
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
# Enable golang pprof handlers in dashboard listener.
 | 
			
		||||
# Dashboard port must be set first
 | 
			
		||||
pprof_enable = false
 | 
			
		||||
 | 
			
		||||
[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
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								doc/pic/dashboard.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 31 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/pic/donate-alipay.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 36 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/pic/donate-wechatpay.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 27 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/pic/sponsor_doppler.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 41 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/pic/sponsor_workos.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 37 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/pic/zsxq.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 12 KiB  | 
@@ -1,135 +0,0 @@
 | 
			
		||||
# Quick Start
 | 
			
		||||
 | 
			
		||||
frp is easier to use compared with other similar projects.
 | 
			
		||||
 | 
			
		||||
We will use two simple demo to demonstrate how to use frp.
 | 
			
		||||
 | 
			
		||||
1. 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).
 | 
			
		||||
2. How to visit web service in **server A**'s **8000 port** and **8001 port** by **web01.yourdomain.com** and **web02.yourdomain.com** through **server B** with public ID address.
 | 
			
		||||
 | 
			
		||||
### 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`.
 | 
			
		||||
 | 
			
		||||
If you want to try it quickly, download the compiled program and configuration files from [https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases).
 | 
			
		||||
 | 
			
		||||
### 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**), or visit custom domains by browser.
 | 
			
		||||
 | 
			
		||||
## Tcp port forwarding
 | 
			
		||||
 | 
			
		||||
### 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
 | 
			
		||||
 | 
			
		||||
# ssh is the custom name of proxy and there can be many proxies with unique name in one configure file
 | 
			
		||||
[ssh]
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
# ssh is proxy name same with configure in frps.ini
 | 
			
		||||
[ssh]
 | 
			
		||||
# 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
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Http port forwarding and Custom domains binding
 | 
			
		||||
 | 
			
		||||
If you only want to forward port one by one, you just need refer to [Tcp port forwarding](/doc/quick_start_en.md#Tcp-port-forwarding).If you want to visit different web pages deployed in different web servers by **server B**'s **80 port**, you should specify the type as **http**.
 | 
			
		||||
 | 
			
		||||
You also need to resolve your **A record** of your custom domain to [server_addr], or resolve your **CNAME record** to [server_addr] if [server_addr] is a domain.
 | 
			
		||||
 | 
			
		||||
After that, you can visit your web pages in local server by custom domains.
 | 
			
		||||
 | 
			
		||||
### Configuration files
 | 
			
		||||
 | 
			
		||||
#### frps.ini
 | 
			
		||||
 | 
			
		||||
```ini
 | 
			
		||||
[common]
 | 
			
		||||
bind_addr = 0.0.0.0
 | 
			
		||||
bind_port = 7000
 | 
			
		||||
# if you want to support vhost, specify one port for http services
 | 
			
		||||
vhost_http_port = 80
 | 
			
		||||
log_file = ./frps.log
 | 
			
		||||
log_level = info
 | 
			
		||||
 | 
			
		||||
[web01]
 | 
			
		||||
type = http
 | 
			
		||||
auth_token = 123
 | 
			
		||||
# # if proxy type equals http, custom_domains must be set separated by commas
 | 
			
		||||
custom_domains = web01.yourdomain.com
 | 
			
		||||
 | 
			
		||||
[web02]
 | 
			
		||||
type = http
 | 
			
		||||
auth_token = 123
 | 
			
		||||
custom_domains = web02.yourdomain.com
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### frpc.ini
 | 
			
		||||
 | 
			
		||||
```ini
 | 
			
		||||
[common]
 | 
			
		||||
server_addr = x.x.x.x
 | 
			
		||||
server_port = 7000
 | 
			
		||||
log_file = ./frpc.log
 | 
			
		||||
log_level = info
 | 
			
		||||
auth_token = 123 
 | 
			
		||||
 | 
			
		||||
# custom domains are set in frps.ini
 | 
			
		||||
[web01]
 | 
			
		||||
type = http
 | 
			
		||||
local_ip = 127.0.0.1
 | 
			
		||||
local_port = 8000
 | 
			
		||||
# encryption is optional, default is false
 | 
			
		||||
use_encryption = true
 | 
			
		||||
 | 
			
		||||
[web02]
 | 
			
		||||
type = http
 | 
			
		||||
local_ip = 127.0.0.1
 | 
			
		||||
local_port = 8001
 | 
			
		||||
```
 | 
			
		||||
@@ -1,137 +0,0 @@
 | 
			
		||||
# frp 使用文档
 | 
			
		||||
 | 
			
		||||
相比于其他项目而言 frp 更易于部署和使用,这里我们用两个简单的示例来演示 frp 的使用过程。
 | 
			
		||||
 | 
			
		||||
1. 如何通过一台拥有公网IP地址的**服务器B**,访问处于公司内部网络环境中的**服务器A**的**ssh**端口,**服务器B**的IP地址为 x.x.x.x(测试时替换为真实的IP地址)。
 | 
			
		||||
2. 如何利用一台拥有公网IP地址的**服务器B**,使通过 **web01.yourdomain.com** 可以访问内网环境中**服务器A**上**8000端口**的web服务,**web02.yourdomain.com** 可以访问**服务器A**上**8001端口**的web服务。
 | 
			
		||||
 | 
			
		||||
### 下载源码
 | 
			
		||||
 | 
			
		||||
推荐直接使用 `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` 拷贝到相应目录下。
 | 
			
		||||
 | 
			
		||||
如果您想快速进行测试,也可以根据您服务器的操作系统及架构直接下载编译好的程序及示例配置文件,[https://github.com/fatedier/frp/releases](https://github.com/fatedier/frp/releases)。
 | 
			
		||||
 | 
			
		||||
### 编译
 | 
			
		||||
 | 
			
		||||
进入下载后的源码根目录,执行 `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**上存在的真实用户),或通过浏览器访问自定义域名验证 http 服务是否转发成功。
 | 
			
		||||
 | 
			
		||||
## tcp 端口转发
 | 
			
		||||
 | 
			
		||||
转发 tcp 端口需要按照需求修改 frps 和 frpc 的配置文件。
 | 
			
		||||
 | 
			
		||||
### 配置文件
 | 
			
		||||
 | 
			
		||||
#### frps.ini
 | 
			
		||||
 | 
			
		||||
```ini
 | 
			
		||||
[common]
 | 
			
		||||
bind_addr = 0.0.0.0
 | 
			
		||||
# 用于接收 frpc 连接的端口
 | 
			
		||||
bind_port = 7000
 | 
			
		||||
log_file = ./frps.log
 | 
			
		||||
log_level = info
 | 
			
		||||
 | 
			
		||||
# ssh 为代理的自定义名称,可以有多个,不能重复,和frpc中名称对应
 | 
			
		||||
[ssh]
 | 
			
		||||
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 
 | 
			
		||||
 | 
			
		||||
# ssh 需要和 frps.ini 中配置一致
 | 
			
		||||
[ssh]
 | 
			
		||||
# 需要转发的本地端口
 | 
			
		||||
local_port = 22
 | 
			
		||||
# 启用加密,frpc与frps之间通信加密,默认为 false
 | 
			
		||||
use_encryption = true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## http 端口转发,自定义域名绑定
 | 
			
		||||
 | 
			
		||||
如果只需要一对一的转发,例如**服务器B**的**80端口**转发**服务器A**的**8000端口**,则只需要配置 [tcp 端口转发](/doc/quick_start_zh.md#tcp-端口转发) 即可,如果需要使**服务器B**的**80端口**可以转发至**多个**web服务端口,则需要指定代理的类型为 http,并且在 frps 的配置文件中配置用于提供 http 转发服务的端口。
 | 
			
		||||
 | 
			
		||||
按照如下的内容修改配置文件后,需要将自定义域名的 **A 记录**解析到 [server_addr],如果 [server_addr] 是域名也可以将自定义域名的 **CNAME 记录**解析到 [server_addr]。
 | 
			
		||||
 | 
			
		||||
之后就可以通过自定义域名访问到本地的多个 web 服务。
 | 
			
		||||
 | 
			
		||||
### 配置文件
 | 
			
		||||
 | 
			
		||||
#### frps.ini
 | 
			
		||||
 | 
			
		||||
```ini
 | 
			
		||||
[common]
 | 
			
		||||
bind_addr = 0.0.0.0
 | 
			
		||||
bind_port = 7000
 | 
			
		||||
# 如果需要支持http类型的代理则需要指定一个端口
 | 
			
		||||
vhost_http_port = 80
 | 
			
		||||
log_file = ./frps.log
 | 
			
		||||
log_level = info
 | 
			
		||||
 | 
			
		||||
[web01]
 | 
			
		||||
# type 默认为 tcp,这里需要特别指定为 http
 | 
			
		||||
type = http
 | 
			
		||||
auth_token = 123
 | 
			
		||||
# 自定义域名绑定,如果需要同时绑定多个以英文逗号分隔
 | 
			
		||||
custom_domains = web01.yourdomain.com
 | 
			
		||||
 | 
			
		||||
[web02]
 | 
			
		||||
type = http
 | 
			
		||||
auth_token = 123
 | 
			
		||||
custom_domains = web02.yourdomain.com
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### frpc.ini
 | 
			
		||||
 | 
			
		||||
```ini
 | 
			
		||||
[common]
 | 
			
		||||
server_addr = x.x.x.x
 | 
			
		||||
server_port = 7000
 | 
			
		||||
log_file = ./frpc.log
 | 
			
		||||
log_level = info
 | 
			
		||||
auth_token = 123 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 自定义域名在 frps.ini 中配置,方便做统一管理
 | 
			
		||||
[web01]
 | 
			
		||||
type = http
 | 
			
		||||
local_ip = 127.0.0.1
 | 
			
		||||
local_port = 8000
 | 
			
		||||
# 可选是否加密
 | 
			
		||||
use_encryption = true
 | 
			
		||||
 | 
			
		||||
[web02]
 | 
			
		||||
type = http
 | 
			
		||||
local_ip = 127.0.0.1
 | 
			
		||||
local_port = 8001
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										262
									
								
								doc/server_plugin.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,262 @@
 | 
			
		||||
### 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`, `CloseProxy`, `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,
 | 
			
		||||
        "client_address": <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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### CloseProxy
 | 
			
		||||
 | 
			
		||||
A previously created proxy is closed.
 | 
			
		||||
 | 
			
		||||
Please note that one request will be sent for every proxy that is closed, do **NOT** use this
 | 
			
		||||
if you have too many proxies bound to a single client, as this may exhaust the server's resources.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
{
 | 
			
		||||
    "content": {
 | 
			
		||||
        "user": {
 | 
			
		||||
            "user": <string>,
 | 
			
		||||
            "metas": map<string>string
 | 
			
		||||
            "run_id": <string>
 | 
			
		||||
        },
 | 
			
		||||
        "proxy_name": <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
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										12
									
								
								dockerfiles/Dockerfile-for-frpc
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
			
		||||
FROM golang:1.19 AS building
 | 
			
		||||
 | 
			
		||||
COPY . /building
 | 
			
		||||
WORKDIR /building
 | 
			
		||||
 | 
			
		||||
RUN make frpc
 | 
			
		||||
 | 
			
		||||
FROM alpine:3
 | 
			
		||||
 | 
			
		||||
COPY --from=building /building/bin/frpc /usr/bin/frpc
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["/usr/bin/frpc"]
 | 
			
		||||
							
								
								
									
										12
									
								
								dockerfiles/Dockerfile-for-frps
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
			
		||||
FROM golang:1.19 AS building
 | 
			
		||||
 | 
			
		||||
COPY . /building
 | 
			
		||||
WORKDIR /building
 | 
			
		||||
 | 
			
		||||
RUN make frps
 | 
			
		||||
 | 
			
		||||
FROM alpine:3
 | 
			
		||||
 | 
			
		||||
COPY --from=building /building/bin/frps /usr/bin/frps
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["/usr/bin/frps"]
 | 
			
		||||
							
								
								
									
										76
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,76 @@
 | 
			
		||||
module github.com/fatedier/frp
 | 
			
		||||
 | 
			
		||||
go 1.19
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
 | 
			
		||||
	github.com/coreos/go-oidc/v3 v3.4.0
 | 
			
		||||
	github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
 | 
			
		||||
	github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac
 | 
			
		||||
	github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible
 | 
			
		||||
	github.com/go-playground/validator/v10 v10.11.0
 | 
			
		||||
	github.com/google/uuid v1.3.0
 | 
			
		||||
	github.com/gorilla/mux v1.8.0
 | 
			
		||||
	github.com/gorilla/websocket v1.5.0
 | 
			
		||||
	github.com/hashicorp/yamux v0.1.1
 | 
			
		||||
	github.com/lucas-clemente/quic-go v0.31.0
 | 
			
		||||
	github.com/onsi/ginkgo v1.16.4
 | 
			
		||||
	github.com/onsi/gomega v1.20.2
 | 
			
		||||
	github.com/pires/go-proxyproto v0.6.2
 | 
			
		||||
	github.com/prometheus/client_golang v1.13.0
 | 
			
		||||
	github.com/rodaine/table v1.0.1
 | 
			
		||||
	github.com/spf13/cobra v1.1.3
 | 
			
		||||
	github.com/stretchr/testify v1.7.0
 | 
			
		||||
	golang.org/x/net v0.4.0
 | 
			
		||||
	golang.org/x/oauth2 v0.3.0
 | 
			
		||||
	golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
 | 
			
		||||
	gopkg.in/ini.v1 v1.67.0
 | 
			
		||||
	k8s.io/apimachinery v0.25.0
 | 
			
		||||
	k8s.io/client-go v0.25.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect
 | 
			
		||||
	github.com/beorn7/perks v1.0.1 // indirect
 | 
			
		||||
	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 | 
			
		||||
	github.com/davecgh/go-spew v1.1.1 // indirect
 | 
			
		||||
	github.com/fsnotify/fsnotify v1.4.9 // indirect
 | 
			
		||||
	github.com/go-playground/locales v0.14.0 // indirect
 | 
			
		||||
	github.com/go-playground/universal-translator v0.18.0 // indirect
 | 
			
		||||
	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 | 
			
		||||
	github.com/golang/mock v1.6.0 // indirect
 | 
			
		||||
	github.com/golang/protobuf v1.5.2 // indirect
 | 
			
		||||
	github.com/golang/snappy v0.0.3 // indirect
 | 
			
		||||
	github.com/google/go-cmp v0.5.8 // indirect
 | 
			
		||||
	github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
 | 
			
		||||
	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 | 
			
		||||
	github.com/klauspost/cpuid/v2 v2.0.6 // indirect
 | 
			
		||||
	github.com/klauspost/reedsolomon v1.9.15 // indirect
 | 
			
		||||
	github.com/leodido/go-urn v1.2.1 // indirect
 | 
			
		||||
	github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
 | 
			
		||||
	github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
 | 
			
		||||
	github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
 | 
			
		||||
	github.com/nxadm/tail v1.4.8 // indirect
 | 
			
		||||
	github.com/onsi/ginkgo/v2 v2.2.0 // indirect
 | 
			
		||||
	github.com/pkg/errors v0.9.1 // indirect
 | 
			
		||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
			
		||||
	github.com/prometheus/client_model v0.2.0 // indirect
 | 
			
		||||
	github.com/prometheus/common v0.37.0 // indirect
 | 
			
		||||
	github.com/prometheus/procfs v0.8.0 // indirect
 | 
			
		||||
	github.com/spf13/pflag v1.0.5 // indirect
 | 
			
		||||
	github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
 | 
			
		||||
	github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect
 | 
			
		||||
	github.com/tjfoc/gmsm v1.4.1 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.4.0 // indirect
 | 
			
		||||
	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
 | 
			
		||||
	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
 | 
			
		||||
	golang.org/x/sys v0.3.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.5.0 // indirect
 | 
			
		||||
	golang.org/x/tools v0.1.12 // indirect
 | 
			
		||||
	google.golang.org/appengine v1.6.7 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.28.1 // indirect
 | 
			
		||||
	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
 | 
			
		||||
	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
			
		||||
	k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										977
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,977 @@
 | 
			
		||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 | 
			
		||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 | 
			
		||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
 | 
			
		||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 | 
			
		||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 | 
			
		||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
 | 
			
		||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
 | 
			
		||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
 | 
			
		||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
 | 
			
		||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
 | 
			
		||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
 | 
			
		||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
 | 
			
		||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
 | 
			
		||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
 | 
			
		||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
 | 
			
		||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
 | 
			
		||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
 | 
			
		||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
 | 
			
		||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
 | 
			
		||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
 | 
			
		||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
 | 
			
		||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
 | 
			
		||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
 | 
			
		||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
 | 
			
		||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
 | 
			
		||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
 | 
			
		||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
 | 
			
		||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
 | 
			
		||||
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
 | 
			
		||||
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
 | 
			
		||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 | 
			
		||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 | 
			
		||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 | 
			
		||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 | 
			
		||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 | 
			
		||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 | 
			
		||||
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
 | 
			
		||||
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
 | 
			
		||||
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
 | 
			
		||||
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
 | 
			
		||||
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
 | 
			
		||||
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
 | 
			
		||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 | 
			
		||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 | 
			
		||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
 | 
			
		||||
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
 | 
			
		||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 | 
			
		||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 | 
			
		||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
 | 
			
		||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
 | 
			
		||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 | 
			
		||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 | 
			
		||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 | 
			
		||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 | 
			
		||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 | 
			
		||||
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
 | 
			
		||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 | 
			
		||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
 | 
			
		||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
 | 
			
		||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
			
		||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
			
		||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 | 
			
		||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 | 
			
		||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 | 
			
		||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 | 
			
		||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 | 
			
		||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 | 
			
		||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 | 
			
		||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 | 
			
		||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 | 
			
		||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 | 
			
		||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 | 
			
		||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 | 
			
		||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 | 
			
		||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 | 
			
		||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 | 
			
		||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 | 
			
		||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 | 
			
		||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 | 
			
		||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 | 
			
		||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 | 
			
		||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
			
		||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
 | 
			
		||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
			
		||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 | 
			
		||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 | 
			
		||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 | 
			
		||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 | 
			
		||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 | 
			
		||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 | 
			
		||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 | 
			
		||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
 | 
			
		||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 | 
			
		||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 | 
			
		||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 | 
			
		||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 | 
			
		||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 | 
			
		||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 | 
			
		||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
			
		||||
github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g=
 | 
			
		||||
github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
 | 
			
		||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 | 
			
		||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 | 
			
		||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 | 
			
		||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
			
		||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
 | 
			
		||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 | 
			
		||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
 | 
			
		||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
 | 
			
		||||
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac h1:td1FJwN/oz8+9GldeEm3YdBX0Husc0FSPywLesZxi4w=
 | 
			
		||||
github.com/fatedier/golib v0.1.1-0.20220321042308-c306138b83ac/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo=
 | 
			
		||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
 | 
			
		||||
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
 | 
			
		||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 | 
			
		||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 | 
			
		||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 | 
			
		||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 | 
			
		||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 | 
			
		||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 | 
			
		||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 | 
			
		||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
 | 
			
		||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
 | 
			
		||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 | 
			
		||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 | 
			
		||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
 | 
			
		||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
 | 
			
		||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 | 
			
		||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
 | 
			
		||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 | 
			
		||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
 | 
			
		||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
 | 
			
		||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 | 
			
		||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
 | 
			
		||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 | 
			
		||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 | 
			
		||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 | 
			
		||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
			
		||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 | 
			
		||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 | 
			
		||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 | 
			
		||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 | 
			
		||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 | 
			
		||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 | 
			
		||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
 | 
			
		||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
 | 
			
		||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
 | 
			
		||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 | 
			
		||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 | 
			
		||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 | 
			
		||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 | 
			
		||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 | 
			
		||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 | 
			
		||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 | 
			
		||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 | 
			
		||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 | 
			
		||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 | 
			
		||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 | 
			
		||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 | 
			
		||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
 | 
			
		||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 | 
			
		||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 | 
			
		||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 | 
			
		||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
			
		||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
			
		||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 | 
			
		||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
 | 
			
		||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
			
		||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 | 
			
		||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 | 
			
		||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 | 
			
		||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
 | 
			
		||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
 | 
			
		||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 | 
			
		||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 | 
			
		||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 | 
			
		||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 | 
			
		||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 | 
			
		||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 | 
			
		||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 | 
			
		||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
 | 
			
		||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 | 
			
		||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 | 
			
		||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 | 
			
		||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
 | 
			
		||||
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
			
		||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 | 
			
		||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 | 
			
		||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
			
		||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
 | 
			
		||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
			
		||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 | 
			
		||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 | 
			
		||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 | 
			
		||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 | 
			
		||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
 | 
			
		||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
 | 
			
		||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 | 
			
		||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
 | 
			
		||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 | 
			
		||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
 | 
			
		||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
 | 
			
		||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
 | 
			
		||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
 | 
			
		||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 | 
			
		||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 | 
			
		||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 | 
			
		||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 | 
			
		||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 | 
			
		||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
 | 
			
		||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 | 
			
		||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 | 
			
		||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 | 
			
		||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
 | 
			
		||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 | 
			
		||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 | 
			
		||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 | 
			
		||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 | 
			
		||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
			
		||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 | 
			
		||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 | 
			
		||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 | 
			
		||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
			
		||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
			
		||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 | 
			
		||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 | 
			
		||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 | 
			
		||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 | 
			
		||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 | 
			
		||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 | 
			
		||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 | 
			
		||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
			
		||||
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
 | 
			
		||||
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 | 
			
		||||
github.com/klauspost/reedsolomon v1.9.15 h1:g2erWKD2M6rgnPf89fCji6jNlhMKMdXcuNHMW1SYCIo=
 | 
			
		||||
github.com/klauspost/reedsolomon v1.9.15/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 | 
			
		||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 | 
			
		||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 | 
			
		||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
			
		||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 | 
			
		||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 | 
			
		||||
github.com/lucas-clemente/quic-go v0.31.0 h1:MfNp3fk0wjWRajw6quMFA3ap1AVtlU+2mtwmbVogB2M=
 | 
			
		||||
github.com/lucas-clemente/quic-go v0.31.0/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g=
 | 
			
		||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
			
		||||
github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
 | 
			
		||||
github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
 | 
			
		||||
github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
 | 
			
		||||
github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
 | 
			
		||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 | 
			
		||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 | 
			
		||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 | 
			
		||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 | 
			
		||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 | 
			
		||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
 | 
			
		||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
 | 
			
		||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
 | 
			
		||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 | 
			
		||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 | 
			
		||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 | 
			
		||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 | 
			
		||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 | 
			
		||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 | 
			
		||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 | 
			
		||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
			
		||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 | 
			
		||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
 | 
			
		||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 | 
			
		||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
 | 
			
		||||
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
 | 
			
		||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 | 
			
		||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 | 
			
		||||
github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY=
 | 
			
		||||
github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
 | 
			
		||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 | 
			
		||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 | 
			
		||||
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
 | 
			
		||||
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
 | 
			
		||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 | 
			
		||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 | 
			
		||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
 | 
			
		||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 | 
			
		||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 | 
			
		||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 | 
			
		||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 | 
			
		||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 | 
			
		||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 | 
			
		||||
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
 | 
			
		||||
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
 | 
			
		||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 | 
			
		||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
			
		||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
			
		||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
 | 
			
		||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
			
		||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 | 
			
		||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 | 
			
		||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 | 
			
		||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 | 
			
		||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 | 
			
		||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 | 
			
		||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
 | 
			
		||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
 | 
			
		||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 | 
			
		||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 | 
			
		||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 | 
			
		||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 | 
			
		||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 | 
			
		||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 | 
			
		||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
 | 
			
		||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
 | 
			
		||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 | 
			
		||||
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
 | 
			
		||||
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
 | 
			
		||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 | 
			
		||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
			
		||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 | 
			
		||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 | 
			
		||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 | 
			
		||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 | 
			
		||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 | 
			
		||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 | 
			
		||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 | 
			
		||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 | 
			
		||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 | 
			
		||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
 | 
			
		||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 | 
			
		||||
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
 | 
			
		||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
 | 
			
		||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 | 
			
		||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 | 
			
		||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 | 
			
		||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 | 
			
		||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
			
		||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
			
		||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
			
		||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 | 
			
		||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 | 
			
		||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
 | 
			
		||||
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
 | 
			
		||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
 | 
			
		||||
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
 | 
			
		||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
 | 
			
		||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
 | 
			
		||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 | 
			
		||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 | 
			
		||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
 | 
			
		||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 | 
			
		||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 | 
			
		||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 | 
			
		||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 | 
			
		||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 | 
			
		||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 | 
			
		||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 | 
			
		||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 | 
			
		||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 | 
			
		||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 | 
			
		||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 | 
			
		||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 | 
			
		||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 | 
			
		||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
 | 
			
		||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
 | 
			
		||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 | 
			
		||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 | 
			
		||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 | 
			
		||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
 | 
			
		||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
 | 
			
		||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 | 
			
		||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 | 
			
		||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 | 
			
		||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 | 
			
		||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 | 
			
		||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 | 
			
		||||
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 | 
			
		||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 | 
			
		||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 | 
			
		||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
			
		||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 | 
			
		||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 | 
			
		||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 | 
			
		||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 | 
			
		||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 | 
			
		||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 | 
			
		||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 | 
			
		||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 | 
			
		||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 | 
			
		||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 | 
			
		||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 | 
			
		||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
 | 
			
		||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 | 
			
		||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
			
		||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
			
		||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
			
		||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
			
		||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
			
		||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
			
		||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 | 
			
		||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 | 
			
		||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 | 
			
		||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
			
		||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
			
		||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
			
		||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
			
		||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 | 
			
		||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 | 
			
		||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 | 
			
		||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 | 
			
		||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 | 
			
		||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 | 
			
		||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 | 
			
		||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 | 
			
		||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
			
		||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
			
		||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 | 
			
		||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
 | 
			
		||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
 | 
			
		||||
golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
 | 
			
		||||
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
 | 
			
		||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
 | 
			
		||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
			
		||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
 | 
			
		||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
 | 
			
		||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 | 
			
		||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 | 
			
		||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
 | 
			
		||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
 | 
			
		||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 | 
			
		||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 | 
			
		||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 | 
			
		||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 | 
			
		||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 | 
			
		||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 | 
			
		||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 | 
			
		||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 | 
			
		||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 | 
			
		||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 | 
			
		||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 | 
			
		||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 | 
			
		||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 | 
			
		||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
 | 
			
		||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
 | 
			
		||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
 | 
			
		||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
 | 
			
		||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
 | 
			
		||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
 | 
			
		||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
 | 
			
		||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
 | 
			
		||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
 | 
			
		||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
 | 
			
		||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
 | 
			
		||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
 | 
			
		||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
 | 
			
		||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 | 
			
		||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 | 
			
		||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
 | 
			
		||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
 | 
			
		||||
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
 | 
			
		||||
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
 | 
			
		||||
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
 | 
			
		||||
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
 | 
			
		||||
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
 | 
			
		||||
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
 | 
			
		||||
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
 | 
			
		||||
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
 | 
			
		||||
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
 | 
			
		||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 | 
			
		||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
			
		||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
			
		||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 | 
			
		||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 | 
			
		||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 | 
			
		||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 | 
			
		||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
 | 
			
		||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 | 
			
		||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 | 
			
		||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 | 
			
		||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 | 
			
		||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 | 
			
		||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 | 
			
		||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 | 
			
		||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 | 
			
		||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
 | 
			
		||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
 | 
			
		||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 | 
			
		||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 | 
			
		||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
 | 
			
		||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 | 
			
		||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
 | 
			
		||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
 | 
			
		||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 | 
			
		||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 | 
			
		||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 | 
			
		||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 | 
			
		||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 | 
			
		||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 | 
			
		||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 | 
			
		||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 | 
			
		||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 | 
			
		||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 | 
			
		||||
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 | 
			
		||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
 | 
			
		||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 | 
			
		||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 | 
			
		||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 | 
			
		||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 | 
			
		||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 | 
			
		||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
 | 
			
		||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 | 
			
		||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 | 
			
		||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 | 
			
		||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
 | 
			
		||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 | 
			
		||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 | 
			
		||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
			
		||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
			
		||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 | 
			
		||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 | 
			
		||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 | 
			
		||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 | 
			
		||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
			
		||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 | 
			
		||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
			
		||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 | 
			
		||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
			
		||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 | 
			
		||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
 | 
			
		||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 | 
			
		||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 | 
			
		||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 | 
			
		||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 | 
			
		||||
k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU=
 | 
			
		||||
k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0=
 | 
			
		||||
k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E=
 | 
			
		||||
k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8=
 | 
			
		||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
 | 
			
		||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
 | 
			
		||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 | 
			
		||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 | 
			
		||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 | 
			
		||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 | 
			
		||||