Compare commits
	
		
			20 Commits
		
	
	
		
			v0.0.1
			...
			116c7b51fe
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 116c7b51fe | |||
| 9be710dc19 | |||
| e8be8d625a | |||
| c87faa2017 | |||
| 79c15f2c37 | |||
| 44064e4dd1 | |||
| c95edd6ed3 | |||
| 74639e9c4e | |||
| 88cc6ff4d5 | |||
| 330631fd79 | |||
| 42a4ccb24c | |||
| 2c82233b4f | |||
| a465210bd6 | |||
| f5a20fd0fc | |||
| 34b9e3b16e | |||
| 3038eb66d8 | |||
| 52aa2027d0 | |||
| 9584bdbf4c | |||
| d7b7ac6357 | |||
| ca1f44d93c | 
| @ -62,6 +62,9 @@ type saveConfigRequest struct { | |||||||
| 	// output format, one of yaml|json, default is yaml. | 	// output format, one of yaml|json, default is yaml. | ||||||
| 	// in: query | 	// in: query | ||||||
| 	Format string `form:"format" json:"format"` | 	Format string `form:"format" json:"format"` | ||||||
|  | 	// file path, default is gost.yaml|gost.json in current working directory. | ||||||
|  | 	// in: query | ||||||
|  | 	Path string `form:"path" json:"path"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // successful operation. | // successful operation. | ||||||
| @ -92,6 +95,10 @@ func saveConfig(ctx *gin.Context) { | |||||||
| 		req.Format = "yaml" | 		req.Format = "yaml" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if req.Path != "" { | ||||||
|  | 		file = req.Path | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	f, err := os.Create(file) | 	f, err := os.Create(file) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		writeError(ctx, &Error{ | 		writeError(ctx, &Error{ | ||||||
|  | |||||||
							
								
								
									
										169
									
								
								api/config_router.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								api/config_router.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,169 @@ | |||||||
|  | package api | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  |  | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
|  | 	"github.com/go-gost/x/config" | ||||||
|  | 	parser "github.com/go-gost/x/config/parsing/router" | ||||||
|  | 	"github.com/go-gost/x/registry" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // swagger:parameters createRouterRequest | ||||||
|  | type createRouterRequest struct { | ||||||
|  | 	// in: body | ||||||
|  | 	Data config.RouterConfig `json:"data"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // successful operation. | ||||||
|  | // swagger:response createRouterResponse | ||||||
|  | type createRouterResponse struct { | ||||||
|  | 	Data Response | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createRouter(ctx *gin.Context) { | ||||||
|  | 	// swagger:route POST /config/routers Router createRouterRequest | ||||||
|  | 	// | ||||||
|  | 	// Create a new router, the name of the router must be unique in router list. | ||||||
|  | 	// | ||||||
|  | 	//     Security: | ||||||
|  | 	//       basicAuth: [] | ||||||
|  | 	// | ||||||
|  | 	//     Responses: | ||||||
|  | 	//       200: createRouterResponse | ||||||
|  |  | ||||||
|  | 	var req createRouterRequest | ||||||
|  | 	ctx.ShouldBindJSON(&req.Data) | ||||||
|  |  | ||||||
|  | 	if req.Data.Name == "" { | ||||||
|  | 		writeError(ctx, ErrInvalid) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v := parser.ParseRouter(&req.Data) | ||||||
|  |  | ||||||
|  | 	if err := registry.RouterRegistry().Register(req.Data.Name, v); err != nil { | ||||||
|  | 		writeError(ctx, ErrDup) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	config.OnUpdate(func(c *config.Config) error { | ||||||
|  | 		c.Routers = append(c.Routers, &req.Data) | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	ctx.JSON(http.StatusOK, Response{ | ||||||
|  | 		Msg: "OK", | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // swagger:parameters updateRouterRequest | ||||||
|  | type updateRouterRequest struct { | ||||||
|  | 	// in: path | ||||||
|  | 	// required: true | ||||||
|  | 	Router string `uri:"router" json:"router"` | ||||||
|  | 	// in: body | ||||||
|  | 	Data config.RouterConfig `json:"data"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // successful operation. | ||||||
|  | // swagger:response updateRouterResponse | ||||||
|  | type updateRouterResponse struct { | ||||||
|  | 	Data Response | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func updateRouter(ctx *gin.Context) { | ||||||
|  | 	// swagger:route PUT /config/routers/{router} Router updateRouterRequest | ||||||
|  | 	// | ||||||
|  | 	// Update router by name, the router must already exist. | ||||||
|  | 	// | ||||||
|  | 	//     Security: | ||||||
|  | 	//       basicAuth: [] | ||||||
|  | 	// | ||||||
|  | 	//     Responses: | ||||||
|  | 	//       200: updateRouterResponse | ||||||
|  |  | ||||||
|  | 	var req updateRouterRequest | ||||||
|  | 	ctx.ShouldBindUri(&req) | ||||||
|  | 	ctx.ShouldBindJSON(&req.Data) | ||||||
|  |  | ||||||
|  | 	if !registry.RouterRegistry().IsRegistered(req.Router) { | ||||||
|  | 		writeError(ctx, ErrNotFound) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req.Data.Name = req.Router | ||||||
|  |  | ||||||
|  | 	v := parser.ParseRouter(&req.Data) | ||||||
|  |  | ||||||
|  | 	registry.RouterRegistry().Unregister(req.Router) | ||||||
|  |  | ||||||
|  | 	if err := registry.RouterRegistry().Register(req.Router, v); err != nil { | ||||||
|  | 		writeError(ctx, ErrDup) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	config.OnUpdate(func(c *config.Config) error { | ||||||
|  | 		for i := range c.Routers { | ||||||
|  | 			if c.Routers[i].Name == req.Router { | ||||||
|  | 				c.Routers[i] = &req.Data | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	ctx.JSON(http.StatusOK, Response{ | ||||||
|  | 		Msg: "OK", | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // swagger:parameters deleteRouterRequest | ||||||
|  | type deleteRouterRequest struct { | ||||||
|  | 	// in: path | ||||||
|  | 	// required: true | ||||||
|  | 	Router string `uri:"router" json:"router"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // successful operation. | ||||||
|  | // swagger:response deleteRouterResponse | ||||||
|  | type deleteRouterResponse struct { | ||||||
|  | 	Data Response | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func deleteRouter(ctx *gin.Context) { | ||||||
|  | 	// swagger:route DELETE /config/routers/{router} Router deleteRouterRequest | ||||||
|  | 	// | ||||||
|  | 	// Delete router by name. | ||||||
|  | 	// | ||||||
|  | 	//     Security: | ||||||
|  | 	//       basicAuth: [] | ||||||
|  | 	// | ||||||
|  | 	//     Responses: | ||||||
|  | 	//       200: deleteRouterResponse | ||||||
|  |  | ||||||
|  | 	var req deleteRouterRequest | ||||||
|  | 	ctx.ShouldBindUri(&req) | ||||||
|  |  | ||||||
|  | 	if !registry.RouterRegistry().IsRegistered(req.Router) { | ||||||
|  | 		writeError(ctx, ErrNotFound) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	registry.RouterRegistry().Unregister(req.Router) | ||||||
|  |  | ||||||
|  | 	config.OnUpdate(func(c *config.Config) error { | ||||||
|  | 		routeres := c.Routers | ||||||
|  | 		c.Routers = nil | ||||||
|  | 		for _, s := range routeres { | ||||||
|  | 			if s.Name == req.Router { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			c.Routers = append(c.Routers, s) | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	ctx.JSON(http.StatusOK, Response{ | ||||||
|  | 		Msg: "OK", | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @ -138,6 +138,10 @@ func registerConfig(config *gin.RouterGroup) { | |||||||
| 	config.PUT("/ingresses/:ingress", updateIngress) | 	config.PUT("/ingresses/:ingress", updateIngress) | ||||||
| 	config.DELETE("/ingresses/:ingress", deleteIngress) | 	config.DELETE("/ingresses/:ingress", deleteIngress) | ||||||
|  |  | ||||||
|  | 	config.POST("/routers", createRouter) | ||||||
|  | 	config.PUT("/routers/:router", updateRouter) | ||||||
|  | 	config.DELETE("/routers/:router", deleteRouter) | ||||||
|  |  | ||||||
| 	config.POST("/limiters", createLimiter) | 	config.POST("/limiters", createLimiter) | ||||||
| 	config.PUT("/limiters/:limiter", updateLimiter) | 	config.PUT("/limiters/:limiter", updateLimiter) | ||||||
| 	config.DELETE("/limiters/:limiter", deleteLimiter) | 	config.DELETE("/limiters/:limiter", deleteLimiter) | ||||||
|  | |||||||
							
								
								
									
										289
									
								
								api/swagger.yaml
									
									
									
									
									
								
							
							
						
						
									
										289
									
								
								api/swagger.yaml
									
									
									
									
									
								
							| @ -34,6 +34,8 @@ definitions: | |||||||
|             name: |             name: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Name |                 x-go-name: Name | ||||||
|  |             plugin: | ||||||
|  |                 $ref: '#/definitions/PluginConfig' | ||||||
|             redis: |             redis: | ||||||
|                 $ref: '#/definitions/RedisLoader' |                 $ref: '#/definitions/RedisLoader' | ||||||
|             reload: |             reload: | ||||||
| @ -71,6 +73,8 @@ definitions: | |||||||
|             name: |             name: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Name |                 x-go-name: Name | ||||||
|  |             plugin: | ||||||
|  |                 $ref: '#/definitions/PluginConfig' | ||||||
|             redis: |             redis: | ||||||
|                 $ref: '#/definitions/RedisLoader' |                 $ref: '#/definitions/RedisLoader' | ||||||
|             reload: |             reload: | ||||||
| @ -91,6 +95,8 @@ definitions: | |||||||
|             name: |             name: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Name |                 x-go-name: Name | ||||||
|  |             plugin: | ||||||
|  |                 $ref: '#/definitions/PluginConfig' | ||||||
|             redis: |             redis: | ||||||
|                 $ref: '#/definitions/RedisLoader' |                 $ref: '#/definitions/RedisLoader' | ||||||
|             reload: |             reload: | ||||||
| @ -107,9 +113,6 @@ definitions: | |||||||
|     ChainConfig: |     ChainConfig: | ||||||
|         properties: |         properties: | ||||||
|             hops: |             hops: | ||||||
|                 description: |- |  | ||||||
|                     REMOVED since beta.6 |  | ||||||
|                     Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"` |  | ||||||
|                 items: |                 items: | ||||||
|                     $ref: '#/definitions/HopConfig' |                     $ref: '#/definitions/HopConfig' | ||||||
|                 type: array |                 type: array | ||||||
| @ -204,6 +207,16 @@ definitions: | |||||||
|                     $ref: '#/definitions/LimiterConfig' |                     $ref: '#/definitions/LimiterConfig' | ||||||
|                 type: array |                 type: array | ||||||
|                 x-go-name: RLimiters |                 x-go-name: RLimiters | ||||||
|  |             routers: | ||||||
|  |                 items: | ||||||
|  |                     $ref: '#/definitions/RouterConfig' | ||||||
|  |                 type: array | ||||||
|  |                 x-go-name: Routers | ||||||
|  |             sds: | ||||||
|  |                 items: | ||||||
|  |                     $ref: '#/definitions/SDConfig' | ||||||
|  |                 type: array | ||||||
|  |                 x-go-name: SDs | ||||||
|             services: |             services: | ||||||
|                 items: |                 items: | ||||||
|                     $ref: '#/definitions/ServiceConfig' |                     $ref: '#/definitions/ServiceConfig' | ||||||
| @ -273,6 +286,8 @@ definitions: | |||||||
|             addr: |             addr: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Addr |                 x-go-name: Addr | ||||||
|  |             auth: | ||||||
|  |                 $ref: '#/definitions/AuthConfig' | ||||||
|             bypass: |             bypass: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Bypass |                 x-go-name: Bypass | ||||||
| @ -284,12 +299,22 @@ definitions: | |||||||
|             host: |             host: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Host |                 x-go-name: Host | ||||||
|  |             http: | ||||||
|  |                 $ref: '#/definitions/HTTPNodeConfig' | ||||||
|             name: |             name: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Name |                 x-go-name: Name | ||||||
|  |             network: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Network | ||||||
|  |             path: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Path | ||||||
|             protocol: |             protocol: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Protocol |                 x-go-name: Protocol | ||||||
|  |             tls: | ||||||
|  |                 $ref: '#/definitions/TLSNodeConfig' | ||||||
|         type: object |         type: object | ||||||
|         x-go-package: github.com/go-gost/x/config |         x-go-package: github.com/go-gost/x/config | ||||||
|     ForwarderConfig: |     ForwarderConfig: | ||||||
| @ -304,12 +329,6 @@ definitions: | |||||||
|                 x-go-name: Nodes |                 x-go-name: Nodes | ||||||
|             selector: |             selector: | ||||||
|                 $ref: '#/definitions/SelectorConfig' |                 $ref: '#/definitions/SelectorConfig' | ||||||
|             targets: |  | ||||||
|                 description: DEPRECATED by nodes since beta.4 |  | ||||||
|                 items: |  | ||||||
|                     type: string |  | ||||||
|                 type: array |  | ||||||
|                 x-go-name: Targets |  | ||||||
|         type: object |         type: object | ||||||
|         x-go-package: github.com/go-gost/x/config |         x-go-package: github.com/go-gost/x/config | ||||||
|     HTTPLoader: |     HTTPLoader: | ||||||
| @ -321,6 +340,27 @@ definitions: | |||||||
|                 x-go-name: URL |                 x-go-name: URL | ||||||
|         type: object |         type: object | ||||||
|         x-go-package: github.com/go-gost/x/config |         x-go-package: github.com/go-gost/x/config | ||||||
|  |     HTTPNodeConfig: | ||||||
|  |         properties: | ||||||
|  |             header: | ||||||
|  |                 additionalProperties: | ||||||
|  |                     type: string | ||||||
|  |                 type: object | ||||||
|  |                 x-go-name: Header | ||||||
|  |             host: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Host | ||||||
|  |         type: object | ||||||
|  |         x-go-package: github.com/go-gost/x/config | ||||||
|  |     HTTPRecorder: | ||||||
|  |         properties: | ||||||
|  |             timeout: | ||||||
|  |                 $ref: '#/definitions/Duration' | ||||||
|  |             url: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: URL | ||||||
|  |         type: object | ||||||
|  |         x-go-package: github.com/go-gost/x/config | ||||||
|     HandlerConfig: |     HandlerConfig: | ||||||
|         properties: |         properties: | ||||||
|             auth: |             auth: | ||||||
| @ -338,9 +378,9 @@ definitions: | |||||||
|                 x-go-name: Chain |                 x-go-name: Chain | ||||||
|             chainGroup: |             chainGroup: | ||||||
|                 $ref: '#/definitions/ChainGroupConfig' |                 $ref: '#/definitions/ChainGroupConfig' | ||||||
|             ingress: |             limiter: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Ingress |                 x-go-name: Limiter | ||||||
|             metadata: |             metadata: | ||||||
|                 additionalProperties: {} |                 additionalProperties: {} | ||||||
|                 type: object |                 type: object | ||||||
| @ -366,9 +406,13 @@ definitions: | |||||||
|                     type: string |                     type: string | ||||||
|                 type: array |                 type: array | ||||||
|                 x-go-name: Bypasses |                 x-go-name: Bypasses | ||||||
|  |             file: | ||||||
|  |                 $ref: '#/definitions/FileLoader' | ||||||
|             hosts: |             hosts: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Hosts |                 x-go-name: Hosts | ||||||
|  |             http: | ||||||
|  |                 $ref: '#/definitions/HTTPLoader' | ||||||
|             interface: |             interface: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Interface |                 x-go-name: Interface | ||||||
| @ -380,6 +424,12 @@ definitions: | |||||||
|                     $ref: '#/definitions/NodeConfig' |                     $ref: '#/definitions/NodeConfig' | ||||||
|                 type: array |                 type: array | ||||||
|                 x-go-name: Nodes |                 x-go-name: Nodes | ||||||
|  |             plugin: | ||||||
|  |                 $ref: '#/definitions/PluginConfig' | ||||||
|  |             redis: | ||||||
|  |                 $ref: '#/definitions/RedisLoader' | ||||||
|  |             reload: | ||||||
|  |                 $ref: '#/definitions/Duration' | ||||||
|             resolver: |             resolver: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Resolver |                 x-go-name: Resolver | ||||||
| @ -418,6 +468,8 @@ definitions: | |||||||
|             name: |             name: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Name |                 x-go-name: Name | ||||||
|  |             plugin: | ||||||
|  |                 $ref: '#/definitions/PluginConfig' | ||||||
|             redis: |             redis: | ||||||
|                 $ref: '#/definitions/RedisLoader' |                 $ref: '#/definitions/RedisLoader' | ||||||
|             reload: |             reload: | ||||||
| @ -433,6 +485,8 @@ definitions: | |||||||
|             name: |             name: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Name |                 x-go-name: Name | ||||||
|  |             plugin: | ||||||
|  |                 $ref: '#/definitions/PluginConfig' | ||||||
|             redis: |             redis: | ||||||
|                 $ref: '#/definitions/RedisLoader' |                 $ref: '#/definitions/RedisLoader' | ||||||
|             reload: |             reload: | ||||||
| @ -468,6 +522,8 @@ definitions: | |||||||
|             name: |             name: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Name |                 x-go-name: Name | ||||||
|  |             plugin: | ||||||
|  |                 $ref: '#/definitions/PluginConfig' | ||||||
|             redis: |             redis: | ||||||
|                 $ref: '#/definitions/RedisLoader' |                 $ref: '#/definitions/RedisLoader' | ||||||
|             reload: |             reload: | ||||||
| @ -564,6 +620,11 @@ definitions: | |||||||
|             addr: |             addr: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Addr |                 x-go-name: Addr | ||||||
|  |             auth: | ||||||
|  |                 $ref: '#/definitions/AuthConfig' | ||||||
|  |             auther: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Auther | ||||||
|             path: |             path: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Path |                 x-go-name: Path | ||||||
| @ -574,6 +635,9 @@ definitions: | |||||||
|             addr: |             addr: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Addr |                 x-go-name: Addr | ||||||
|  |             async: | ||||||
|  |                 type: boolean | ||||||
|  |                 x-go-name: Async | ||||||
|             chain: |             chain: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Chain |                 x-go-name: Chain | ||||||
| @ -583,6 +647,9 @@ definitions: | |||||||
|             hostname: |             hostname: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Hostname |                 x-go-name: Hostname | ||||||
|  |             only: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Only | ||||||
|             prefer: |             prefer: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Prefer |                 x-go-name: Prefer | ||||||
| @ -597,6 +664,8 @@ definitions: | |||||||
|             addr: |             addr: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Addr |                 x-go-name: Addr | ||||||
|  |             auth: | ||||||
|  |                 $ref: '#/definitions/AuthConfig' | ||||||
|             bypass: |             bypass: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Bypass |                 x-go-name: Bypass | ||||||
| @ -615,6 +684,8 @@ definitions: | |||||||
|             hosts: |             hosts: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Hosts |                 x-go-name: Hosts | ||||||
|  |             http: | ||||||
|  |                 $ref: '#/definitions/HTTPNodeConfig' | ||||||
|             interface: |             interface: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Interface |                 x-go-name: Interface | ||||||
| @ -625,6 +696,12 @@ definitions: | |||||||
|             name: |             name: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Name |                 x-go-name: Name | ||||||
|  |             network: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Network | ||||||
|  |             path: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Path | ||||||
|             protocol: |             protocol: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Protocol |                 x-go-name: Protocol | ||||||
| @ -633,6 +710,25 @@ definitions: | |||||||
|                 x-go-name: Resolver |                 x-go-name: Resolver | ||||||
|             sockopts: |             sockopts: | ||||||
|                 $ref: '#/definitions/SockOptsConfig' |                 $ref: '#/definitions/SockOptsConfig' | ||||||
|  |             tls: | ||||||
|  |                 $ref: '#/definitions/TLSNodeConfig' | ||||||
|  |         type: object | ||||||
|  |         x-go-package: github.com/go-gost/x/config | ||||||
|  |     PluginConfig: | ||||||
|  |         properties: | ||||||
|  |             addr: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Addr | ||||||
|  |             timeout: | ||||||
|  |                 $ref: '#/definitions/Duration' | ||||||
|  |             tls: | ||||||
|  |                 $ref: '#/definitions/TLSConfig' | ||||||
|  |             token: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Token | ||||||
|  |             type: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Type | ||||||
|         type: object |         type: object | ||||||
|         x-go-package: github.com/go-gost/x/config |         x-go-package: github.com/go-gost/x/config | ||||||
|     ProfilingConfig: |     ProfilingConfig: | ||||||
| @ -646,15 +742,24 @@ definitions: | |||||||
|         properties: |         properties: | ||||||
|             file: |             file: | ||||||
|                 $ref: '#/definitions/FileRecorder' |                 $ref: '#/definitions/FileRecorder' | ||||||
|  |             http: | ||||||
|  |                 $ref: '#/definitions/HTTPRecorder' | ||||||
|             name: |             name: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Name |                 x-go-name: Name | ||||||
|  |             plugin: | ||||||
|  |                 $ref: '#/definitions/PluginConfig' | ||||||
|             redis: |             redis: | ||||||
|                 $ref: '#/definitions/RedisRecorder' |                 $ref: '#/definitions/RedisRecorder' | ||||||
|  |             tcp: | ||||||
|  |                 $ref: '#/definitions/TCPRecorder' | ||||||
|         type: object |         type: object | ||||||
|         x-go-package: github.com/go-gost/x/config |         x-go-package: github.com/go-gost/x/config | ||||||
|     RecorderObject: |     RecorderObject: | ||||||
|         properties: |         properties: | ||||||
|  |             Metadata: | ||||||
|  |                 additionalProperties: {} | ||||||
|  |                 type: object | ||||||
|             name: |             name: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Name |                 x-go-name: Name | ||||||
| @ -713,6 +818,8 @@ definitions: | |||||||
|                     $ref: '#/definitions/NameserverConfig' |                     $ref: '#/definitions/NameserverConfig' | ||||||
|                 type: array |                 type: array | ||||||
|                 x-go-name: Nameservers |                 x-go-name: Nameservers | ||||||
|  |             plugin: | ||||||
|  |                 $ref: '#/definitions/PluginConfig' | ||||||
|         type: object |         type: object | ||||||
|         x-go-package: github.com/go-gost/x/config |         x-go-package: github.com/go-gost/x/config | ||||||
|     Response: |     Response: | ||||||
| @ -726,6 +833,47 @@ definitions: | |||||||
|                 x-go-name: Msg |                 x-go-name: Msg | ||||||
|         type: object |         type: object | ||||||
|         x-go-package: github.com/go-gost/x/api |         x-go-package: github.com/go-gost/x/api | ||||||
|  |     RouterConfig: | ||||||
|  |         properties: | ||||||
|  |             file: | ||||||
|  |                 $ref: '#/definitions/FileLoader' | ||||||
|  |             http: | ||||||
|  |                 $ref: '#/definitions/HTTPLoader' | ||||||
|  |             name: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Name | ||||||
|  |             plugin: | ||||||
|  |                 $ref: '#/definitions/PluginConfig' | ||||||
|  |             redis: | ||||||
|  |                 $ref: '#/definitions/RedisLoader' | ||||||
|  |             reload: | ||||||
|  |                 $ref: '#/definitions/Duration' | ||||||
|  |             routes: | ||||||
|  |                 items: | ||||||
|  |                     $ref: '#/definitions/RouterRouteConfig' | ||||||
|  |                 type: array | ||||||
|  |                 x-go-name: Routes | ||||||
|  |         type: object | ||||||
|  |         x-go-package: github.com/go-gost/x/config | ||||||
|  |     RouterRouteConfig: | ||||||
|  |         properties: | ||||||
|  |             gateway: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Gateway | ||||||
|  |             net: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Net | ||||||
|  |         type: object | ||||||
|  |         x-go-package: github.com/go-gost/x/config | ||||||
|  |     SDConfig: | ||||||
|  |         properties: | ||||||
|  |             name: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Name | ||||||
|  |             plugin: | ||||||
|  |                 $ref: '#/definitions/PluginConfig' | ||||||
|  |         type: object | ||||||
|  |         x-go-package: github.com/go-gost/x/config | ||||||
|     SelectorConfig: |     SelectorConfig: | ||||||
|         properties: |         properties: | ||||||
|             failTimeout: |             failTimeout: | ||||||
| @ -809,6 +957,15 @@ definitions: | |||||||
|                 x-go-name: Mark |                 x-go-name: Mark | ||||||
|         type: object |         type: object | ||||||
|         x-go-package: github.com/go-gost/x/config |         x-go-package: github.com/go-gost/x/config | ||||||
|  |     TCPRecorder: | ||||||
|  |         properties: | ||||||
|  |             addr: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: Addr | ||||||
|  |             timeout: | ||||||
|  |                 $ref: '#/definitions/Duration' | ||||||
|  |         type: object | ||||||
|  |         x-go-package: github.com/go-gost/x/config | ||||||
|     TLSConfig: |     TLSConfig: | ||||||
|         properties: |         properties: | ||||||
|             caFile: |             caFile: | ||||||
| @ -823,6 +980,8 @@ definitions: | |||||||
|             keyFile: |             keyFile: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: KeyFile |                 x-go-name: KeyFile | ||||||
|  |             options: | ||||||
|  |                 $ref: '#/definitions/TLSOptions' | ||||||
|             organization: |             organization: | ||||||
|                 type: string |                 type: string | ||||||
|                 x-go-name: Organization |                 x-go-name: Organization | ||||||
| @ -836,6 +995,33 @@ definitions: | |||||||
|                 $ref: '#/definitions/Duration' |                 $ref: '#/definitions/Duration' | ||||||
|         type: object |         type: object | ||||||
|         x-go-package: github.com/go-gost/x/config |         x-go-package: github.com/go-gost/x/config | ||||||
|  |     TLSNodeConfig: | ||||||
|  |         properties: | ||||||
|  |             options: | ||||||
|  |                 $ref: '#/definitions/TLSOptions' | ||||||
|  |             secure: | ||||||
|  |                 type: boolean | ||||||
|  |                 x-go-name: Secure | ||||||
|  |             serverName: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: ServerName | ||||||
|  |         type: object | ||||||
|  |         x-go-package: github.com/go-gost/x/config | ||||||
|  |     TLSOptions: | ||||||
|  |         properties: | ||||||
|  |             cipherSuites: | ||||||
|  |                 items: | ||||||
|  |                     type: string | ||||||
|  |                 type: array | ||||||
|  |                 x-go-name: CipherSuites | ||||||
|  |             maxVersion: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: MaxVersion | ||||||
|  |             minVersion: | ||||||
|  |                 type: string | ||||||
|  |                 x-go-name: MinVersion | ||||||
|  |         type: object | ||||||
|  |         x-go-package: github.com/go-gost/x/config | ||||||
| info: | info: | ||||||
|     title: Documentation of Web API. |     title: Documentation of Web API. | ||||||
|     version: 1.0.0 |     version: 1.0.0 | ||||||
| @ -866,6 +1052,11 @@ paths: | |||||||
|                   name: format |                   name: format | ||||||
|                   type: string |                   type: string | ||||||
|                   x-go-name: Format |                   x-go-name: Format | ||||||
|  |                 - description: file path, default is gost.yaml|gost.json in current working directory. | ||||||
|  |                   in: query | ||||||
|  |                   name: path | ||||||
|  |                   type: string | ||||||
|  |                   x-go-name: Path | ||||||
|             responses: |             responses: | ||||||
|                 "200": |                 "200": | ||||||
|                     $ref: '#/responses/saveConfigResponse' |                     $ref: '#/responses/saveConfigResponse' | ||||||
| @ -1513,6 +1704,64 @@ paths: | |||||||
|             summary: Update rate limiter by name, the limiter must already exist. |             summary: Update rate limiter by name, the limiter must already exist. | ||||||
|             tags: |             tags: | ||||||
|                 - Limiter |                 - Limiter | ||||||
|  |     /config/routers: | ||||||
|  |         post: | ||||||
|  |             operationId: createRouterRequest | ||||||
|  |             parameters: | ||||||
|  |                 - in: body | ||||||
|  |                   name: data | ||||||
|  |                   schema: | ||||||
|  |                     $ref: '#/definitions/RouterConfig' | ||||||
|  |                   x-go-name: Data | ||||||
|  |             responses: | ||||||
|  |                 "200": | ||||||
|  |                     $ref: '#/responses/createRouterResponse' | ||||||
|  |             security: | ||||||
|  |                 - basicAuth: | ||||||
|  |                     - '[]' | ||||||
|  |             summary: Create a new router, the name of the router must be unique in router list. | ||||||
|  |             tags: | ||||||
|  |                 - Router | ||||||
|  |     /config/routers/{router}: | ||||||
|  |         delete: | ||||||
|  |             operationId: deleteRouterRequest | ||||||
|  |             parameters: | ||||||
|  |                 - in: path | ||||||
|  |                   name: router | ||||||
|  |                   required: true | ||||||
|  |                   type: string | ||||||
|  |                   x-go-name: Router | ||||||
|  |             responses: | ||||||
|  |                 "200": | ||||||
|  |                     $ref: '#/responses/deleteRouterResponse' | ||||||
|  |             security: | ||||||
|  |                 - basicAuth: | ||||||
|  |                     - '[]' | ||||||
|  |             summary: Delete router by name. | ||||||
|  |             tags: | ||||||
|  |                 - Router | ||||||
|  |         put: | ||||||
|  |             operationId: updateRouterRequest | ||||||
|  |             parameters: | ||||||
|  |                 - in: path | ||||||
|  |                   name: router | ||||||
|  |                   required: true | ||||||
|  |                   type: string | ||||||
|  |                   x-go-name: Router | ||||||
|  |                 - in: body | ||||||
|  |                   name: data | ||||||
|  |                   schema: | ||||||
|  |                     $ref: '#/definitions/RouterConfig' | ||||||
|  |                   x-go-name: Data | ||||||
|  |             responses: | ||||||
|  |                 "200": | ||||||
|  |                     $ref: '#/responses/updateRouterResponse' | ||||||
|  |             security: | ||||||
|  |                 - basicAuth: | ||||||
|  |                     - '[]' | ||||||
|  |             summary: Update router by name, the router must already exist. | ||||||
|  |             tags: | ||||||
|  |                 - Router | ||||||
|     /config/services: |     /config/services: | ||||||
|         post: |         post: | ||||||
|             operationId: createServiceRequest |             operationId: createServiceRequest | ||||||
| @ -1640,6 +1889,12 @@ responses: | |||||||
|             Data: {} |             Data: {} | ||||||
|         schema: |         schema: | ||||||
|             $ref: '#/definitions/Response' |             $ref: '#/definitions/Response' | ||||||
|  |     createRouterResponse: | ||||||
|  |         description: successful operation. | ||||||
|  |         headers: | ||||||
|  |             Data: {} | ||||||
|  |         schema: | ||||||
|  |             $ref: '#/definitions/Response' | ||||||
|     createServiceResponse: |     createServiceResponse: | ||||||
|         description: successful operation. |         description: successful operation. | ||||||
|         headers: |         headers: | ||||||
| @ -1712,6 +1967,12 @@ responses: | |||||||
|             Data: {} |             Data: {} | ||||||
|         schema: |         schema: | ||||||
|             $ref: '#/definitions/Response' |             $ref: '#/definitions/Response' | ||||||
|  |     deleteRouterResponse: | ||||||
|  |         description: successful operation. | ||||||
|  |         headers: | ||||||
|  |             Data: {} | ||||||
|  |         schema: | ||||||
|  |             $ref: '#/definitions/Response' | ||||||
|     deleteServiceResponse: |     deleteServiceResponse: | ||||||
|         description: successful operation. |         description: successful operation. | ||||||
|         headers: |         headers: | ||||||
| @ -1796,6 +2057,12 @@ responses: | |||||||
|             Data: {} |             Data: {} | ||||||
|         schema: |         schema: | ||||||
|             $ref: '#/definitions/Response' |             $ref: '#/definitions/Response' | ||||||
|  |     updateRouterResponse: | ||||||
|  |         description: successful operation. | ||||||
|  |         headers: | ||||||
|  |             Data: {} | ||||||
|  |         schema: | ||||||
|  |             $ref: '#/definitions/Response' | ||||||
|     updateServiceResponse: |     updateServiceResponse: | ||||||
|         description: successful operation. |         description: successful operation. | ||||||
|         headers: |         headers: | ||||||
|  | |||||||
| @ -10,8 +10,8 @@ import ( | |||||||
| 	"github.com/go-gost/core/auth" | 	"github.com/go-gost/core/auth" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/plugin/auth/proto" | 	"github.com/go-gost/plugin/auth/proto" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	"github.com/go-gost/x/internal/plugin" | 	"github.com/go-gost/x/internal/plugin" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" |  | ||||||
| 	"google.golang.org/grpc" | 	"google.golang.org/grpc" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -58,7 +58,7 @@ func (p *grpcPlugin) Authenticate(ctx context.Context, user, password string, op | |||||||
| 		&proto.AuthenticateRequest{ | 		&proto.AuthenticateRequest{ | ||||||
| 			Username: user, | 			Username: user, | ||||||
| 			Password: password, | 			Password: password, | ||||||
| 			Client:   string(auth_util.ClientAddrFromContext(ctx)), | 			Client:   string(ctxvalue.ClientAddrFromContext(ctx)), | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		p.log.Error(err) | 		p.log.Error(err) | ||||||
| @ -118,7 +118,7 @@ func (p *httpPlugin) Authenticate(ctx context.Context, user, password string, op | |||||||
| 	rb := httpPluginRequest{ | 	rb := httpPluginRequest{ | ||||||
| 		Username: user, | 		Username: user, | ||||||
| 		Password: password, | 		Password: password, | ||||||
| 		Client:   string(auth_util.ClientAddrFromContext(ctx)), | 		Client:   string(ctxvalue.ClientAddrFromContext(ctx)), | ||||||
| 	} | 	} | ||||||
| 	v, err := json.Marshal(&rb) | 	v, err := json.Marshal(&rb) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | |||||||
| @ -10,8 +10,8 @@ import ( | |||||||
| 	"github.com/go-gost/core/bypass" | 	"github.com/go-gost/core/bypass" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/plugin/bypass/proto" | 	"github.com/go-gost/plugin/bypass/proto" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	"github.com/go-gost/x/internal/plugin" | 	"github.com/go-gost/x/internal/plugin" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" |  | ||||||
| 	"google.golang.org/grpc" | 	"google.golang.org/grpc" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -61,7 +61,7 @@ func (p *grpcPlugin) Contains(ctx context.Context, network, addr string, opts .. | |||||||
| 		&proto.BypassRequest{ | 		&proto.BypassRequest{ | ||||||
| 			Network: network, | 			Network: network, | ||||||
| 			Addr:    addr, | 			Addr:    addr, | ||||||
| 			Client:  string(auth_util.IDFromContext(ctx)), | 			Client:  string(ctxvalue.ClientIDFromContext(ctx)), | ||||||
| 			Host:    options.Host, | 			Host:    options.Host, | ||||||
| 			Path:    options.Path, | 			Path:    options.Path, | ||||||
| 		}) | 		}) | ||||||
| @ -129,7 +129,7 @@ func (p *httpPlugin) Contains(ctx context.Context, network, addr string, opts .. | |||||||
| 	rb := httpPluginRequest{ | 	rb := httpPluginRequest{ | ||||||
| 		Network: network, | 		Network: network, | ||||||
| 		Addr:    addr, | 		Addr:    addr, | ||||||
| 		Client:  string(auth_util.IDFromContext(ctx)), | 		Client:  string(ctxvalue.ClientIDFromContext(ctx)), | ||||||
| 		Host:    options.Host, | 		Host:    options.Host, | ||||||
| 		Path:    options.Path, | 		Path:    options.Path, | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -79,6 +79,11 @@ type LogRotationConfig struct { | |||||||
| 	Compress bool `yaml:"compress,omitempty" json:"compress,omitempty"` | 	Compress bool `yaml:"compress,omitempty" json:"compress,omitempty"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type LoggerConfig struct { | ||||||
|  | 	Name string     `json:"name"` | ||||||
|  | 	Log  *LogConfig `yaml:",omitempty" json:"log,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
| type ProfilingConfig struct { | type ProfilingConfig struct { | ||||||
| 	Addr string `json:"addr"` | 	Addr string `json:"addr"` | ||||||
| } | } | ||||||
| @ -244,6 +249,21 @@ type SDConfig struct { | |||||||
| 	Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"` | 	Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type RouterRouteConfig struct { | ||||||
|  | 	Net     string `json:"net"` | ||||||
|  | 	Gateway string `json:"gateway"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type RouterConfig struct { | ||||||
|  | 	Name   string               `json:"name"` | ||||||
|  | 	Routes []*RouterRouteConfig `yaml:",omitempty" json:"routes,omitempty"` | ||||||
|  | 	Reload time.Duration        `yaml:",omitempty" json:"reload,omitempty"` | ||||||
|  | 	File   *FileLoader          `yaml:",omitempty" json:"file,omitempty"` | ||||||
|  | 	Redis  *RedisLoader         `yaml:",omitempty" json:"redis,omitempty"` | ||||||
|  | 	HTTP   *HTTPLoader          `yaml:"http,omitempty" json:"http,omitempty"` | ||||||
|  | 	Plugin *PluginConfig        `yaml:",omitempty" json:"plugin,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
| type RecorderConfig struct { | type RecorderConfig struct { | ||||||
| 	Name   string         `json:"name"` | 	Name   string         `json:"name"` | ||||||
| 	File   *FileRecorder  `yaml:",omitempty" json:"file,omitempty"` | 	File   *FileRecorder  `yaml:",omitempty" json:"file,omitempty"` | ||||||
| @ -289,6 +309,7 @@ type LimiterConfig struct { | |||||||
| 	File   *FileLoader   `yaml:",omitempty" json:"file,omitempty"` | 	File   *FileLoader   `yaml:",omitempty" json:"file,omitempty"` | ||||||
| 	Redis  *RedisLoader  `yaml:",omitempty" json:"redis,omitempty"` | 	Redis  *RedisLoader  `yaml:",omitempty" json:"redis,omitempty"` | ||||||
| 	HTTP   *HTTPLoader   `yaml:"http,omitempty" json:"http,omitempty"` | 	HTTP   *HTTPLoader   `yaml:"http,omitempty" json:"http,omitempty"` | ||||||
|  | 	Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type ListenerConfig struct { | type ListenerConfig struct { | ||||||
| @ -311,7 +332,7 @@ type HandlerConfig struct { | |||||||
| 	Authers    []string          `yaml:",omitempty" json:"authers,omitempty"` | 	Authers    []string          `yaml:",omitempty" json:"authers,omitempty"` | ||||||
| 	Auth       *AuthConfig       `yaml:",omitempty" json:"auth,omitempty"` | 	Auth       *AuthConfig       `yaml:",omitempty" json:"auth,omitempty"` | ||||||
| 	TLS        *TLSConfig        `yaml:",omitempty" json:"tls,omitempty"` | 	TLS        *TLSConfig        `yaml:",omitempty" json:"tls,omitempty"` | ||||||
| 	Ingress    string            `yaml:",omitempty" json:"ingress,omitempty"` | 	Limiter    string            `yaml:",omitempty" json:"limiter,omitempty"` | ||||||
| 	Metadata   map[string]any    `yaml:",omitempty" json:"metadata,omitempty"` | 	Metadata   map[string]any    `yaml:",omitempty" json:"metadata,omitempty"` | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -380,6 +401,7 @@ type ServiceConfig struct { | |||||||
| 	Limiter    string            `yaml:",omitempty" json:"limiter,omitempty"` | 	Limiter    string            `yaml:",omitempty" json:"limiter,omitempty"` | ||||||
| 	CLimiter   string            `yaml:"climiter,omitempty" json:"climiter,omitempty"` | 	CLimiter   string            `yaml:"climiter,omitempty" json:"climiter,omitempty"` | ||||||
| 	RLimiter   string            `yaml:"rlimiter,omitempty" json:"rlimiter,omitempty"` | 	RLimiter   string            `yaml:"rlimiter,omitempty" json:"rlimiter,omitempty"` | ||||||
|  | 	Logger     string            `yaml:",omitempty" json:"logger,omitempty"` | ||||||
| 	Recorders  []*RecorderObject `yaml:",omitempty" json:"recorders,omitempty"` | 	Recorders  []*RecorderObject `yaml:",omitempty" json:"recorders,omitempty"` | ||||||
| 	Handler    *HandlerConfig    `yaml:",omitempty" json:"handler,omitempty"` | 	Handler    *HandlerConfig    `yaml:",omitempty" json:"handler,omitempty"` | ||||||
| 	Listener   *ListenerConfig   `yaml:",omitempty" json:"listener,omitempty"` | 	Listener   *ListenerConfig   `yaml:",omitempty" json:"listener,omitempty"` | ||||||
| @ -446,11 +468,13 @@ type Config struct { | |||||||
| 	Resolvers  []*ResolverConfig  `yaml:",omitempty" json:"resolvers,omitempty"` | 	Resolvers  []*ResolverConfig  `yaml:",omitempty" json:"resolvers,omitempty"` | ||||||
| 	Hosts      []*HostsConfig     `yaml:",omitempty" json:"hosts,omitempty"` | 	Hosts      []*HostsConfig     `yaml:",omitempty" json:"hosts,omitempty"` | ||||||
| 	Ingresses  []*IngressConfig   `yaml:",omitempty" json:"ingresses,omitempty"` | 	Ingresses  []*IngressConfig   `yaml:",omitempty" json:"ingresses,omitempty"` | ||||||
|  | 	Routers    []*RouterConfig    `yaml:",omitempty" json:"routers,omitempty"` | ||||||
| 	SDs        []*SDConfig        `yaml:"sds,omitempty" json:"sds,omitempty"` | 	SDs        []*SDConfig        `yaml:"sds,omitempty" json:"sds,omitempty"` | ||||||
| 	Recorders  []*RecorderConfig  `yaml:",omitempty" json:"recorders,omitempty"` | 	Recorders  []*RecorderConfig  `yaml:",omitempty" json:"recorders,omitempty"` | ||||||
| 	Limiters   []*LimiterConfig   `yaml:",omitempty" json:"limiters,omitempty"` | 	Limiters   []*LimiterConfig   `yaml:",omitempty" json:"limiters,omitempty"` | ||||||
| 	CLimiters  []*LimiterConfig   `yaml:"climiters,omitempty" json:"climiters,omitempty"` | 	CLimiters  []*LimiterConfig   `yaml:"climiters,omitempty" json:"climiters,omitempty"` | ||||||
| 	RLimiters  []*LimiterConfig   `yaml:"rlimiters,omitempty" json:"rlimiters,omitempty"` | 	RLimiters  []*LimiterConfig   `yaml:"rlimiters,omitempty" json:"rlimiters,omitempty"` | ||||||
|  | 	Loggers    []*LoggerConfig    `yaml:",omitempty" json:"loggers,omitempty"` | ||||||
| 	TLS        *TLSConfig         `yaml:",omitempty" json:"tls,omitempty"` | 	TLS        *TLSConfig         `yaml:",omitempty" json:"tls,omitempty"` | ||||||
| 	Log        *LogConfig         `yaml:",omitempty" json:"log,omitempty"` | 	Log        *LogConfig         `yaml:",omitempty" json:"log,omitempty"` | ||||||
| 	Profiling  *ProfilingConfig   `yaml:",omitempty" json:"profiling,omitempty"` | 	Profiling  *ProfilingConfig   `yaml:",omitempty" json:"profiling,omitempty"` | ||||||
|  | |||||||
| @ -41,13 +41,13 @@ func ParseIngress(cfg *config.IngressConfig) ingress.Ingress { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var rules []xingress.Rule | 	var rules []*ingress.Rule | ||||||
| 	for _, rule := range cfg.Rules { | 	for _, rule := range cfg.Rules { | ||||||
| 		if rule.Hostname == "" || rule.Endpoint == "" { | 		if rule.Hostname == "" || rule.Endpoint == "" { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		rules = append(rules, xingress.Rule{ | 		rules = append(rules, &ingress.Rule{ | ||||||
| 			Hostname: rule.Hostname, | 			Hostname: rule.Hostname, | ||||||
| 			Endpoint: rule.Endpoint, | 			Endpoint: rule.Endpoint, | ||||||
| 		}) | 		}) | ||||||
|  | |||||||
| @ -1,12 +1,16 @@ | |||||||
| package limiter | package limiter | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/go-gost/core/limiter/conn" | 	"github.com/go-gost/core/limiter/conn" | ||||||
| 	"github.com/go-gost/core/limiter/rate" | 	"github.com/go-gost/core/limiter/rate" | ||||||
| 	"github.com/go-gost/core/limiter/traffic" | 	"github.com/go-gost/core/limiter/traffic" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/x/config" | 	"github.com/go-gost/x/config" | ||||||
| 	"github.com/go-gost/x/internal/loader" | 	"github.com/go-gost/x/internal/loader" | ||||||
|  | 	"github.com/go-gost/x/internal/plugin" | ||||||
| 	xconn "github.com/go-gost/x/limiter/conn" | 	xconn "github.com/go-gost/x/limiter/conn" | ||||||
| 	xrate "github.com/go-gost/x/limiter/rate" | 	xrate "github.com/go-gost/x/limiter/rate" | ||||||
| 	xtraffic "github.com/go-gost/x/limiter/traffic" | 	xtraffic "github.com/go-gost/x/limiter/traffic" | ||||||
| @ -17,6 +21,30 @@ func ParseTrafficLimiter(cfg *config.LimiterConfig) (lim traffic.TrafficLimiter) | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if cfg.Plugin != nil { | ||||||
|  | 		var tlsCfg *tls.Config | ||||||
|  | 		if cfg.Plugin.TLS != nil { | ||||||
|  | 			tlsCfg = &tls.Config{ | ||||||
|  | 				ServerName:         cfg.Plugin.TLS.ServerName, | ||||||
|  | 				InsecureSkipVerify: !cfg.Plugin.TLS.Secure, | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		switch strings.ToLower(cfg.Plugin.Type) { | ||||||
|  | 		case "http": | ||||||
|  | 			return xtraffic.NewHTTPPlugin( | ||||||
|  | 				cfg.Name, cfg.Plugin.Addr, | ||||||
|  | 				plugin.TLSConfigOption(tlsCfg), | ||||||
|  | 				plugin.TimeoutOption(cfg.Plugin.Timeout), | ||||||
|  | 			) | ||||||
|  | 		default: | ||||||
|  | 			return xtraffic.NewGRPCPlugin( | ||||||
|  | 				cfg.Name, cfg.Plugin.Addr, | ||||||
|  | 				plugin.TokenOption(cfg.Plugin.Token), | ||||||
|  | 				plugin.TLSConfigOption(tlsCfg), | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	var opts []xtraffic.Option | 	var opts []xtraffic.Option | ||||||
|  |  | ||||||
| 	if cfg.File != nil && cfg.File.Path != "" { | 	if cfg.File != nil && cfg.File.Path != "" { | ||||||
|  | |||||||
							
								
								
									
										55
									
								
								config/parsing/logger/parse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								config/parsing/logger/parse.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | package logger | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/logger" | ||||||
|  | 	"github.com/go-gost/x/config" | ||||||
|  | 	xlogger "github.com/go-gost/x/logger" | ||||||
|  | 	"gopkg.in/natefinch/lumberjack.v2" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func ParseLogger(cfg *config.LoggerConfig) logger.Logger { | ||||||
|  | 	if cfg == nil || cfg.Log == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	opts := []xlogger.Option{ | ||||||
|  | 		xlogger.NameOption(cfg.Name), | ||||||
|  | 		xlogger.FormatOption(logger.LogFormat(cfg.Log.Format)), | ||||||
|  | 		xlogger.LevelOption(logger.LogLevel(cfg.Log.Level)), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var out io.Writer = os.Stderr | ||||||
|  | 	switch cfg.Log.Output { | ||||||
|  | 	case "none", "null": | ||||||
|  | 		return xlogger.Nop() | ||||||
|  | 	case "stdout": | ||||||
|  | 		out = os.Stdout | ||||||
|  | 	case "stderr", "": | ||||||
|  | 		out = os.Stderr | ||||||
|  | 	default: | ||||||
|  | 		if cfg.Log.Rotation != nil { | ||||||
|  | 			out = &lumberjack.Logger{ | ||||||
|  | 				Filename:   cfg.Log.Output, | ||||||
|  | 				MaxSize:    cfg.Log.Rotation.MaxSize, | ||||||
|  | 				MaxAge:     cfg.Log.Rotation.MaxAge, | ||||||
|  | 				MaxBackups: cfg.Log.Rotation.MaxBackups, | ||||||
|  | 				LocalTime:  cfg.Log.Rotation.LocalTime, | ||||||
|  | 				Compress:   cfg.Log.Rotation.Compress, | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			os.MkdirAll(filepath.Dir(cfg.Log.Output), 0755) | ||||||
|  | 			f, err := os.OpenFile(cfg.Log.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) | ||||||
|  | 			if err != nil { | ||||||
|  | 				logger.Default().Warn(err) | ||||||
|  | 			} else { | ||||||
|  | 				out = f | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	opts = append(opts, xlogger.OutputOption(out)) | ||||||
|  |  | ||||||
|  | 	return xlogger.NewLogger(opts...) | ||||||
|  | } | ||||||
							
								
								
									
										104
									
								
								config/parsing/router/parse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								config/parsing/router/parse.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,104 @@ | |||||||
|  | package router | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"net" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/logger" | ||||||
|  | 	"github.com/go-gost/core/router" | ||||||
|  | 	"github.com/go-gost/x/config" | ||||||
|  | 	"github.com/go-gost/x/internal/loader" | ||||||
|  | 	"github.com/go-gost/x/internal/plugin" | ||||||
|  | 	xrouter "github.com/go-gost/x/router" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func ParseRouter(cfg *config.RouterConfig) router.Router { | ||||||
|  | 	if cfg == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if cfg.Plugin != nil { | ||||||
|  | 		var tlsCfg *tls.Config | ||||||
|  | 		if cfg.Plugin.TLS != nil { | ||||||
|  | 			tlsCfg = &tls.Config{ | ||||||
|  | 				ServerName:         cfg.Plugin.TLS.ServerName, | ||||||
|  | 				InsecureSkipVerify: !cfg.Plugin.TLS.Secure, | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		switch strings.ToLower(cfg.Plugin.Type) { | ||||||
|  | 		case "http": | ||||||
|  | 			return xrouter.NewHTTPPlugin( | ||||||
|  | 				cfg.Name, cfg.Plugin.Addr, | ||||||
|  | 				plugin.TLSConfigOption(tlsCfg), | ||||||
|  | 				plugin.TimeoutOption(cfg.Plugin.Timeout), | ||||||
|  | 			) | ||||||
|  | 		default: | ||||||
|  | 			return xrouter.NewGRPCPlugin( | ||||||
|  | 				cfg.Name, cfg.Plugin.Addr, | ||||||
|  | 				plugin.TokenOption(cfg.Plugin.Token), | ||||||
|  | 				plugin.TLSConfigOption(tlsCfg), | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var routes []*router.Route | ||||||
|  | 	for _, route := range cfg.Routes { | ||||||
|  | 		_, ipNet, _ := net.ParseCIDR(route.Net) | ||||||
|  | 		if ipNet == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		gw := net.ParseIP(route.Gateway) | ||||||
|  | 		if gw == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		routes = append(routes, &router.Route{ | ||||||
|  | 			Net:     ipNet, | ||||||
|  | 			Gateway: gw, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	opts := []xrouter.Option{ | ||||||
|  | 		xrouter.RoutesOption(routes), | ||||||
|  | 		xrouter.ReloadPeriodOption(cfg.Reload), | ||||||
|  | 		xrouter.LoggerOption(logger.Default().WithFields(map[string]any{ | ||||||
|  | 			"kind":   "router", | ||||||
|  | 			"router": cfg.Name, | ||||||
|  | 		})), | ||||||
|  | 	} | ||||||
|  | 	if cfg.File != nil && cfg.File.Path != "" { | ||||||
|  | 		opts = append(opts, xrouter.FileLoaderOption(loader.FileLoader(cfg.File.Path))) | ||||||
|  | 	} | ||||||
|  | 	if cfg.Redis != nil && cfg.Redis.Addr != "" { | ||||||
|  | 		switch cfg.Redis.Type { | ||||||
|  | 		case "list": // rediss list | ||||||
|  | 			opts = append(opts, xrouter.RedisLoaderOption(loader.RedisListLoader( | ||||||
|  | 				cfg.Redis.Addr, | ||||||
|  | 				loader.DBRedisLoaderOption(cfg.Redis.DB), | ||||||
|  | 				loader.PasswordRedisLoaderOption(cfg.Redis.Password), | ||||||
|  | 				loader.KeyRedisLoaderOption(cfg.Redis.Key), | ||||||
|  | 			))) | ||||||
|  | 		case "set": // redis set | ||||||
|  | 			opts = append(opts, xrouter.RedisLoaderOption(loader.RedisSetLoader( | ||||||
|  | 				cfg.Redis.Addr, | ||||||
|  | 				loader.DBRedisLoaderOption(cfg.Redis.DB), | ||||||
|  | 				loader.PasswordRedisLoaderOption(cfg.Redis.Password), | ||||||
|  | 				loader.KeyRedisLoaderOption(cfg.Redis.Key), | ||||||
|  | 			))) | ||||||
|  | 		default: // redis hash | ||||||
|  | 			opts = append(opts, xrouter.RedisLoaderOption(loader.RedisHashLoader( | ||||||
|  | 				cfg.Redis.Addr, | ||||||
|  | 				loader.DBRedisLoaderOption(cfg.Redis.DB), | ||||||
|  | 				loader.PasswordRedisLoaderOption(cfg.Redis.Password), | ||||||
|  | 				loader.KeyRedisLoaderOption(cfg.Redis.Key), | ||||||
|  | 			))) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if cfg.HTTP != nil && cfg.HTTP.URL != "" { | ||||||
|  | 		opts = append(opts, xrouter.HTTPLoaderOption(loader.HTTPLoader( | ||||||
|  | 			cfg.HTTP.URL, | ||||||
|  | 			loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout), | ||||||
|  | 		))) | ||||||
|  | 	} | ||||||
|  | 	return xrouter.NewRouter(opts...) | ||||||
|  | } | ||||||
| @ -23,6 +23,7 @@ import ( | |||||||
| 	bypass_parser "github.com/go-gost/x/config/parsing/bypass" | 	bypass_parser "github.com/go-gost/x/config/parsing/bypass" | ||||||
| 	hop_parser "github.com/go-gost/x/config/parsing/hop" | 	hop_parser "github.com/go-gost/x/config/parsing/hop" | ||||||
| 	selector_parser "github.com/go-gost/x/config/parsing/selector" | 	selector_parser "github.com/go-gost/x/config/parsing/selector" | ||||||
|  | 	xnet "github.com/go-gost/x/internal/net" | ||||||
| 	tls_util "github.com/go-gost/x/internal/util/tls" | 	tls_util "github.com/go-gost/x/internal/util/tls" | ||||||
| 	"github.com/go-gost/x/metadata" | 	"github.com/go-gost/x/metadata" | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| @ -40,7 +41,12 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { | |||||||
| 			Type: "auto", | 			Type: "auto", | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	serviceLogger := logger.Default().WithFields(map[string]any{ |  | ||||||
|  | 	log := registry.LoggerRegistry().Get(cfg.Logger) | ||||||
|  | 	if log == nil { | ||||||
|  | 		log = logger.Default() | ||||||
|  | 	} | ||||||
|  | 	serviceLogger := log.WithFields(map[string]any{ | ||||||
| 		"kind":     "service", | 		"kind":     "service", | ||||||
| 		"service":  cfg.Name, | 		"service":  cfg.Name, | ||||||
| 		"listener": cfg.Listener.Type, | 		"listener": cfg.Listener.Type, | ||||||
| @ -209,6 +215,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { | |||||||
| 			handler.BypassOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)), | 			handler.BypassOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)), | ||||||
| 			handler.TLSConfigOption(tlsConfig), | 			handler.TLSConfigOption(tlsConfig), | ||||||
| 			handler.RateLimiterOption(registry.RateLimiterRegistry().Get(cfg.RLimiter)), | 			handler.RateLimiterOption(registry.RateLimiterRegistry().Get(cfg.RLimiter)), | ||||||
|  | 			handler.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Handler.Limiter)), | ||||||
| 			handler.LoggerOption(handlerLogger), | 			handler.LoggerOption(handlerLogger), | ||||||
| 			handler.ServiceOption(cfg.Name), | 			handler.ServiceOption(cfg.Name), | ||||||
| 		) | 		) | ||||||
| @ -258,10 +265,18 @@ func parseForwarder(cfg *config.ForwarderConfig) (hop.Hop, error) { | |||||||
| 	} | 	} | ||||||
| 	for _, node := range cfg.Nodes { | 	for _, node := range cfg.Nodes { | ||||||
| 		if node != nil { | 		if node != nil { | ||||||
| 			hc.Nodes = append(hc.Nodes, | 			addrs := xnet.AddrPortRange(node.Addr).Addrs() | ||||||
| 				&config.NodeConfig{ | 			if len(addrs) == 0 { | ||||||
| 					Name:     node.Name, | 				addrs = append(addrs, node.Addr) | ||||||
| 					Addr:     node.Addr, | 			} | ||||||
|  | 			for i, addr := range addrs { | ||||||
|  | 				name := node.Name | ||||||
|  | 				if i > 0 { | ||||||
|  | 					name = fmt.Sprintf("%s-%d", node.Name, i) | ||||||
|  | 				} | ||||||
|  | 				hc.Nodes = append(hc.Nodes, &config.NodeConfig{ | ||||||
|  | 					Name:     name, | ||||||
|  | 					Addr:     addr, | ||||||
| 					Host:     node.Host, | 					Host:     node.Host, | ||||||
| 					Network:  node.Network, | 					Network:  node.Network, | ||||||
| 					Protocol: node.Protocol, | 					Protocol: node.Protocol, | ||||||
| @ -271,8 +286,8 @@ func parseForwarder(cfg *config.ForwarderConfig) (hop.Hop, error) { | |||||||
| 					HTTP:     node.HTTP, | 					HTTP:     node.HTTP, | ||||||
| 					TLS:      node.TLS, | 					TLS:      node.TLS, | ||||||
| 					Auth:     node.Auth, | 					Auth:     node.Auth, | ||||||
| 				}, | 				}) | ||||||
| 			) | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if len(hc.Nodes) > 0 { | 	if len(hc.Nodes) > 0 { | ||||||
|  | |||||||
| @ -16,10 +16,6 @@ import ( | |||||||
|  |  | ||||||
| // Bind implements connector.Binder. | // Bind implements connector.Binder. | ||||||
| func (c *relayConnector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) { | func (c *relayConnector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) { | ||||||
| 	if !c.md.tunnelID.IsZero() { |  | ||||||
| 		return c.bindTunnel(ctx, conn, network, address, c.options.Logger) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	log := c.options.Logger.WithFields(map[string]any{ | 	log := c.options.Logger.WithFields(map[string]any{ | ||||||
| 		"network": network, | 		"network": network, | ||||||
| 		"address": address, | 		"address": address, | ||||||
| @ -43,86 +39,6 @@ func (c *relayConnector) Bind(ctx context.Context, conn net.Conn, network, addre | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *relayConnector) bindTunnel(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) { |  | ||||||
| 	addr, cid, err := c.initTunnel(conn, network, address) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	log.Infof("create tunnel on %s/%s OK, tunnel=%s, connector=%s", addr, network, c.md.tunnelID.String(), cid) |  | ||||||
|  |  | ||||||
| 	session, err := mux.ServerSession(conn, c.md.muxCfg) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return &bindListener{ |  | ||||||
| 		network: network, |  | ||||||
| 		addr:    addr, |  | ||||||
| 		session: session, |  | ||||||
| 		logger:  log, |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *relayConnector) initTunnel(conn net.Conn, network, address string) (addr net.Addr, cid relay.ConnectorID, err error) { |  | ||||||
| 	req := relay.Request{ |  | ||||||
| 		Version: relay.Version1, |  | ||||||
| 		Cmd:     relay.CmdBind, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if network == "udp" { |  | ||||||
| 		req.Cmd |= relay.FUDP |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if c.options.Auth != nil { |  | ||||||
| 		pwd, _ := c.options.Auth.Password() |  | ||||||
| 		req.Features = append(req.Features, &relay.UserAuthFeature{ |  | ||||||
| 			Username: c.options.Auth.Username(), |  | ||||||
| 			Password: pwd, |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	af := &relay.AddrFeature{} |  | ||||||
| 	af.ParseFrom(address) |  | ||||||
|  |  | ||||||
| 	req.Features = append(req.Features, af, |  | ||||||
| 		&relay.TunnelFeature{ |  | ||||||
| 			ID: c.md.tunnelID.ID(), |  | ||||||
| 		}, |  | ||||||
| 	) |  | ||||||
| 	if _, err = req.WriteTo(conn); err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// first reply, bind status |  | ||||||
| 	resp := relay.Response{} |  | ||||||
| 	if _, err = resp.ReadFrom(conn); err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if resp.Status != relay.StatusOK { |  | ||||||
| 		err = fmt.Errorf("%d: create tunnel %s failed", resp.Status, c.md.tunnelID.String()) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, f := range resp.Features { |  | ||||||
| 		switch f.Type() { |  | ||||||
| 		case relay.FeatureAddr: |  | ||||||
| 			if feature, _ := f.(*relay.AddrFeature); feature != nil { |  | ||||||
| 				addr = &bindAddr{ |  | ||||||
| 					network: network, |  | ||||||
| 					addr:    net.JoinHostPort(feature.Host, strconv.Itoa(int(feature.Port))), |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		case relay.FeatureTunnel: |  | ||||||
| 			if feature, _ := f.(*relay.TunnelFeature); feature != nil { |  | ||||||
| 				cid = relay.NewConnectorID(feature.ID[:]) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *relayConnector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) { | func (c *relayConnector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) { | ||||||
| 	laddr, err := c.bind(conn, relay.CmdBind, network, address) | 	laddr, err := c.bind(conn, relay.CmdBind, network, address) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -177,6 +93,14 @@ func (c *relayConnector) bind(conn net.Conn, cmd relay.CmdType, network, address | |||||||
| 			Password: pwd, | 			Password: pwd, | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	nid := relay.NetworkTCP | ||||||
|  | 	if network == "udp" || network == "udp4" || network == "udp6" { | ||||||
|  | 		nid = relay.NetworkUDP | ||||||
|  | 	} | ||||||
|  | 	req.Features = append(req.Features, &relay.NetworkFeature{ | ||||||
|  | 		Network: nid, | ||||||
|  | 	}) | ||||||
| 	fa := &relay.AddrFeature{} | 	fa := &relay.AddrFeature{} | ||||||
| 	fa.ParseFrom(address) | 	fa.ParseFrom(address) | ||||||
| 	req.Features = append(req.Features, fa) | 	req.Features = append(req.Features, fa) | ||||||
|  | |||||||
| @ -13,12 +13,14 @@ import ( | |||||||
| 	"github.com/go-gost/core/common/bufpool" | 	"github.com/go-gost/core/common/bufpool" | ||||||
| 	mdata "github.com/go-gost/core/metadata" | 	mdata "github.com/go-gost/core/metadata" | ||||||
| 	"github.com/go-gost/relay" | 	"github.com/go-gost/relay" | ||||||
|  | 	xrelay "github.com/go-gost/x/internal/util/relay" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type tcpConn struct { | type tcpConn struct { | ||||||
| 	net.Conn | 	net.Conn | ||||||
| 	wbuf *bytes.Buffer | 	wbuf *bytes.Buffer | ||||||
| 	once sync.Once | 	once sync.Once | ||||||
|  | 	mu   sync.Mutex | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *tcpConn) Read(b []byte) (n int, err error) { | func (c *tcpConn) Read(b []byte) (n int, err error) { | ||||||
| @ -36,6 +38,10 @@ func (c *tcpConn) Read(b []byte) (n int, err error) { | |||||||
|  |  | ||||||
| func (c *tcpConn) Write(b []byte) (n int, err error) { | func (c *tcpConn) Write(b []byte) (n int, err error) { | ||||||
| 	n = len(b) // force byte length consistent | 	n = len(b) // force byte length consistent | ||||||
|  |  | ||||||
|  | 	c.mu.Lock() | ||||||
|  | 	defer c.mu.Unlock() | ||||||
|  |  | ||||||
| 	if c.wbuf != nil && c.wbuf.Len() > 0 { | 	if c.wbuf != nil && c.wbuf.Len() > 0 { | ||||||
| 		c.wbuf.Write(b) // append the data to the cached header | 		c.wbuf.Write(b) // append the data to the cached header | ||||||
| 		_, err = c.Conn.Write(c.wbuf.Bytes()) | 		_, err = c.Conn.Write(c.wbuf.Bytes()) | ||||||
| @ -50,6 +56,7 @@ type udpConn struct { | |||||||
| 	net.Conn | 	net.Conn | ||||||
| 	wbuf *bytes.Buffer | 	wbuf *bytes.Buffer | ||||||
| 	once sync.Once | 	once sync.Once | ||||||
|  | 	mu   sync.Mutex | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *udpConn) Read(b []byte) (n int, err error) { | func (c *udpConn) Read(b []byte) (n int, err error) { | ||||||
| @ -88,6 +95,10 @@ func (c *udpConn) Write(b []byte) (n int, err error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	n = len(b) | 	n = len(b) | ||||||
|  |  | ||||||
|  | 	c.mu.Lock() | ||||||
|  | 	defer c.mu.Unlock() | ||||||
|  |  | ||||||
| 	if c.wbuf != nil && c.wbuf.Len() > 0 { | 	if c.wbuf != nil && c.wbuf.Len() > 0 { | ||||||
| 		var bb [2]byte | 		var bb [2]byte | ||||||
| 		binary.BigEndian.PutUint16(bb[:], uint16(len(b))) | 		binary.BigEndian.PutUint16(bb[:], uint16(len(b))) | ||||||
| @ -119,7 +130,7 @@ func readResponse(r io.Reader) (err error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if resp.Status != relay.StatusOK { | 	if resp.Status != relay.StatusOK { | ||||||
| 		err = fmt.Errorf("status %d", resp.Status) | 		err = fmt.Errorf("%d %s", resp.Status, xrelay.StatusText(resp.Status)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| @ -213,16 +224,3 @@ func (c *bindUDPConn) RemoteAddr() net.Addr { | |||||||
| func (c *bindUDPConn) Metadata() mdata.Metadata { | func (c *bindUDPConn) Metadata() mdata.Metadata { | ||||||
| 	return c.md | 	return c.md | ||||||
| } | } | ||||||
|  |  | ||||||
| type bindAddr struct { |  | ||||||
| 	network string |  | ||||||
| 	addr    string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *bindAddr) Network() string { |  | ||||||
| 	return p.network |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *bindAddr) String() string { |  | ||||||
| 	return p.addr |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -101,12 +101,6 @@ func (c *relayConnector) Connect(ctx context.Context, conn net.Conn, network, ad | |||||||
| 		req.Features = append(req.Features, af) | 		req.Features = append(req.Features, af) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !c.md.tunnelID.IsZero() { |  | ||||||
| 		req.Features = append(req.Features, &relay.TunnelFeature{ |  | ||||||
| 			ID: c.md.tunnelID.ID(), |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if c.md.noDelay { | 	if c.md.noDelay { | ||||||
| 		if _, err := req.WriteTo(conn); err != nil { | 		if _, err := req.WriteTo(conn); err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
|  | |||||||
| @ -5,15 +5,12 @@ import ( | |||||||
|  |  | ||||||
| 	mdata "github.com/go-gost/core/metadata" | 	mdata "github.com/go-gost/core/metadata" | ||||||
| 	mdutil "github.com/go-gost/core/metadata/util" | 	mdutil "github.com/go-gost/core/metadata/util" | ||||||
| 	"github.com/go-gost/relay" |  | ||||||
| 	"github.com/go-gost/x/internal/util/mux" | 	"github.com/go-gost/x/internal/util/mux" | ||||||
| 	"github.com/google/uuid" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type metadata struct { | type metadata struct { | ||||||
| 	connectTimeout time.Duration | 	connectTimeout time.Duration | ||||||
| 	noDelay        bool | 	noDelay        bool | ||||||
| 	tunnelID       relay.TunnelID |  | ||||||
| 	muxCfg         *mux.Config | 	muxCfg         *mux.Config | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -26,14 +23,6 @@ func (c *relayConnector) parseMetadata(md mdata.Metadata) (err error) { | |||||||
| 	c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout) | 	c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout) | ||||||
| 	c.md.noDelay = mdutil.GetBool(md, noDelay) | 	c.md.noDelay = mdutil.GetBool(md, noDelay) | ||||||
|  |  | ||||||
| 	if s := mdutil.GetString(md, "tunnelID", "tunnel.id"); s != "" { |  | ||||||
| 		uuid, err := uuid.Parse(s) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		c.md.tunnelID = relay.NewTunnelID(uuid[:]) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	c.md.muxCfg = &mux.Config{ | 	c.md.muxCfg = &mux.Config{ | ||||||
| 		Version:           mdutil.GetInt(md, "mux.version"), | 		Version:           mdutil.GetInt(md, "mux.version"), | ||||||
| 		KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"), | 		KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"), | ||||||
|  | |||||||
| @ -1,67 +1,24 @@ | |||||||
| package tunnel | package tunnel | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bytes" |  | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"math" | 	"math" | ||||||
| 	"net" | 	"net" | ||||||
| 	"sync" |  | ||||||
|  |  | ||||||
| 	"github.com/go-gost/core/common/bufpool" | 	"github.com/go-gost/core/common/bufpool" | ||||||
| 	mdata "github.com/go-gost/core/metadata" | 	mdata "github.com/go-gost/core/metadata" | ||||||
| 	"github.com/go-gost/relay" | 	"github.com/go-gost/relay" | ||||||
|  | 	xrelay "github.com/go-gost/x/internal/util/relay" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type tcpConn struct { |  | ||||||
| 	net.Conn |  | ||||||
| 	wbuf *bytes.Buffer |  | ||||||
| 	once sync.Once |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *tcpConn) Read(b []byte) (n int, err error) { |  | ||||||
| 	c.once.Do(func() { |  | ||||||
| 		if c.wbuf != nil { |  | ||||||
| 			err = readResponse(c.Conn) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	if err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	return c.Conn.Read(b) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *tcpConn) Write(b []byte) (n int, err error) { |  | ||||||
| 	n = len(b) // force byte length consistent |  | ||||||
| 	if c.wbuf != nil && c.wbuf.Len() > 0 { |  | ||||||
| 		c.wbuf.Write(b) // append the data to the cached header |  | ||||||
| 		_, err = c.Conn.Write(c.wbuf.Bytes()) |  | ||||||
| 		c.wbuf.Reset() |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	_, err = c.Conn.Write(b) |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type udpConn struct { | type udpConn struct { | ||||||
| 	net.Conn | 	net.Conn | ||||||
| 	wbuf *bytes.Buffer |  | ||||||
| 	once sync.Once |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *udpConn) Read(b []byte) (n int, err error) { | func (c *udpConn) Read(b []byte) (n int, err error) { | ||||||
| 	c.once.Do(func() { |  | ||||||
| 		if c.wbuf != nil { |  | ||||||
| 			err = readResponse(c.Conn) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var bb [2]byte | 	var bb [2]byte | ||||||
| 	_, err = io.ReadFull(c.Conn, bb[:]) | 	_, err = io.ReadFull(c.Conn, bb[:]) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -88,14 +45,6 @@ func (c *udpConn) Write(b []byte) (n int, err error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	n = len(b) | 	n = len(b) | ||||||
| 	if c.wbuf != nil && c.wbuf.Len() > 0 { |  | ||||||
| 		var bb [2]byte |  | ||||||
| 		binary.BigEndian.PutUint16(bb[:], uint16(len(b))) |  | ||||||
| 		c.wbuf.Write(bb[:]) |  | ||||||
| 		c.wbuf.Write(b) // append the data to the cached header |  | ||||||
| 		_, err = c.wbuf.WriteTo(c.Conn) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var bb [2]byte | 	var bb [2]byte | ||||||
| 	binary.BigEndian.PutUint16(bb[:], uint16(len(b))) | 	binary.BigEndian.PutUint16(bb[:], uint16(len(b))) | ||||||
| @ -119,7 +68,7 @@ func readResponse(r io.Reader) (err error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if resp.Status != relay.StatusOK { | 	if resp.Status != relay.StatusOK { | ||||||
| 		err = fmt.Errorf("status %d", resp.Status) | 		err = fmt.Errorf("%d %s", resp.Status, xrelay.StatusText(resp.Status)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| package tunnel | package tunnel | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bytes" |  | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net" | 	"net" | ||||||
| @ -10,7 +9,7 @@ import ( | |||||||
| 	"github.com/go-gost/core/connector" | 	"github.com/go-gost/core/connector" | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
| 	"github.com/go-gost/relay" | 	"github.com/go-gost/relay" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -74,7 +73,7 @@ func (c *tunnelConnector) Connect(ctx context.Context, conn net.Conn, network, a | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	srcAddr := conn.LocalAddr().String() | 	srcAddr := conn.LocalAddr().String() | ||||||
| 	if v := auth_util.ClientAddrFromContext(ctx); v != "" { | 	if v := ctxvalue.ClientAddrFromContext(ctx); v != "" { | ||||||
| 		srcAddr = string(v) | 		srcAddr = string(v) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -90,7 +89,6 @@ func (c *tunnelConnector) Connect(ctx context.Context, conn net.Conn, network, a | |||||||
| 		ID: c.md.tunnelID.ID(), | 		ID: c.md.tunnelID.ID(), | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	if c.md.noDelay { |  | ||||||
| 	if _, err := req.WriteTo(conn); err != nil { | 	if _, err := req.WriteTo(conn); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @ -98,31 +96,13 @@ func (c *tunnelConnector) Connect(ctx context.Context, conn net.Conn, network, a | |||||||
| 	if err := readResponse(conn); err != nil { | 	if err := readResponse(conn); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	switch network { | 	switch network { | ||||||
| 	case "tcp", "tcp4", "tcp6": | 	case "tcp", "tcp4", "tcp6": | ||||||
| 		if !c.md.noDelay { |  | ||||||
| 			cc := &tcpConn{ |  | ||||||
| 				Conn: conn, |  | ||||||
| 				wbuf: &bytes.Buffer{}, |  | ||||||
| 			} |  | ||||||
| 			if _, err := req.WriteTo(cc.wbuf); err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 			conn = cc |  | ||||||
| 		} |  | ||||||
| 	case "udp", "udp4", "udp6": | 	case "udp", "udp4", "udp6": | ||||||
| 		cc := &udpConn{ | 		conn = &udpConn{ | ||||||
| 			Conn: conn, | 			Conn: conn, | ||||||
| 		} | 		} | ||||||
| 		if !c.md.noDelay { |  | ||||||
| 			cc.wbuf = &bytes.Buffer{} |  | ||||||
| 			if _, err := req.WriteTo(cc.wbuf); err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		conn = cc |  | ||||||
| 	default: | 	default: | ||||||
| 		err := fmt.Errorf("network %s is unsupported", network) | 		err := fmt.Errorf("network %s is unsupported", network) | ||||||
| 		log.Error(err) | 		log.Error(err) | ||||||
|  | |||||||
| @ -18,13 +18,11 @@ var ( | |||||||
| type metadata struct { | type metadata struct { | ||||||
| 	connectTimeout time.Duration | 	connectTimeout time.Duration | ||||||
| 	tunnelID       relay.TunnelID | 	tunnelID       relay.TunnelID | ||||||
| 	noDelay        bool |  | ||||||
| 	muxCfg         *mux.Config | 	muxCfg         *mux.Config | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *tunnelConnector) parseMetadata(md mdata.Metadata) (err error) { | func (c *tunnelConnector) parseMetadata(md mdata.Metadata) (err error) { | ||||||
| 	c.md.connectTimeout = mdutil.GetDuration(md, "connectTimeout") | 	c.md.connectTimeout = mdutil.GetDuration(md, "connectTimeout") | ||||||
| 	c.md.noDelay = mdutil.GetBool(md, "nodelay") |  | ||||||
|  |  | ||||||
| 	if s := mdutil.GetString(md, "tunnelID", "tunnel.id"); s != "" { | 	if s := mdutil.GetString(md, "tunnelID", "tunnel.id"); s != "" { | ||||||
| 		uuid, err := uuid.Parse(s) | 		uuid, err := uuid.Parse(s) | ||||||
|  | |||||||
| @ -21,14 +21,14 @@ func (d *kcpDialer) parseMetadata(md mdata.Metadata) (err error) { | |||||||
| 		handshakeTimeout = "handshakeTimeout" | 		handshakeTimeout = "handshakeTimeout" | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
| 	if file := mdutil.GetString(md, configFile); file != "" { | 	if file := mdutil.GetString(md, "kcp.configFile", "configFile", "c"); file != "" { | ||||||
| 		d.md.config, err = kcp_util.ParseFromFile(file) | 		d.md.config, err = kcp_util.ParseFromFile(file) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if m := mdutil.GetStringMap(md, config); len(m) > 0 { | 	if m := mdutil.GetStringMap(md, "kcp.config", "config"); len(m) > 0 { | ||||||
| 		b, err := json.Marshal(m) | 		b, err := json.Marshal(m) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| @ -42,6 +42,14 @@ func (d *kcpDialer) parseMetadata(md mdata.Metadata) (err error) { | |||||||
| 	if d.md.config == nil { | 	if d.md.config == nil { | ||||||
| 		d.md.config = kcp_util.DefaultConfig | 		d.md.config = kcp_util.DefaultConfig | ||||||
| 	} | 	} | ||||||
|  | 	d.md.config.TCP = mdutil.GetBool(md, "kcp.tcp", "tcp") | ||||||
|  | 	d.md.config.Key = mdutil.GetString(md, "kcp.key") | ||||||
|  | 	d.md.config.Crypt = mdutil.GetString(md, "kcp.crypt") | ||||||
|  | 	d.md.config.Mode = mdutil.GetString(md, "kcp.mode") | ||||||
|  | 	d.md.config.KeepAlive = mdutil.GetInt(md, "kcp.keepalive") | ||||||
|  | 	d.md.config.Interval = mdutil.GetInt(md, "kcp.interval") | ||||||
|  | 	d.md.config.MTU = mdutil.GetInt(md, "kcp.mtu") | ||||||
|  | 	d.md.config.SmuxVer = mdutil.GetInt(md, "kcp.smuxver") | ||||||
|  |  | ||||||
| 	d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout) | 	d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout) | ||||||
| 	return | 	return | ||||||
|  | |||||||
| @ -2,8 +2,8 @@ package mtls | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"crypto/tls" |  | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	tls "github.com/refraction-networking/utls" | ||||||
| 	"net" | 	"net" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
| @ -123,7 +123,20 @@ func (d *mtlsDialer) Handshake(ctx context.Context, conn net.Conn, options ...di | |||||||
| } | } | ||||||
|  |  | ||||||
| func (d *mtlsDialer) initSession(ctx context.Context, conn net.Conn) (*muxSession, error) { | func (d *mtlsDialer) initSession(ctx context.Context, conn net.Conn) (*muxSession, error) { | ||||||
| 	tlsConn := tls.Client(conn, d.options.TLSConfig) | 	tlsConfig := d.options.TLSConfig | ||||||
|  | 	var utlsConf = &tls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName, ClientAuth: tls.ClientAuthType(tlsConfig.ClientAuth), ClientCAs: tlsConfig.ClientCAs, RootCAs: tlsConfig.RootCAs} | ||||||
|  | 	if len(tlsConfig.Certificates) > 0 { | ||||||
|  | 		for _, certificate := range tlsConfig.Certificates { | ||||||
|  | 			utlsConf.Certificates = append(utlsConf.Certificates, tls.Certificate{ | ||||||
|  | 				Certificate:                 certificate.Certificate, | ||||||
|  | 				PrivateKey:                  certificate.PrivateKey, | ||||||
|  | 				OCSPStaple:                  certificate.OCSPStaple, | ||||||
|  | 				SignedCertificateTimestamps: certificate.SignedCertificateTimestamps, | ||||||
|  | 				Leaf:                        certificate.Leaf, | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	tlsConn := tls.UClient(conn, utlsConf, tls.HelloChrome_Auto) | ||||||
| 	if err := tlsConn.HandshakeContext(ctx); err != nil { | 	if err := tlsConn.HandshakeContext(ctx); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -3,6 +3,8 @@ package mws | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"github.com/go-gost/x/util" | ||||||
|  | 	tls "github.com/refraction-networking/utls" | ||||||
| 	"net" | 	"net" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"sync" | 	"sync" | ||||||
| @ -158,6 +160,33 @@ func (d *mwsDialer) initSession(ctx context.Context, host string, conn net.Conn, | |||||||
| 	if d.tlsEnabled { | 	if d.tlsEnabled { | ||||||
| 		url.Scheme = "wss" | 		url.Scheme = "wss" | ||||||
| 		dialer.TLSClientConfig = d.options.TLSConfig | 		dialer.TLSClientConfig = d.options.TLSConfig | ||||||
|  | 		tlsConfig := d.options.TLSConfig | ||||||
|  | 		dialer.NetDialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) { | ||||||
|  | 			utlsConf := &tls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName, ClientAuth: tls.ClientAuthType(tlsConfig.ClientAuth), ClientCAs: tlsConfig.ClientCAs, RootCAs: tlsConfig.RootCAs} | ||||||
|  | 			if len(tlsConfig.Certificates) > 0 { | ||||||
|  | 				for _, certificate := range tlsConfig.Certificates { | ||||||
|  | 					utlsConf.Certificates = append(utlsConf.Certificates, tls.Certificate{ | ||||||
|  | 						Certificate:                 certificate.Certificate, | ||||||
|  | 						PrivateKey:                  certificate.PrivateKey, | ||||||
|  | 						OCSPStaple:                  certificate.OCSPStaple, | ||||||
|  | 						SignedCertificateTimestamps: certificate.SignedCertificateTimestamps, | ||||||
|  | 						Leaf:                        certificate.Leaf, | ||||||
|  | 					}) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			var client *tls.UConn | ||||||
|  | 			if d.md.useH2 { | ||||||
|  | 				client = tls.UClient(conn, utlsConf, tls.HelloChrome_Auto) | ||||||
|  | 			} else { | ||||||
|  | 				client = tls.UClient(conn, utlsConf, tls.HelloCustom) | ||||||
|  | 				client.ApplyPreset(util.NewWsSpec()) | ||||||
|  | 			} | ||||||
|  | 			err := client.Handshake() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			return client, nil | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if d.md.handshakeTimeout > 0 { | 	if d.md.handshakeTimeout > 0 { | ||||||
|  | |||||||
| @ -27,6 +27,9 @@ type metadata struct { | |||||||
| 	header            http.Header | 	header            http.Header | ||||||
| 	keepaliveInterval time.Duration | 	keepaliveInterval time.Duration | ||||||
| 	muxCfg            *mux.Config | 	muxCfg            *mux.Config | ||||||
|  |  | ||||||
|  | 	//Evan Enhanced | ||||||
|  | 	useH2 bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *mwsDialer) parseMetadata(md mdata.Metadata) (err error) { | func (d *mwsDialer) parseMetadata(md mdata.Metadata) (err error) { | ||||||
| @ -67,5 +70,6 @@ func (d *mwsDialer) parseMetadata(md mdata.Metadata) (err error) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	d.md.useH2 = mdutil.GetBool(md, "h2") | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | |||||||
| @ -70,7 +70,7 @@ func (d *tlsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dia | |||||||
| 			}) | 			}) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	tlsConn := tls.UClient(conn, utlsConf, tls.HelloChrome_102) | 	tlsConn := tls.UClient(conn, utlsConf, tls.HelloChrome_Auto) | ||||||
| 	if err := tlsConn.HandshakeContext(ctx); err != nil { | 	if err := tlsConn.HandshakeContext(ctx); err != nil { | ||||||
| 		conn.Close() | 		conn.Close() | ||||||
| 		return nil, err | 		return nil, err | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ package ws | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"github.com/go-gost/x/util" | ||||||
| 	tls "github.com/refraction-networking/utls" | 	tls "github.com/refraction-networking/utls" | ||||||
| 	"net" | 	"net" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| @ -110,8 +111,13 @@ func (d *wsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dial | |||||||
| 					}) | 					}) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			client := tls.UClient(conn, utlsConf, tls.HelloCustom) | 			var client *tls.UConn | ||||||
| 			client.ApplyPreset(newWsSpec()) | 			if d.md.useH2 { | ||||||
|  | 				client = tls.UClient(conn, utlsConf, tls.HelloChrome_Auto) | ||||||
|  | 			} else { | ||||||
|  | 				client = tls.UClient(conn, utlsConf, tls.HelloCustom) | ||||||
|  | 				client.ApplyPreset(util.NewWsSpec()) | ||||||
|  | 			} | ||||||
| 			err := client.Handshake() | 			err := client.Handshake() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| @ -159,76 +165,3 @@ func (d *wsDialer) keepalive(conn ws_util.WebsocketConn) { | |||||||
| 		conn.SetWriteDeadline(time.Time{}) | 		conn.SetWriteDeadline(time.Time{}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func newWsSpec() *tls.ClientHelloSpec { |  | ||||||
| 	return &tls.ClientHelloSpec{ |  | ||||||
| 		CipherSuites: []uint16{ |  | ||||||
| 			tls.GREASE_PLACEHOLDER, |  | ||||||
| 			tls.TLS_AES_128_GCM_SHA256, |  | ||||||
| 			tls.TLS_AES_256_GCM_SHA384, |  | ||||||
| 			tls.TLS_CHACHA20_POLY1305_SHA256, |  | ||||||
| 			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, |  | ||||||
| 			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, |  | ||||||
| 			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, |  | ||||||
| 			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, |  | ||||||
| 			tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, |  | ||||||
| 			tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, |  | ||||||
| 			tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, |  | ||||||
| 			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, |  | ||||||
| 			tls.TLS_RSA_WITH_AES_128_GCM_SHA256, |  | ||||||
| 			tls.TLS_RSA_WITH_AES_256_GCM_SHA384, |  | ||||||
| 			tls.TLS_RSA_WITH_AES_128_CBC_SHA, |  | ||||||
| 			tls.TLS_RSA_WITH_AES_256_CBC_SHA, |  | ||||||
| 		}, |  | ||||||
| 		CompressionMethods: []byte{ |  | ||||||
| 			0x00, // compressionNone |  | ||||||
| 		}, |  | ||||||
| 		Extensions: []tls.TLSExtension{ |  | ||||||
| 			&tls.UtlsGREASEExtension{}, |  | ||||||
| 			&tls.SNIExtension{}, |  | ||||||
| 			&tls.ExtendedMasterSecretExtension{}, |  | ||||||
| 			&tls.RenegotiationInfoExtension{Renegotiation: tls.RenegotiateOnceAsClient}, |  | ||||||
| 			&tls.SupportedCurvesExtension{[]tls.CurveID{ |  | ||||||
| 				tls.GREASE_PLACEHOLDER, |  | ||||||
| 				tls.X25519, |  | ||||||
| 				tls.CurveP256, |  | ||||||
| 				tls.CurveP384, |  | ||||||
| 			}}, |  | ||||||
| 			&tls.SupportedPointsExtension{SupportedPoints: []byte{ |  | ||||||
| 				0x00, // pointFormatUncompressed |  | ||||||
| 			}}, |  | ||||||
| 			&tls.SessionTicketExtension{}, |  | ||||||
| 			&tls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}}, |  | ||||||
| 			&tls.StatusRequestExtension{}, |  | ||||||
| 			&tls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []tls.SignatureScheme{ |  | ||||||
| 				tls.ECDSAWithP256AndSHA256, |  | ||||||
| 				tls.PSSWithSHA256, |  | ||||||
| 				tls.PKCS1WithSHA256, |  | ||||||
| 				tls.ECDSAWithP384AndSHA384, |  | ||||||
| 				tls.PSSWithSHA384, |  | ||||||
| 				tls.PKCS1WithSHA384, |  | ||||||
| 				tls.PSSWithSHA512, |  | ||||||
| 				tls.PKCS1WithSHA512, |  | ||||||
| 			}}, |  | ||||||
| 			&tls.SCTExtension{}, |  | ||||||
| 			&tls.KeyShareExtension{[]tls.KeyShare{ |  | ||||||
| 				{Group: tls.CurveID(tls.GREASE_PLACEHOLDER), Data: []byte{0}}, |  | ||||||
| 				{Group: tls.X25519}, |  | ||||||
| 			}}, |  | ||||||
| 			&tls.PSKKeyExchangeModesExtension{[]uint8{ |  | ||||||
| 				tls.PskModeDHE, |  | ||||||
| 			}}, |  | ||||||
| 			&tls.SupportedVersionsExtension{[]uint16{ |  | ||||||
| 				tls.GREASE_PLACEHOLDER, |  | ||||||
| 				tls.VersionTLS13, |  | ||||||
| 				tls.VersionTLS12, |  | ||||||
| 			}}, |  | ||||||
| 			&tls.UtlsCompressCertExtension{[]tls.CertCompressionAlgo{ |  | ||||||
| 				tls.CertCompressionBrotli, |  | ||||||
| 			}}, |  | ||||||
| 			&tls.ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}}, |  | ||||||
| 			&tls.UtlsGREASEExtension{}, |  | ||||||
| 			&tls.UtlsPaddingExtension{GetPaddingLen: tls.BoringPaddingStyle}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -25,6 +25,9 @@ type metadata struct { | |||||||
|  |  | ||||||
| 	header            http.Header | 	header            http.Header | ||||||
| 	keepaliveInterval time.Duration | 	keepaliveInterval time.Duration | ||||||
|  |  | ||||||
|  | 	//Evan Enhance | ||||||
|  | 	useH2 bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *wsDialer) parseMetadata(md mdata.Metadata) (err error) { | func (d *wsDialer) parseMetadata(md mdata.Metadata) (err error) { | ||||||
| @ -56,5 +59,6 @@ func (d *wsDialer) parseMetadata(md mdata.Metadata) (err error) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	d.md.useH2 = mdutil.GetBool(md, "h2") | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								go.mod
									
									
									
									
									
								
							| @ -7,10 +7,10 @@ require ( | |||||||
| 	github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d | 	github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d | ||||||
| 	github.com/gin-contrib/cors v1.3.1 | 	github.com/gin-contrib/cors v1.3.1 | ||||||
| 	github.com/gin-gonic/gin v1.9.1 | 	github.com/gin-gonic/gin v1.9.1 | ||||||
| 	github.com/go-gost/core v0.0.0-20231109123312-8e4fc06cf1b7 | 	github.com/go-gost/core v0.0.0-20231119081403-abc73f2ca2b7 | ||||||
| 	github.com/go-gost/gosocks4 v0.0.1 | 	github.com/go-gost/gosocks4 v0.0.1 | ||||||
| 	github.com/go-gost/gosocks5 v0.4.0 | 	github.com/go-gost/gosocks5 v0.4.0 | ||||||
| 	github.com/go-gost/plugin v0.0.0-20231109123346-0ae4157b9d25 | 	github.com/go-gost/plugin v0.0.0-20231119084331-d49a1cb23b3b | ||||||
| 	github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7 | 	github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7 | ||||||
| 	github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 | 	github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 | ||||||
| 	github.com/go-redis/redis/v8 v8.11.5 | 	github.com/go-redis/redis/v8 v8.11.5 | ||||||
| @ -44,6 +44,7 @@ require ( | |||||||
| 	golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 | 	golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 | ||||||
| 	google.golang.org/grpc v1.59.0 | 	google.golang.org/grpc v1.59.0 | ||||||
| 	google.golang.org/protobuf v1.31.0 | 	google.golang.org/protobuf v1.31.0 | ||||||
|  | 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 | 	gopkg.in/yaml.v3 v3.0.1 | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								go.sum
									
									
									
									
									
								
							| @ -93,14 +93,14 @@ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SU | |||||||
| github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= | 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-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-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||||||
| github.com/go-gost/core v0.0.0-20231109123312-8e4fc06cf1b7 h1:sDsPtmP51qf8zN/RbZZj/3vNLCoH0sdvpIRwV6TfzvY= | github.com/go-gost/core v0.0.0-20231119081403-abc73f2ca2b7 h1:fxVUlZANqPApygO7lT8bYySyajiCFA62bDiNorral1w= | ||||||
| github.com/go-gost/core v0.0.0-20231109123312-8e4fc06cf1b7/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E= | github.com/go-gost/core v0.0.0-20231119081403-abc73f2ca2b7/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E= | ||||||
| github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s= | github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s= | ||||||
| github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc= | github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc= | ||||||
| github.com/go-gost/gosocks5 v0.4.0 h1:EIrOEkpJez4gwHrMa33frA+hHXJyevjp47thpMQsJzI= | github.com/go-gost/gosocks5 v0.4.0 h1:EIrOEkpJez4gwHrMa33frA+hHXJyevjp47thpMQsJzI= | ||||||
| github.com/go-gost/gosocks5 v0.4.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4= | github.com/go-gost/gosocks5 v0.4.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4= | ||||||
| github.com/go-gost/plugin v0.0.0-20231109123346-0ae4157b9d25 h1:sOarC0xAJij4VtEhkJRng5okZW23KlXprxhb5XFZ+pw= | github.com/go-gost/plugin v0.0.0-20231119084331-d49a1cb23b3b h1:ZmnYutflq+KOZK+Px5RDckorDSxTYlkT4aQbjTC8/C4= | ||||||
| github.com/go-gost/plugin v0.0.0-20231109123346-0ae4157b9d25/go.mod h1:qXr2Zm9Ex2ATqnWuNUzVZqySPMnuIihvblYZt4MlZLw= | github.com/go-gost/plugin v0.0.0-20231119084331-d49a1cb23b3b/go.mod h1:qXr2Zm9Ex2ATqnWuNUzVZqySPMnuIihvblYZt4MlZLw= | ||||||
| github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7 h1:qAG1OyjvdA5h221CfFSS3J359V3d2E7dJWyP29QoDSI= | github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7 h1:qAG1OyjvdA5h221CfFSS3J359V3d2E7dJWyP29QoDSI= | ||||||
| github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8= | github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8= | ||||||
| github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 h1:xj8gUZGYO3nb5+6Bjw9+tsFkA9sYynrOvDvvC4uDV2I= | github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 h1:xj8gUZGYO3nb5+6Bjw9+tsFkA9sYynrOvDvvC4uDV2I= | ||||||
| @ -724,6 +724,8 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8 | |||||||
| gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= | gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= | ||||||
| gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= | 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/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||||
|  | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= | ||||||
|  | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= | ||||||
| gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= | 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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= | ||||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
|  | |||||||
| @ -21,9 +21,9 @@ import ( | |||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
| 	"github.com/go-gost/x/config" | 	"github.com/go-gost/x/config" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	xio "github.com/go-gost/x/internal/io" | 	xio "github.com/go-gost/x/internal/io" | ||||||
| 	xnet "github.com/go-gost/x/internal/net" | 	xnet "github.com/go-gost/x/internal/net" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" |  | ||||||
| 	"github.com/go-gost/x/internal/util/forward" | 	"github.com/go-gost/x/internal/util/forward" | ||||||
| 	tls_util "github.com/go-gost/x/internal/util/tls" | 	tls_util "github.com/go-gost/x/internal/util/tls" | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| @ -119,8 +119,6 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand | |||||||
| 		host = net.JoinHostPort(host, "0") | 		host = net.JoinHostPort(host, "0") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx = auth_util.ContextWithClientAddr(ctx, auth_util.ClientAddr(conn.RemoteAddr().String())) |  | ||||||
|  |  | ||||||
| 	var target *chain.Node | 	var target *chain.Node | ||||||
| 	if host != "" { | 	if host != "" { | ||||||
| 		target = &chain.Node{ | 		target = &chain.Node{ | ||||||
| @ -223,10 +221,9 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, remot | |||||||
| 					"src": addr.String(), | 					"src": addr.String(), | ||||||
| 				}) | 				}) | ||||||
| 				remoteAddr = addr | 				remoteAddr = addr | ||||||
|  | 				ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(remoteAddr.String())) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			ctx = auth_util.ContextWithClientAddr(ctx, auth_util.ClientAddr(remoteAddr.String())) |  | ||||||
|  |  | ||||||
| 			target := &chain.Node{ | 			target := &chain.Node{ | ||||||
| 				Addr: req.Host, | 				Addr: req.Host, | ||||||
| 			} | 			} | ||||||
| @ -259,7 +256,7 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, remot | |||||||
| 					log.Warnf("node %s(%s) 401 unauthorized", target.Name, target.Addr) | 					log.Warnf("node %s(%s) 401 unauthorized", target.Name, target.Addr) | ||||||
| 					return resp.Write(rw) | 					return resp.Write(rw) | ||||||
| 				} | 				} | ||||||
| 				ctx = auth_util.ContextWithID(ctx, auth_util.ID(id)) | 				ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(id)) | ||||||
| 			} | 			} | ||||||
| 			if httpSettings := target.Options().HTTP; httpSettings != nil { | 			if httpSettings := target.Options().HTTP; httpSettings != nil { | ||||||
| 				if httpSettings.Host != "" { | 				if httpSettings.Host != "" { | ||||||
|  | |||||||
| @ -22,10 +22,10 @@ import ( | |||||||
| 	mdata "github.com/go-gost/core/metadata" | 	mdata "github.com/go-gost/core/metadata" | ||||||
| 	mdutil "github.com/go-gost/core/metadata/util" | 	mdutil "github.com/go-gost/core/metadata/util" | ||||||
| 	"github.com/go-gost/x/config" | 	"github.com/go-gost/x/config" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	xio "github.com/go-gost/x/internal/io" | 	xio "github.com/go-gost/x/internal/io" | ||||||
| 	xnet "github.com/go-gost/x/internal/net" | 	xnet "github.com/go-gost/x/internal/net" | ||||||
| 	"github.com/go-gost/x/internal/net/proxyproto" | 	"github.com/go-gost/x/internal/net/proxyproto" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" |  | ||||||
| 	"github.com/go-gost/x/internal/util/forward" | 	"github.com/go-gost/x/internal/util/forward" | ||||||
| 	tls_util "github.com/go-gost/x/internal/util/tls" | 	tls_util "github.com/go-gost/x/internal/util/tls" | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| @ -117,8 +117,6 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx = auth_util.ContextWithClientAddr(ctx, auth_util.ClientAddr(conn.RemoteAddr().String())) |  | ||||||
|  |  | ||||||
| 	if md, ok := conn.(mdata.Metadatable); ok { | 	if md, ok := conn.(mdata.Metadatable); ok { | ||||||
| 		if v := mdutil.GetString(md.Metadata(), "host"); v != "" { | 		if v := mdutil.GetString(md.Metadata(), "host"); v != "" { | ||||||
| 			host = v | 			host = v | ||||||
| @ -224,10 +222,9 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, remot | |||||||
| 					"src": addr.String(), | 					"src": addr.String(), | ||||||
| 				}) | 				}) | ||||||
| 				remoteAddr = addr | 				remoteAddr = addr | ||||||
|  | 				ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(remoteAddr.String())) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			ctx = auth_util.ContextWithClientAddr(ctx, auth_util.ClientAddr(remoteAddr.String())) |  | ||||||
|  |  | ||||||
| 			target := &chain.Node{ | 			target := &chain.Node{ | ||||||
| 				Addr: req.Host, | 				Addr: req.Host, | ||||||
| 			} | 			} | ||||||
| @ -260,7 +257,7 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, remot | |||||||
| 					log.Warnf("node %s(%s) 401 unauthorized", target.Name, target.Addr) | 					log.Warnf("node %s(%s) 401 unauthorized", target.Name, target.Addr) | ||||||
| 					return resp.Write(rw) | 					return resp.Write(rw) | ||||||
| 				} | 				} | ||||||
| 				ctx = auth_util.ContextWithID(ctx, auth_util.ID(id)) | 				ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(id)) | ||||||
| 			} | 			} | ||||||
| 			if httpSettings := target.Options().HTTP; httpSettings != nil { | 			if httpSettings := target.Options().HTTP; httpSettings != nil { | ||||||
| 				if httpSettings.Host != "" { | 				if httpSettings.Host != "" { | ||||||
|  | |||||||
| @ -19,11 +19,12 @@ import ( | |||||||
| 	"github.com/asaskevich/govalidator" | 	"github.com/asaskevich/govalidator" | ||||||
| 	"github.com/go-gost/core/chain" | 	"github.com/go-gost/core/chain" | ||||||
| 	"github.com/go-gost/core/handler" | 	"github.com/go-gost/core/handler" | ||||||
|  | 	"github.com/go-gost/core/limiter/traffic" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	netpkg "github.com/go-gost/x/internal/net" | 	netpkg "github.com/go-gost/x/internal/net" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" | 	"github.com/go-gost/x/limiter/traffic/wrapper" | ||||||
| 	sx "github.com/go-gost/x/internal/util/selector" |  | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -89,8 +90,6 @@ func (h *httpHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler | |||||||
| 	} | 	} | ||||||
| 	defer req.Body.Close() | 	defer req.Body.Close() | ||||||
|  |  | ||||||
| 	ctx = auth_util.ContextWithClientAddr(ctx, auth_util.ClientAddr(conn.RemoteAddr().String())) |  | ||||||
|  |  | ||||||
| 	return h.handleRequest(ctx, conn, req, log) | 	return h.handleRequest(ctx, conn, req, log) | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -148,11 +147,11 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt | |||||||
| 		resp.Header = http.Header{} | 		resp.Header = http.Header{} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	id, ok := h.authenticate(ctx, conn, req, resp, log) | 	clientID, ok := h.authenticate(ctx, conn, req, resp, log) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	ctx = auth_util.ContextWithID(ctx, auth_util.ID(id)) | 	ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID)) | ||||||
|  |  | ||||||
| 	if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, network, addr) { | 	if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, network, addr) { | ||||||
| 		resp.StatusCode = http.StatusForbidden | 		resp.StatusCode = http.StatusForbidden | ||||||
| @ -186,7 +185,7 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt | |||||||
|  |  | ||||||
| 	switch h.md.hash { | 	switch h.md.hash { | ||||||
| 	case "host": | 	case "host": | ||||||
| 		ctx = sx.ContextWithHash(ctx, &sx.Hash{Source: addr}) | 		ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cc, err := h.router.Dial(ctx, network, addr) | 	cc, err := h.router.Dial(ctx, network, addr) | ||||||
| @ -222,9 +221,16 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	rw := wrapper.WrapReadWriter(h.options.Limiter, conn, conn.RemoteAddr().String(), | ||||||
|  | 		traffic.NetworkOption(network), | ||||||
|  | 		traffic.AddrOption(addr), | ||||||
|  | 		traffic.ClientOption(clientID), | ||||||
|  | 		traffic.SrcOption(conn.RemoteAddr().String()), | ||||||
|  | 	) | ||||||
|  |  | ||||||
| 	start := time.Now() | 	start := time.Now() | ||||||
| 	log.Infof("%s <-> %s", conn.RemoteAddr(), addr) | 	log.Infof("%s <-> %s", conn.RemoteAddr(), addr) | ||||||
| 	netpkg.Transport(conn, cc) | 	netpkg.Transport(rw, cc) | ||||||
| 	log.WithFields(map[string]any{ | 	log.WithFields(map[string]any{ | ||||||
| 		"duration": time.Since(start), | 		"duration": time.Since(start), | ||||||
| 	}).Infof("%s >-< %s", conn.RemoteAddr(), addr) | 	}).Infof("%s >-< %s", conn.RemoteAddr(), addr) | ||||||
|  | |||||||
| @ -71,7 +71,7 @@ func (h *httpHandler) handleUDP(ctx context.Context, conn net.Conn, log logger.L | |||||||
|  |  | ||||||
| 	t := time.Now() | 	t := time.Now() | ||||||
| 	log.Infof("%s <-> %s", conn.RemoteAddr(), pc.LocalAddr()) | 	log.Infof("%s <-> %s", conn.RemoteAddr(), pc.LocalAddr()) | ||||||
| 	relay.Run() | 	relay.Run(ctx) | ||||||
| 	log.WithFields(map[string]any{ | 	log.WithFields(map[string]any{ | ||||||
| 		"duration": time.Since(t), | 		"duration": time.Since(t), | ||||||
| 	}).Infof("%s >-< %s", conn.RemoteAddr(), pc.LocalAddr()) | 	}).Infof("%s >-< %s", conn.RemoteAddr(), pc.LocalAddr()) | ||||||
|  | |||||||
| @ -20,12 +20,13 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/go-gost/core/chain" | 	"github.com/go-gost/core/chain" | ||||||
| 	"github.com/go-gost/core/handler" | 	"github.com/go-gost/core/handler" | ||||||
|  | 	"github.com/go-gost/core/limiter/traffic" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	xio "github.com/go-gost/x/internal/io" | 	xio "github.com/go-gost/x/internal/io" | ||||||
| 	netpkg "github.com/go-gost/x/internal/net" | 	netpkg "github.com/go-gost/x/internal/net" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" | 	"github.com/go-gost/x/limiter/traffic/wrapper" | ||||||
| 	sx "github.com/go-gost/x/internal/util/selector" |  | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -89,8 +90,6 @@ func (h *http2Handler) Handle(ctx context.Context, conn net.Conn, opts ...handle | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx = auth_util.ContextWithClientAddr(ctx, auth_util.ClientAddr(conn.RemoteAddr().String())) |  | ||||||
|  |  | ||||||
| 	md := v.Metadata() | 	md := v.Metadata() | ||||||
| 	return h.roundTrip(ctx, | 	return h.roundTrip(ctx, | ||||||
| 		md.Get("w").(http.ResponseWriter), | 		md.Get("w").(http.ResponseWriter), | ||||||
| @ -149,11 +148,11 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req | |||||||
| 		Body:       io.NopCloser(bytes.NewReader([]byte{})), | 		Body:       io.NopCloser(bytes.NewReader([]byte{})), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	id, ok := h.authenticate(ctx, w, req, resp, log) | 	clientID, ok := h.authenticate(ctx, w, req, resp, log) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	ctx = auth_util.ContextWithID(ctx, auth_util.ID(id)) | 	ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID)) | ||||||
|  |  | ||||||
| 	if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", addr) { | 	if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", addr) { | ||||||
| 		w.WriteHeader(http.StatusForbidden) | 		w.WriteHeader(http.StatusForbidden) | ||||||
| @ -167,7 +166,7 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req | |||||||
|  |  | ||||||
| 	switch h.md.hash { | 	switch h.md.hash { | ||||||
| 	case "host": | 	case "host": | ||||||
| 		ctx = sx.ContextWithHash(ctx, &sx.Hash{Source: addr}) | 		ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cc, err := h.router.Dial(ctx, "tcp", addr) | 	cc, err := h.router.Dial(ctx, "tcp", addr) | ||||||
| @ -205,9 +204,15 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req | |||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		rw := wrapper.WrapReadWriter(h.options.Limiter, xio.NewReadWriter(req.Body, flushWriter{w}), req.RemoteAddr, | ||||||
|  | 			traffic.NetworkOption("tcp"), | ||||||
|  | 			traffic.AddrOption(addr), | ||||||
|  | 			traffic.ClientOption(clientID), | ||||||
|  | 			traffic.SrcOption(req.RemoteAddr), | ||||||
|  | 		) | ||||||
| 		start := time.Now() | 		start := time.Now() | ||||||
| 		log.Infof("%s <-> %s", req.RemoteAddr, addr) | 		log.Infof("%s <-> %s", req.RemoteAddr, addr) | ||||||
| 		netpkg.Transport(xio.NewReadWriter(req.Body, flushWriter{w}), cc) | 		netpkg.Transport(rw, cc) | ||||||
| 		log.WithFields(map[string]any{ | 		log.WithFields(map[string]any{ | ||||||
| 			"duration": time.Since(start), | 			"duration": time.Since(start), | ||||||
| 		}).Infof("%s >-< %s", req.RemoteAddr, addr) | 		}).Infof("%s >-< %s", req.RemoteAddr, addr) | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ import ( | |||||||
| 	"github.com/go-gost/core/hop" | 	"github.com/go-gost/core/hop" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
| 	sx "github.com/go-gost/x/internal/util/selector" | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -114,7 +114,7 @@ func (h *http3Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req | |||||||
|  |  | ||||||
| 	switch h.md.hash { | 	switch h.md.hash { | ||||||
| 	case "host": | 	case "host": | ||||||
| 		ctx = sx.ContextWithHash(ctx, &sx.Hash{Source: addr}) | 		ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var target *chain.Node | 	var target *chain.Node | ||||||
|  | |||||||
| @ -2,8 +2,6 @@ package relay | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"crypto/md5" |  | ||||||
| 	"encoding/hex" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net" | 	"net" | ||||||
| 	"time" | 	"time" | ||||||
| @ -17,7 +15,6 @@ import ( | |||||||
| 	relay_util "github.com/go-gost/x/internal/util/relay" | 	relay_util "github.com/go-gost/x/internal/util/relay" | ||||||
| 	metrics "github.com/go-gost/x/metrics/wrapper" | 	metrics "github.com/go-gost/x/metrics/wrapper" | ||||||
| 	xservice "github.com/go-gost/x/service" | 	xservice "github.com/go-gost/x/service" | ||||||
| 	"github.com/google/uuid" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (h *relayHandler) handleBind(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error { | func (h *relayHandler) handleBind(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error { | ||||||
| @ -176,60 +173,9 @@ func (h *relayHandler) bindUDP(ctx context.Context, conn net.Conn, network, addr | |||||||
|  |  | ||||||
| 	t := time.Now() | 	t := time.Now() | ||||||
| 	log.Debugf("%s <-> %s", conn.RemoteAddr(), pc.LocalAddr()) | 	log.Debugf("%s <-> %s", conn.RemoteAddr(), pc.LocalAddr()) | ||||||
| 	r.Run() | 	r.Run(ctx) | ||||||
| 	log.WithFields(map[string]any{ | 	log.WithFields(map[string]any{ | ||||||
| 		"duration": time.Since(t), | 		"duration": time.Since(t), | ||||||
| 	}).Debugf("%s >-< %s", conn.RemoteAddr(), pc.LocalAddr()) | 	}).Debugf("%s >-< %s", conn.RemoteAddr(), pc.LocalAddr()) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (h *relayHandler) handleBindTunnel(ctx context.Context, conn net.Conn, network, address string, tunnelID relay.TunnelID, log logger.Logger) (err error) { |  | ||||||
| 	resp := relay.Response{ |  | ||||||
| 		Version: relay.Version1, |  | ||||||
| 		Status:  relay.StatusOK, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	uuid, err := uuid.NewRandom() |  | ||||||
| 	if err != nil { |  | ||||||
| 		resp.Status = relay.StatusInternalServerError |  | ||||||
| 		resp.WriteTo(conn) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	connectorID := relay.NewConnectorID(uuid[:]) |  | ||||||
| 	if network == "udp" { |  | ||||||
| 		connectorID = relay.NewUDPConnectorID(uuid[:]) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	addr := address |  | ||||||
| 	if host, port, _ := net.SplitHostPort(addr); host == "" { |  | ||||||
| 		v := md5.Sum([]byte(tunnelID.String())) |  | ||||||
| 		host = hex.EncodeToString(v[:8]) |  | ||||||
| 		addr = net.JoinHostPort(host, port) |  | ||||||
| 	} |  | ||||||
| 	af := &relay.AddrFeature{} |  | ||||||
| 	err = af.ParseFrom(addr) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Warn(err) |  | ||||||
| 	} |  | ||||||
| 	resp.Features = append(resp.Features, af, |  | ||||||
| 		&relay.TunnelFeature{ |  | ||||||
| 			ID: connectorID.ID(), |  | ||||||
| 		}, |  | ||||||
| 	) |  | ||||||
| 	resp.WriteTo(conn) |  | ||||||
|  |  | ||||||
| 	// Upgrade connection to multiplex session. |  | ||||||
| 	session, err := mux.ClientSession(conn, h.md.muxCfg) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	h.pool.Add(tunnelID, NewConnector(connectorID, session)) |  | ||||||
| 	if h.md.ingress != nil { |  | ||||||
| 		h.md.ingress.Set(ctx, addr, tunnelID.String()) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	log.Debugf("%s/%s: tunnel=%s, connector=%s established", address, network, tunnelID, connectorID) |  | ||||||
|  |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -6,14 +6,15 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"net" | 	"net" | ||||||
| 	"strconv" |  | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/limiter/traffic" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/relay" | 	"github.com/go-gost/relay" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	xnet "github.com/go-gost/x/internal/net" | 	xnet "github.com/go-gost/x/internal/net" | ||||||
| 	sx "github.com/go-gost/x/internal/util/selector" |  | ||||||
| 	serial "github.com/go-gost/x/internal/util/serial" | 	serial "github.com/go-gost/x/internal/util/serial" | ||||||
|  | 	"github.com/go-gost/x/limiter/traffic/wrapper" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (err error) { | func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (err error) { | ||||||
| @ -52,7 +53,7 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network | |||||||
|  |  | ||||||
| 	switch h.md.hash { | 	switch h.md.hash { | ||||||
| 	case "host": | 	case "host": | ||||||
| 		ctx = sx.ContextWithHash(ctx, &sx.Hash{Source: address}) | 		ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: address}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var cc io.ReadWriteCloser | 	var cc io.ReadWriteCloser | ||||||
| @ -104,108 +105,19 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	rw := wrapper.WrapReadWriter(h.options.Limiter, conn, conn.RemoteAddr().String(), | ||||||
|  | 		traffic.NetworkOption(network), | ||||||
|  | 		traffic.AddrOption(address), | ||||||
|  | 		traffic.ClientOption(string(ctxvalue.ClientIDFromContext(ctx))), | ||||||
|  | 		traffic.SrcOption(conn.RemoteAddr().String()), | ||||||
|  | 	) | ||||||
|  |  | ||||||
| 	t := time.Now() | 	t := time.Now() | ||||||
| 	log.Infof("%s <-> %s", conn.RemoteAddr(), address) | 	log.Infof("%s <-> %s", conn.RemoteAddr(), address) | ||||||
| 	xnet.Transport(conn, cc) | 	xnet.Transport(rw, cc) | ||||||
| 	log.WithFields(map[string]any{ | 	log.WithFields(map[string]any{ | ||||||
| 		"duration": time.Since(t), | 		"duration": time.Since(t), | ||||||
| 	}).Infof("%s >-< %s", conn.RemoteAddr(), address) | 	}).Infof("%s >-< %s", conn.RemoteAddr(), address) | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (h *relayHandler) handleConnectTunnel(ctx context.Context, conn net.Conn, network, address string, tunnelID relay.TunnelID, log logger.Logger) error { |  | ||||||
| 	log = log.WithFields(map[string]any{ |  | ||||||
| 		"dst":    fmt.Sprintf("%s/%s", address, network), |  | ||||||
| 		"cmd":    "connect", |  | ||||||
| 		"tunnel": tunnelID.String(), |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	log.Debugf("%s >> %s/%s", conn.RemoteAddr(), address, network) |  | ||||||
|  |  | ||||||
| 	resp := relay.Response{ |  | ||||||
| 		Version: relay.Version1, |  | ||||||
| 		Status:  relay.StatusOK, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	host, sp, _ := net.SplitHostPort(address) |  | ||||||
|  |  | ||||||
| 	if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, network, address) { |  | ||||||
| 		log.Debug("bypass: ", address) |  | ||||||
| 		resp.Status = relay.StatusForbidden |  | ||||||
| 		_, err := resp.WriteTo(conn) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var tid relay.TunnelID |  | ||||||
| 	if ingress := h.md.ingress; ingress != nil { |  | ||||||
| 		tid = parseTunnelID(ingress.Get(ctx, host)) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if !tid.Equal(tunnelID) && !h.md.directTunnel { |  | ||||||
| 		resp.Status = relay.StatusHostUnreachable |  | ||||||
| 		resp.WriteTo(conn) |  | ||||||
| 		err := fmt.Errorf("no route to host %s", host) |  | ||||||
| 		log.Error(err) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	cc, _, err := getTunnelConn(network, h.pool, tunnelID, 3, log) |  | ||||||
| 	if err != nil { |  | ||||||
| 		resp.Status = relay.StatusServiceUnavailable |  | ||||||
| 		resp.WriteTo(conn) |  | ||||||
| 		log.Error(err) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer cc.Close() |  | ||||||
|  |  | ||||||
| 	log.Debugf("%s >> %s", conn.RemoteAddr(), cc.RemoteAddr()) |  | ||||||
|  |  | ||||||
| 	if h.md.noDelay { |  | ||||||
| 		if _, err := resp.WriteTo(conn); err != nil { |  | ||||||
| 			log.Error(err) |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		rc := &tcpConn{ |  | ||||||
| 			Conn: conn, |  | ||||||
| 		} |  | ||||||
| 		// cache the header |  | ||||||
| 		if _, err := resp.WriteTo(&rc.wbuf); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		conn = rc |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var features []relay.Feature |  | ||||||
| 	af := &relay.AddrFeature{} // source/visitor address |  | ||||||
| 	af.ParseFrom(conn.RemoteAddr().String()) |  | ||||||
| 	features = append(features, af) |  | ||||||
|  |  | ||||||
| 	if host != "" { |  | ||||||
| 		port, _ := strconv.Atoi(sp) |  | ||||||
| 		// target host |  | ||||||
| 		af = &relay.AddrFeature{ |  | ||||||
| 			AType: relay.AddrDomain, |  | ||||||
| 			Host:  host, |  | ||||||
| 			Port:  uint16(port), |  | ||||||
| 		} |  | ||||||
| 		features = append(features, af) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	resp = relay.Response{ |  | ||||||
| 		Version:  relay.Version1, |  | ||||||
| 		Status:   relay.StatusOK, |  | ||||||
| 		Features: features, |  | ||||||
| 	} |  | ||||||
| 	resp.WriteTo(cc) |  | ||||||
|  |  | ||||||
| 	t := time.Now() |  | ||||||
| 	log.Debugf("%s <-> %s", conn.RemoteAddr(), cc.RemoteAddr()) |  | ||||||
| 	xnet.Transport(conn, cc) |  | ||||||
| 	log.WithFields(map[string]any{ |  | ||||||
| 		"duration": time.Since(t), |  | ||||||
| 	}).Debugf("%s >-< %s", conn.RemoteAddr(), cc.RemoteAddr()) |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,25 +1,17 @@ | |||||||
| package relay | package relay | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"bufio" |  | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"net" | 	"net" | ||||||
| 	"net/http" |  | ||||||
| 	"net/http/httputil" |  | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/go-gost/core/handler" | 	"github.com/go-gost/core/handler" | ||||||
| 	"github.com/go-gost/core/ingress" |  | ||||||
| 	"github.com/go-gost/core/listener" | 	"github.com/go-gost/core/listener" | ||||||
| 	"github.com/go-gost/core/logger" |  | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
| 	"github.com/go-gost/relay" | 	"github.com/go-gost/relay" | ||||||
| 	admission "github.com/go-gost/x/admission/wrapper" | 	admission "github.com/go-gost/x/admission/wrapper" | ||||||
| 	xnet "github.com/go-gost/x/internal/net" | 	xnet "github.com/go-gost/x/internal/net" | ||||||
| 	"github.com/go-gost/x/internal/net/proxyproto" | 	"github.com/go-gost/x/internal/net/proxyproto" | ||||||
| 	"github.com/go-gost/x/internal/util/forward" |  | ||||||
| 	"github.com/go-gost/x/internal/util/mux" | 	"github.com/go-gost/x/internal/util/mux" | ||||||
| 	climiter "github.com/go-gost/x/limiter/conn/wrapper" | 	climiter "github.com/go-gost/x/limiter/conn/wrapper" | ||||||
| 	limiter "github.com/go-gost/x/limiter/traffic/wrapper" | 	limiter "github.com/go-gost/x/limiter/traffic/wrapper" | ||||||
| @ -130,203 +122,3 @@ func (h *tcpHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler. | |||||||
| 		Debugf("%s >-< %s", conn.RemoteAddr(), cc.RemoteAddr()) | 		Debugf("%s >-< %s", conn.RemoteAddr(), cc.RemoteAddr()) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| type tunnelHandler struct { |  | ||||||
| 	pool    *ConnectorPool |  | ||||||
| 	ingress ingress.Ingress |  | ||||||
| 	options handler.Options |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newTunnelHandler(pool *ConnectorPool, ingress ingress.Ingress, opts ...handler.Option) handler.Handler { |  | ||||||
| 	options := handler.Options{} |  | ||||||
| 	for _, opt := range opts { |  | ||||||
| 		opt(&options) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return &tunnelHandler{ |  | ||||||
| 		pool:    pool, |  | ||||||
| 		ingress: ingress, |  | ||||||
| 		options: options, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (h *tunnelHandler) Init(md md.Metadata) (err error) { |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (h *tunnelHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { |  | ||||||
| 	defer conn.Close() |  | ||||||
|  |  | ||||||
| 	start := time.Now() |  | ||||||
| 	log := h.options.Logger.WithFields(map[string]any{ |  | ||||||
| 		"remote": conn.RemoteAddr().String(), |  | ||||||
| 		"local":  conn.LocalAddr().String(), |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) |  | ||||||
| 	defer func() { |  | ||||||
| 		log.WithFields(map[string]any{ |  | ||||||
| 			"duration": time.Since(start), |  | ||||||
| 		}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	var rw io.ReadWriter = conn |  | ||||||
| 	var host string |  | ||||||
| 	var protocol string |  | ||||||
| 	rw, host, protocol, _ = forward.Sniffing(ctx, conn) |  | ||||||
| 	h.options.Logger.Debugf("sniffing: host=%s, protocol=%s", host, protocol) |  | ||||||
|  |  | ||||||
| 	if protocol == forward.ProtoHTTP { |  | ||||||
| 		return h.handleHTTP(ctx, conn.RemoteAddr(), rw, log) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var tunnelID relay.TunnelID |  | ||||||
| 	if h.ingress != nil { |  | ||||||
| 		tunnelID = parseTunnelID(h.ingress.Get(ctx, host)) |  | ||||||
| 	} |  | ||||||
| 	if tunnelID.IsZero() { |  | ||||||
| 		err := fmt.Errorf("no route to host %s", host) |  | ||||||
| 		log.Error(err) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if tunnelID.IsPrivate() { |  | ||||||
| 		err := fmt.Errorf("access denied: tunnel %s is private for host %s", tunnelID, host) |  | ||||||
| 		log.Error(err) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	log = log.WithFields(map[string]any{ |  | ||||||
| 		"tunnel": tunnelID.String(), |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	cc, _, err := getTunnelConn("tcp", h.pool, tunnelID, 3, log) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Error(err) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer cc.Close() |  | ||||||
|  |  | ||||||
| 	log.Debugf("%s >> %s", conn.RemoteAddr(), cc.RemoteAddr()) |  | ||||||
|  |  | ||||||
| 	var features []relay.Feature |  | ||||||
| 	af := &relay.AddrFeature{} |  | ||||||
| 	af.ParseFrom(conn.RemoteAddr().String()) // client address |  | ||||||
| 	features = append(features, af) |  | ||||||
|  |  | ||||||
| 	if host != "" { |  | ||||||
| 		// target host |  | ||||||
| 		af := &relay.AddrFeature{} |  | ||||||
| 		af.ParseFrom(host) |  | ||||||
| 		features = append(features, af) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	resp := relay.Response{ |  | ||||||
| 		Version:  relay.Version1, |  | ||||||
| 		Status:   relay.StatusOK, |  | ||||||
| 		Features: features, |  | ||||||
| 	} |  | ||||||
| 	resp.WriteTo(cc) |  | ||||||
|  |  | ||||||
| 	t := time.Now() |  | ||||||
| 	log.Debugf("%s <-> %s", conn.RemoteAddr(), cc.RemoteAddr()) |  | ||||||
| 	xnet.Transport(rw, cc) |  | ||||||
| 	log.WithFields(map[string]any{ |  | ||||||
| 		"duration": time.Since(t), |  | ||||||
| 	}).Debugf("%s >-< %s", conn.RemoteAddr(), cc.RemoteAddr()) |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (h *tunnelHandler) handleHTTP(ctx context.Context, raddr net.Addr, rw io.ReadWriter, log logger.Logger) (err error) { |  | ||||||
| 	br := bufio.NewReader(rw) |  | ||||||
|  |  | ||||||
| 	for { |  | ||||||
| 		resp := &http.Response{ |  | ||||||
| 			ProtoMajor: 1, |  | ||||||
| 			ProtoMinor: 1, |  | ||||||
| 			Header:     http.Header{}, |  | ||||||
| 			StatusCode: http.StatusServiceUnavailable, |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		err = func() error { |  | ||||||
| 			req, err := http.ReadRequest(br) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			var tunnelID relay.TunnelID |  | ||||||
| 			if h.ingress != nil { |  | ||||||
| 				tunnelID = parseTunnelID(h.ingress.Get(ctx, req.Host)) |  | ||||||
| 			} |  | ||||||
| 			if tunnelID.IsZero() { |  | ||||||
| 				err := fmt.Errorf("no route to host %s", req.Host) |  | ||||||
| 				log.Error(err) |  | ||||||
| 				resp.StatusCode = http.StatusBadGateway |  | ||||||
| 				return resp.Write(rw) |  | ||||||
| 			} |  | ||||||
| 			if tunnelID.IsPrivate() { |  | ||||||
| 				err := fmt.Errorf("access denied: tunnel %s is private for host %s", tunnelID, req.Host) |  | ||||||
| 				log.Error(err) |  | ||||||
| 				resp.StatusCode = http.StatusBadGateway |  | ||||||
| 				return resp.Write(rw) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			log = log.WithFields(map[string]any{ |  | ||||||
| 				"host":   req.Host, |  | ||||||
| 				"tunnel": tunnelID.String(), |  | ||||||
| 			}) |  | ||||||
|  |  | ||||||
| 			cc, cid, err := getTunnelConn("tcp", h.pool, tunnelID, 3, log) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Error(err) |  | ||||||
| 				return resp.Write(rw) |  | ||||||
| 			} |  | ||||||
| 			defer cc.Close() |  | ||||||
|  |  | ||||||
| 			log.Debugf("new connection to tunnel %s(connector %s)", tunnelID, cid) |  | ||||||
|  |  | ||||||
| 			var features []relay.Feature |  | ||||||
| 			af := &relay.AddrFeature{} |  | ||||||
| 			af.ParseFrom(raddr.String()) |  | ||||||
| 			features = append(features, af) |  | ||||||
|  |  | ||||||
| 			if host := req.Host; host != "" { |  | ||||||
| 				if h, _, _ := net.SplitHostPort(host); h == "" { |  | ||||||
| 					host = net.JoinHostPort(host, "80") |  | ||||||
| 				} |  | ||||||
| 				af := &relay.AddrFeature{} |  | ||||||
| 				af.ParseFrom(host) |  | ||||||
| 				features = append(features, af) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			(&relay.Response{ |  | ||||||
| 				Version:  relay.Version1, |  | ||||||
| 				Status:   relay.StatusOK, |  | ||||||
| 				Features: features, |  | ||||||
| 			}).WriteTo(cc) |  | ||||||
|  |  | ||||||
| 			if log.IsLevelEnabled(logger.TraceLevel) { |  | ||||||
| 				dump, _ := httputil.DumpRequest(req, false) |  | ||||||
| 				log.Trace(string(dump)) |  | ||||||
| 			} |  | ||||||
| 			if err := req.Write(cc); err != nil { |  | ||||||
| 				log.Warnf("send request to tunnel %s: %v", tunnelID, err) |  | ||||||
| 				return resp.Write(rw) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			res, err := http.ReadResponse(bufio.NewReader(cc), req) |  | ||||||
| 			if err != nil { |  | ||||||
| 				log.Warnf("read response from tunnel %s: %v", tunnelID, err) |  | ||||||
| 				return resp.Write(rw) |  | ||||||
| 			} |  | ||||||
| 			defer res.Body.Close() |  | ||||||
|  |  | ||||||
| 			return res.Write(rw) |  | ||||||
| 		}() |  | ||||||
| 		if err != nil { |  | ||||||
| 			// log.Error(err) |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -7,9 +7,12 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/limiter/traffic" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/relay" | 	"github.com/go-gost/relay" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	netpkg "github.com/go-gost/x/internal/net" | 	netpkg "github.com/go-gost/x/internal/net" | ||||||
|  | 	"github.com/go-gost/x/limiter/traffic/wrapper" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (h *relayHandler) handleForward(ctx context.Context, conn net.Conn, network string, log logger.Logger) error { | func (h *relayHandler) handleForward(ctx context.Context, conn net.Conn, network string, log logger.Logger) error { | ||||||
| @ -84,9 +87,16 @@ func (h *relayHandler) handleForward(ctx context.Context, conn net.Conn, network | |||||||
| 		conn = rc | 		conn = rc | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	rw := wrapper.WrapReadWriter(h.options.Limiter, conn, conn.RemoteAddr().String(), | ||||||
|  | 		traffic.NetworkOption(network), | ||||||
|  | 		traffic.AddrOption(target.Addr), | ||||||
|  | 		traffic.ClientOption(string(ctxvalue.ClientIDFromContext(ctx))), | ||||||
|  | 		traffic.SrcOption(conn.RemoteAddr().String()), | ||||||
|  | 	) | ||||||
|  |  | ||||||
| 	t := time.Now() | 	t := time.Now() | ||||||
| 	log.Debugf("%s <-> %s", conn.RemoteAddr(), target.Addr) | 	log.Debugf("%s <-> %s", conn.RemoteAddr(), target.Addr) | ||||||
| 	netpkg.Transport(conn, cc) | 	netpkg.Transport(rw, cc) | ||||||
| 	log.WithFields(map[string]any{ | 	log.WithFields(map[string]any{ | ||||||
| 		"duration": time.Since(t), | 		"duration": time.Since(t), | ||||||
| 	}).Debugf("%s >-< %s", conn.RemoteAddr(), target.Addr) | 	}).Debugf("%s >-< %s", conn.RemoteAddr(), target.Addr) | ||||||
|  | |||||||
| @ -3,7 +3,6 @@ package relay | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" |  | ||||||
| 	"net" | 	"net" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"time" | 	"time" | ||||||
| @ -11,14 +10,11 @@ import ( | |||||||
| 	"github.com/go-gost/core/chain" | 	"github.com/go-gost/core/chain" | ||||||
| 	"github.com/go-gost/core/handler" | 	"github.com/go-gost/core/handler" | ||||||
| 	"github.com/go-gost/core/hop" | 	"github.com/go-gost/core/hop" | ||||||
| 	"github.com/go-gost/core/listener" |  | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
| 	"github.com/go-gost/core/service" | 	"github.com/go-gost/core/service" | ||||||
| 	"github.com/go-gost/relay" | 	"github.com/go-gost/relay" | ||||||
| 	xnet "github.com/go-gost/x/internal/net" | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" |  | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| 	xservice "github.com/go-gost/x/service" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| @ -38,7 +34,6 @@ type relayHandler struct { | |||||||
| 	md      metadata | 	md      metadata | ||||||
| 	options handler.Options | 	options handler.Options | ||||||
| 	ep      service.Service | 	ep      service.Service | ||||||
| 	pool    *ConnectorPool |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewHandler(opts ...handler.Option) handler.Handler { | func NewHandler(opts ...handler.Option) handler.Handler { | ||||||
| @ -49,7 +44,6 @@ func NewHandler(opts ...handler.Option) handler.Handler { | |||||||
|  |  | ||||||
| 	return &relayHandler{ | 	return &relayHandler{ | ||||||
| 		options: options, | 		options: options, | ||||||
| 		pool:    NewConnectorPool(), |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -63,68 +57,9 @@ func (h *relayHandler) Init(md md.Metadata) (err error) { | |||||||
| 		h.router = chain.NewRouter(chain.LoggerRouterOption(h.options.Logger)) | 		h.router = chain.NewRouter(chain.LoggerRouterOption(h.options.Logger)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err = h.initEntryPoint(); err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (h *relayHandler) initEntryPoint() (err error) { |  | ||||||
| 	if h.md.entryPoint == "" { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	network := "tcp" |  | ||||||
| 	if xnet.IsIPv4(h.md.entryPoint) { |  | ||||||
| 		network = "tcp4" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ln, err := net.Listen(network, h.md.entryPoint) |  | ||||||
| 	if err != nil { |  | ||||||
| 		h.options.Logger.Error(err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	serviceName := fmt.Sprintf("%s-ep-%s", h.options.Service, ln.Addr()) |  | ||||||
| 	log := h.options.Logger.WithFields(map[string]any{ |  | ||||||
| 		"service":  serviceName, |  | ||||||
| 		"listener": "tcp", |  | ||||||
| 		"handler":  "ep-tunnel", |  | ||||||
| 		"kind":     "service", |  | ||||||
| 	}) |  | ||||||
| 	epListener := newTCPListener(ln, |  | ||||||
| 		listener.AddrOption(h.md.entryPoint), |  | ||||||
| 		listener.ServiceOption(serviceName), |  | ||||||
| 		listener.ProxyProtocolOption(h.md.entryPointProxyProtocol), |  | ||||||
| 		listener.LoggerOption(log.WithFields(map[string]any{ |  | ||||||
| 			"kind": "listener", |  | ||||||
| 		})), |  | ||||||
| 	) |  | ||||||
| 	if err = epListener.Init(nil); err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	epHandler := newTunnelHandler( |  | ||||||
| 		h.pool, |  | ||||||
| 		h.md.ingress, |  | ||||||
| 		handler.ServiceOption(serviceName), |  | ||||||
| 		handler.LoggerOption(log.WithFields(map[string]any{ |  | ||||||
| 			"kind": "handler", |  | ||||||
| 		})), |  | ||||||
| 	) |  | ||||||
| 	if err = epHandler.Init(nil); err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	h.ep = xservice.NewService( |  | ||||||
| 		serviceName, epListener, epHandler, |  | ||||||
| 		xservice.LoggerOption(log), |  | ||||||
| 	) |  | ||||||
| 	go h.ep.Serve() |  | ||||||
| 	log.Infof("entrypoint: %s", h.ep.Addr()) |  | ||||||
|  |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Forward implements handler.Forwarder. | // Forward implements handler.Forwarder. | ||||||
| func (h *relayHandler) Forward(hop hop.Hop) { | func (h *relayHandler) Forward(hop hop.Hop) { | ||||||
| 	h.hop = hop | 	h.hop = hop | ||||||
| @ -148,8 +83,6 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handle | |||||||
| 		}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) | 		}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) | ||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
| 	ctx = auth_util.ContextWithClientAddr(ctx, auth_util.ClientAddr(conn.RemoteAddr().String())) |  | ||||||
|  |  | ||||||
| 	if !h.checkRateLimit(conn.RemoteAddr()) { | 	if !h.checkRateLimit(conn.RemoteAddr()) { | ||||||
| 		return ErrRateLimit | 		return ErrRateLimit | ||||||
| 	} | 	} | ||||||
| @ -179,7 +112,6 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handle | |||||||
| 	var user, pass string | 	var user, pass string | ||||||
| 	var address string | 	var address string | ||||||
| 	var networkID relay.NetworkID | 	var networkID relay.NetworkID | ||||||
| 	var tunnelID relay.TunnelID |  | ||||||
| 	for _, f := range req.Features { | 	for _, f := range req.Features { | ||||||
| 		switch f.Type() { | 		switch f.Type() { | ||||||
| 		case relay.FeatureUserAuth: | 		case relay.FeatureUserAuth: | ||||||
| @ -190,10 +122,6 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handle | |||||||
| 			if feature, _ := f.(*relay.AddrFeature); feature != nil { | 			if feature, _ := f.(*relay.AddrFeature); feature != nil { | ||||||
| 				address = net.JoinHostPort(feature.Host, strconv.Itoa(int(feature.Port))) | 				address = net.JoinHostPort(feature.Host, strconv.Itoa(int(feature.Port))) | ||||||
| 			} | 			} | ||||||
| 		case relay.FeatureTunnel: |  | ||||||
| 			if feature, _ := f.(*relay.TunnelFeature); feature != nil { |  | ||||||
| 				tunnelID = relay.NewTunnelID(feature.ID[:]) |  | ||||||
| 			} |  | ||||||
| 		case relay.FeatureNetwork: | 		case relay.FeatureNetwork: | ||||||
| 			if feature, _ := f.(*relay.NetworkFeature); feature != nil { | 			if feature, _ := f.(*relay.NetworkFeature); feature != nil { | ||||||
| 				networkID = feature.Network | 				networkID = feature.Network | ||||||
| @ -206,13 +134,13 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handle | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if h.options.Auther != nil { | 	if h.options.Auther != nil { | ||||||
| 		id, ok := h.options.Auther.Authenticate(ctx, user, pass) | 		clientID, ok := h.options.Auther.Authenticate(ctx, user, pass) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			resp.Status = relay.StatusUnauthorized | 			resp.Status = relay.StatusUnauthorized | ||||||
| 			resp.WriteTo(conn) | 			resp.WriteTo(conn) | ||||||
| 			return ErrUnauthorized | 			return ErrUnauthorized | ||||||
| 		} | 		} | ||||||
| 		ctx = auth_util.ContextWithID(ctx, auth_util.ID(id)) | 		ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	network := networkID.String() | 	network := networkID.String() | ||||||
| @ -230,16 +158,10 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handle | |||||||
| 	case 0, relay.CmdConnect: | 	case 0, relay.CmdConnect: | ||||||
| 		defer conn.Close() | 		defer conn.Close() | ||||||
|  |  | ||||||
| 		if !tunnelID.IsZero() { |  | ||||||
| 			return h.handleConnectTunnel(ctx, conn, network, address, tunnelID, log) |  | ||||||
| 		} |  | ||||||
| 		return h.handleConnect(ctx, conn, network, address, log) | 		return h.handleConnect(ctx, conn, network, address, log) | ||||||
| 	case relay.CmdBind: | 	case relay.CmdBind: | ||||||
| 		if !tunnelID.IsZero() { |  | ||||||
| 			return h.handleBindTunnel(ctx, conn, network, address, tunnelID, log) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		defer conn.Close() | 		defer conn.Close() | ||||||
|  |  | ||||||
| 		return h.handleBind(ctx, conn, network, address, log) | 		return h.handleBind(ctx, conn, network, address, log) | ||||||
| 	default: | 	default: | ||||||
| 		resp.Status = relay.StatusBadRequest | 		resp.Status = relay.StatusBadRequest | ||||||
|  | |||||||
| @ -2,16 +2,11 @@ package relay | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"math" | 	"math" | ||||||
| 	"strings" |  | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/go-gost/core/ingress" |  | ||||||
| 	"github.com/go-gost/core/logger" |  | ||||||
| 	mdata "github.com/go-gost/core/metadata" | 	mdata "github.com/go-gost/core/metadata" | ||||||
| 	mdutil "github.com/go-gost/core/metadata/util" | 	mdutil "github.com/go-gost/core/metadata/util" | ||||||
| 	xingress "github.com/go-gost/x/ingress" |  | ||||||
| 	"github.com/go-gost/x/internal/util/mux" | 	"github.com/go-gost/x/internal/util/mux" | ||||||
| 	"github.com/go-gost/x/registry" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type metadata struct { | type metadata struct { | ||||||
| @ -20,10 +15,6 @@ type metadata struct { | |||||||
| 	udpBufferSize int | 	udpBufferSize int | ||||||
| 	noDelay       bool | 	noDelay       bool | ||||||
| 	hash          string | 	hash          string | ||||||
| 	directTunnel            bool |  | ||||||
| 	entryPoint              string |  | ||||||
| 	entryPointProxyProtocol int |  | ||||||
| 	ingress                 ingress.Ingress |  | ||||||
| 	muxCfg        *mux.Config | 	muxCfg        *mux.Config | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -34,9 +25,6 @@ func (h *relayHandler) parseMetadata(md mdata.Metadata) (err error) { | |||||||
| 		udpBufferSize = "udpBufferSize" | 		udpBufferSize = "udpBufferSize" | ||||||
| 		noDelay       = "nodelay" | 		noDelay       = "nodelay" | ||||||
| 		hash          = "hash" | 		hash          = "hash" | ||||||
| 		entryPoint              = "entryPoint" |  | ||||||
| 		entryPointID            = "entryPoint.id" |  | ||||||
| 		entryPointProxyProtocol = "entryPoint.proxyProtocol" |  | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
| 	h.md.readTimeout = mdutil.GetDuration(md, readTimeout) | 	h.md.readTimeout = mdutil.GetDuration(md, readTimeout) | ||||||
| @ -51,33 +39,6 @@ func (h *relayHandler) parseMetadata(md mdata.Metadata) (err error) { | |||||||
|  |  | ||||||
| 	h.md.hash = mdutil.GetString(md, hash) | 	h.md.hash = mdutil.GetString(md, hash) | ||||||
|  |  | ||||||
| 	h.md.directTunnel = mdutil.GetBool(md, "tunnel.direct") |  | ||||||
| 	h.md.entryPoint = mdutil.GetString(md, entryPoint) |  | ||||||
| 	h.md.entryPointProxyProtocol = mdutil.GetInt(md, entryPointProxyProtocol) |  | ||||||
|  |  | ||||||
| 	h.md.ingress = registry.IngressRegistry().Get(mdutil.GetString(md, "ingress")) |  | ||||||
| 	if h.md.ingress == nil { |  | ||||||
| 		var rules []xingress.Rule |  | ||||||
| 		for _, s := range strings.Split(mdutil.GetString(md, "tunnel"), ",") { |  | ||||||
| 			ss := strings.SplitN(s, ":", 2) |  | ||||||
| 			if len(ss) != 2 { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			rules = append(rules, xingress.Rule{ |  | ||||||
| 				Hostname: ss[0], |  | ||||||
| 				Endpoint: ss[1], |  | ||||||
| 			}) |  | ||||||
| 		} |  | ||||||
| 		if len(rules) > 0 { |  | ||||||
| 			h.md.ingress = xingress.NewIngress( |  | ||||||
| 				xingress.RulesOption(rules), |  | ||||||
| 				xingress.LoggerOption(logger.Default().WithFields(map[string]any{ |  | ||||||
| 					"kind": "ingress", |  | ||||||
| 				})), |  | ||||||
| 			) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	h.md.muxCfg = &mux.Config{ | 	h.md.muxCfg = &mux.Config{ | ||||||
| 		Version:           mdutil.GetInt(md, "mux.version"), | 		Version:           mdutil.GetInt(md, "mux.version"), | ||||||
| 		KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"), | 		KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"), | ||||||
|  | |||||||
| @ -1,200 +0,0 @@ | |||||||
| package relay |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"net" |  | ||||||
| 	"sync" |  | ||||||
| 	"sync/atomic" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/go-gost/core/logger" |  | ||||||
| 	"github.com/go-gost/relay" |  | ||||||
| 	"github.com/go-gost/x/internal/util/mux" |  | ||||||
| 	"github.com/google/uuid" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type Connector struct { |  | ||||||
| 	id relay.ConnectorID |  | ||||||
| 	t  time.Time |  | ||||||
| 	s  *mux.Session |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewConnector(id relay.ConnectorID, s *mux.Session) *Connector { |  | ||||||
| 	c := &Connector{ |  | ||||||
| 		id: id, |  | ||||||
| 		t:  time.Now(), |  | ||||||
| 		s:  s, |  | ||||||
| 	} |  | ||||||
| 	go c.accept() |  | ||||||
| 	return c |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *Connector) accept() { |  | ||||||
| 	for { |  | ||||||
| 		conn, err := c.s.Accept() |  | ||||||
| 		if err != nil { |  | ||||||
| 			logger.Default().Errorf("connector %s: %v", c.id, err) |  | ||||||
| 			c.s.Close() |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		conn.Close() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *Connector) ID() relay.ConnectorID { |  | ||||||
| 	return c.id |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *Connector) Session() *mux.Session { |  | ||||||
| 	return c.s |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Tunnel struct { |  | ||||||
| 	id         relay.TunnelID |  | ||||||
| 	connectors []*Connector |  | ||||||
| 	t          time.Time |  | ||||||
| 	n          uint64 |  | ||||||
| 	mu         sync.RWMutex |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewTunnel(id relay.TunnelID) *Tunnel { |  | ||||||
| 	t := &Tunnel{ |  | ||||||
| 		id: id, |  | ||||||
| 		t:  time.Now(), |  | ||||||
| 	} |  | ||||||
| 	go t.clean() |  | ||||||
| 	return t |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *Tunnel) ID() relay.TunnelID { |  | ||||||
| 	return t.id |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *Tunnel) AddConnector(c *Connector) { |  | ||||||
| 	if c == nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	t.mu.Lock() |  | ||||||
| 	defer t.mu.Unlock() |  | ||||||
|  |  | ||||||
| 	t.connectors = append(t.connectors, c) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *Tunnel) GetConnector(network string) *Connector { |  | ||||||
| 	t.mu.RLock() |  | ||||||
| 	defer t.mu.RUnlock() |  | ||||||
|  |  | ||||||
| 	var connectors []*Connector |  | ||||||
| 	for _, c := range t.connectors { |  | ||||||
| 		if network == "udp" && c.id.IsUDP() || |  | ||||||
| 			network != "udp" && !c.id.IsUDP() { |  | ||||||
| 			connectors = append(connectors, c) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if len(connectors) == 0 { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	n := atomic.AddUint64(&t.n, 1) - 1 |  | ||||||
| 	return connectors[n%uint64(len(connectors))] |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (t *Tunnel) clean() { |  | ||||||
| 	ticker := time.NewTicker(30 * time.Second) |  | ||||||
| 	for range ticker.C { |  | ||||||
| 		t.mu.Lock() |  | ||||||
| 		var connectors []*Connector |  | ||||||
| 		for _, c := range t.connectors { |  | ||||||
| 			if c.Session().IsClosed() { |  | ||||||
| 				logger.Default().Debugf("remove tunnel %s connector %s", t.id, c.id) |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			connectors = append(connectors, c) |  | ||||||
| 		} |  | ||||||
| 		if len(connectors) != len(t.connectors) { |  | ||||||
| 			t.connectors = connectors |  | ||||||
| 		} |  | ||||||
| 		t.mu.Unlock() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type ConnectorPool struct { |  | ||||||
| 	tunnels map[string]*Tunnel |  | ||||||
| 	mu      sync.RWMutex |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewConnectorPool() *ConnectorPool { |  | ||||||
| 	return &ConnectorPool{ |  | ||||||
| 		tunnels: make(map[string]*Tunnel), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *ConnectorPool) Add(tid relay.TunnelID, c *Connector) { |  | ||||||
| 	p.mu.Lock() |  | ||||||
| 	defer p.mu.Unlock() |  | ||||||
|  |  | ||||||
| 	s := tid.String() |  | ||||||
|  |  | ||||||
| 	t := p.tunnels[s] |  | ||||||
| 	if t == nil { |  | ||||||
| 		t = NewTunnel(tid) |  | ||||||
| 		p.tunnels[s] = t |  | ||||||
| 	} |  | ||||||
| 	t.AddConnector(c) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *ConnectorPool) Get(network string, tid relay.TunnelID) *Connector { |  | ||||||
| 	if p == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	p.mu.RLock() |  | ||||||
| 	defer p.mu.RUnlock() |  | ||||||
|  |  | ||||||
| 	t := p.tunnels[tid.String()] |  | ||||||
| 	if t == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return t.GetConnector(network) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func parseTunnelID(s string) (tid relay.TunnelID) { |  | ||||||
| 	if s == "" { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	private := false |  | ||||||
| 	if s[0] == '$' { |  | ||||||
| 		private = true |  | ||||||
| 		s = s[1:] |  | ||||||
| 	} |  | ||||||
| 	uuid, _ := uuid.Parse(s) |  | ||||||
|  |  | ||||||
| 	if private { |  | ||||||
| 		return relay.NewPrivateTunnelID(uuid[:]) |  | ||||||
| 	} |  | ||||||
| 	return relay.NewTunnelID(uuid[:]) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func getTunnelConn(network string, pool *ConnectorPool, tid relay.TunnelID, retry int, log logger.Logger) (conn net.Conn, cid relay.ConnectorID, err error) { |  | ||||||
| 	if retry <= 0 { |  | ||||||
| 		retry = 1 |  | ||||||
| 	} |  | ||||||
| 	for i := 0; i < retry; i++ { |  | ||||||
| 		c := pool.Get(network, tid) |  | ||||||
| 		if c == nil { |  | ||||||
| 			err = fmt.Errorf("tunnel %s not available", tid.String()) |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		conn, err = c.Session().GetConn() |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Error(err) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		cid = c.id |  | ||||||
| 		break |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| @ -21,9 +21,9 @@ import ( | |||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
| 	dissector "github.com/go-gost/tls-dissector" | 	dissector "github.com/go-gost/tls-dissector" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	xio "github.com/go-gost/x/internal/io" | 	xio "github.com/go-gost/x/internal/io" | ||||||
| 	netpkg "github.com/go-gost/x/internal/net" | 	netpkg "github.com/go-gost/x/internal/net" | ||||||
| 	sx "github.com/go-gost/x/internal/util/selector" |  | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -123,7 +123,7 @@ func (h *sniHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, raddr net | |||||||
|  |  | ||||||
| 	switch h.md.hash { | 	switch h.md.hash { | ||||||
| 	case "host": | 	case "host": | ||||||
| 		ctx = sx.ContextWithHash(ctx, &sx.Hash{Source: host}) | 		ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: host}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cc, err := h.router.Dial(ctx, "tcp", host) | 	cc, err := h.router.Dial(ctx, "tcp", host) | ||||||
| @ -191,7 +191,7 @@ func (h *sniHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, raddr ne | |||||||
|  |  | ||||||
| 	switch h.md.hash { | 	switch h.md.hash { | ||||||
| 	case "host": | 	case "host": | ||||||
| 		ctx = sx.ContextWithHash(ctx, &sx.Hash{Source: host}) | 		ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: host}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cc, err := h.router.Dial(ctx, "tcp", host) | 	cc, err := h.router.Dial(ctx, "tcp", host) | ||||||
|  | |||||||
| @ -8,12 +8,13 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/go-gost/core/chain" | 	"github.com/go-gost/core/chain" | ||||||
| 	"github.com/go-gost/core/handler" | 	"github.com/go-gost/core/handler" | ||||||
|  | 	"github.com/go-gost/core/limiter/traffic" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
| 	"github.com/go-gost/gosocks4" | 	"github.com/go-gost/gosocks4" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	netpkg "github.com/go-gost/x/internal/net" | 	netpkg "github.com/go-gost/x/internal/net" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" | 	"github.com/go-gost/x/limiter/traffic/wrapper" | ||||||
| 	sx "github.com/go-gost/x/internal/util/selector" |  | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -82,8 +83,6 @@ func (h *socks4Handler) Handle(ctx context.Context, conn net.Conn, opts ...handl | |||||||
| 		conn.SetReadDeadline(time.Now().Add(h.md.readTimeout)) | 		conn.SetReadDeadline(time.Now().Add(h.md.readTimeout)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx = auth_util.ContextWithClientAddr(ctx, auth_util.ClientAddr(conn.RemoteAddr().String())) |  | ||||||
|  |  | ||||||
| 	req, err := gosocks4.ReadRequest(conn) | 	req, err := gosocks4.ReadRequest(conn) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error(err) | 		log.Error(err) | ||||||
| @ -100,7 +99,7 @@ func (h *socks4Handler) Handle(ctx context.Context, conn net.Conn, opts ...handl | |||||||
| 			log.Trace(resp) | 			log.Trace(resp) | ||||||
| 			return resp.Write(conn) | 			return resp.Write(conn) | ||||||
| 		} | 		} | ||||||
| 		ctx = auth_util.ContextWithID(ctx, auth_util.ID(id)) | 		ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(id)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	switch req.Cmd { | 	switch req.Cmd { | ||||||
| @ -132,7 +131,7 @@ func (h *socks4Handler) handleConnect(ctx context.Context, conn net.Conn, req *g | |||||||
|  |  | ||||||
| 	switch h.md.hash { | 	switch h.md.hash { | ||||||
| 	case "host": | 	case "host": | ||||||
| 		ctx = sx.ContextWithHash(ctx, &sx.Hash{Source: addr}) | 		ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cc, err := h.router.Dial(ctx, "tcp", addr) | 	cc, err := h.router.Dial(ctx, "tcp", addr) | ||||||
| @ -152,9 +151,16 @@ func (h *socks4Handler) handleConnect(ctx context.Context, conn net.Conn, req *g | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	rw := wrapper.WrapReadWriter(h.options.Limiter, conn, conn.RemoteAddr().String(), | ||||||
|  | 		traffic.NetworkOption("tcp"), | ||||||
|  | 		traffic.AddrOption(addr), | ||||||
|  | 		traffic.ClientOption(string(ctxvalue.ClientIDFromContext(ctx))), | ||||||
|  | 		traffic.SrcOption(conn.RemoteAddr().String()), | ||||||
|  | 	) | ||||||
|  |  | ||||||
| 	t := time.Now() | 	t := time.Now() | ||||||
| 	log.Infof("%s <-> %s", conn.RemoteAddr(), addr) | 	log.Infof("%s <-> %s", conn.RemoteAddr(), addr) | ||||||
| 	netpkg.Transport(conn, cc) | 	netpkg.Transport(rw, cc) | ||||||
| 	log.WithFields(map[string]any{ | 	log.WithFields(map[string]any{ | ||||||
| 		"duration": time.Since(t), | 		"duration": time.Since(t), | ||||||
| 	}).Infof("%s >-< %s", conn.RemoteAddr(), addr) | 	}).Infof("%s >-< %s", conn.RemoteAddr(), addr) | ||||||
|  | |||||||
| @ -6,10 +6,12 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/limiter/traffic" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/gosocks5" | 	"github.com/go-gost/gosocks5" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	netpkg "github.com/go-gost/x/internal/net" | 	netpkg "github.com/go-gost/x/internal/net" | ||||||
| 	sx "github.com/go-gost/x/internal/util/selector" | 	"github.com/go-gost/x/limiter/traffic/wrapper" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error { | func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error { | ||||||
| @ -28,7 +30,7 @@ func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, networ | |||||||
|  |  | ||||||
| 	switch h.md.hash { | 	switch h.md.hash { | ||||||
| 	case "host": | 	case "host": | ||||||
| 		ctx = sx.ContextWithHash(ctx, &sx.Hash{Source: address}) | 		ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: address}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cc, err := h.router.Dial(ctx, network, address) | 	cc, err := h.router.Dial(ctx, network, address) | ||||||
| @ -48,9 +50,16 @@ func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, networ | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	rw := wrapper.WrapReadWriter(h.options.Limiter, conn, conn.RemoteAddr().String(), | ||||||
|  | 		traffic.NetworkOption(network), | ||||||
|  | 		traffic.AddrOption(address), | ||||||
|  | 		traffic.ClientOption(string(ctxvalue.ClientIDFromContext(ctx))), | ||||||
|  | 		traffic.SrcOption(conn.RemoteAddr().String()), | ||||||
|  | 	) | ||||||
|  |  | ||||||
| 	t := time.Now() | 	t := time.Now() | ||||||
| 	log.Infof("%s <-> %s", conn.RemoteAddr(), address) | 	log.Infof("%s <-> %s", conn.RemoteAddr(), address) | ||||||
| 	netpkg.Transport(conn, cc) | 	netpkg.Transport(rw, cc) | ||||||
| 	log.WithFields(map[string]any{ | 	log.WithFields(map[string]any{ | ||||||
| 		"duration": time.Since(t), | 		"duration": time.Since(t), | ||||||
| 	}).Infof("%s >-< %s", conn.RemoteAddr(), address) | 	}).Infof("%s >-< %s", conn.RemoteAddr(), address) | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ import ( | |||||||
| 	"github.com/go-gost/core/handler" | 	"github.com/go-gost/core/handler" | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
| 	"github.com/go-gost/gosocks5" | 	"github.com/go-gost/gosocks5" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	"github.com/go-gost/x/internal/util/socks" | 	"github.com/go-gost/x/internal/util/socks" | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| ) | ) | ||||||
| @ -95,7 +95,9 @@ func (h *socks5Handler) Handle(ctx context.Context, conn net.Conn, opts ...handl | |||||||
| 	} | 	} | ||||||
| 	log.Trace(req) | 	log.Trace(req) | ||||||
|  |  | ||||||
| 	ctx = auth_util.ContextWithID(ctx, auth_util.ID(sc.ID())) | 	if clientID := sc.ID(); clientID != "" { | ||||||
|  | 		ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	conn = sc | 	conn = sc | ||||||
| 	conn.SetReadDeadline(time.Time{}) | 	conn.SetReadDeadline(time.Time{}) | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ import ( | |||||||
| 	"github.com/go-gost/core/auth" | 	"github.com/go-gost/core/auth" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/gosocks5" | 	"github.com/go-gost/gosocks5" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	"github.com/go-gost/x/internal/util/socks" | 	"github.com/go-gost/x/internal/util/socks" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -70,7 +70,7 @@ func (s *serverSelector) OnSelected(method uint8, conn net.Conn) (string, net.Co | |||||||
| 		var id string | 		var id string | ||||||
| 		if s.Authenticator != nil { | 		if s.Authenticator != nil { | ||||||
| 			var ok bool | 			var ok bool | ||||||
| 			ctx := auth_util.ContextWithClientAddr(context.Background(), auth_util.ClientAddr(conn.RemoteAddr().String())) | 			ctx := ctxvalue.ContextWithClientAddr(context.Background(), ctxvalue.ClientAddr(conn.RemoteAddr().String())) | ||||||
| 			id, ok = s.Authenticator.Authenticate(ctx, req.Username, req.Password) | 			id, ok = s.Authenticator.Authenticate(ctx, req.Username, req.Password) | ||||||
| 			if !ok { | 			if !ok { | ||||||
| 				resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure) | 				resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure) | ||||||
|  | |||||||
| @ -72,7 +72,7 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn, log logger | |||||||
| 		WithLogger(log) | 		WithLogger(log) | ||||||
| 	r.SetBufferSize(h.md.udpBufferSize) | 	r.SetBufferSize(h.md.udpBufferSize) | ||||||
|  |  | ||||||
| 	go r.Run() | 	go r.Run(ctx) | ||||||
|  |  | ||||||
| 	t := time.Now() | 	t := time.Now() | ||||||
| 	log.Debugf("%s <-> %s", conn.RemoteAddr(), cc.LocalAddr()) | 	log.Debugf("%s <-> %s", conn.RemoteAddr(), cc.LocalAddr()) | ||||||
|  | |||||||
| @ -63,7 +63,7 @@ func (h *socks5Handler) handleUDPTun(ctx context.Context, conn net.Conn, network | |||||||
|  |  | ||||||
| 	t := time.Now() | 	t := time.Now() | ||||||
| 	log.Debugf("%s <-> %s", conn.RemoteAddr(), pc.LocalAddr()) | 	log.Debugf("%s <-> %s", conn.RemoteAddr(), pc.LocalAddr()) | ||||||
| 	r.Run() | 	r.Run(ctx) | ||||||
| 	log.WithFields(map[string]any{ | 	log.WithFields(map[string]any{ | ||||||
| 		"duration": time.Since(t), | 		"duration": time.Since(t), | ||||||
| 	}).Debugf("%s >-< %s", conn.RemoteAddr(), pc.LocalAddr()) | 	}).Debugf("%s >-< %s", conn.RemoteAddr(), pc.LocalAddr()) | ||||||
|  | |||||||
| @ -10,8 +10,8 @@ import ( | |||||||
| 	"github.com/go-gost/core/handler" | 	"github.com/go-gost/core/handler" | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
| 	"github.com/go-gost/gosocks5" | 	"github.com/go-gost/gosocks5" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	netpkg "github.com/go-gost/x/internal/net" | 	netpkg "github.com/go-gost/x/internal/net" | ||||||
| 	sx "github.com/go-gost/x/internal/util/selector" |  | ||||||
| 	"github.com/go-gost/x/internal/util/ss" | 	"github.com/go-gost/x/internal/util/ss" | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| 	"github.com/shadowsocks/go-shadowsocks2/core" | 	"github.com/shadowsocks/go-shadowsocks2/core" | ||||||
| @ -108,7 +108,7 @@ func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.H | |||||||
|  |  | ||||||
| 	switch h.md.hash { | 	switch h.md.hash { | ||||||
| 	case "host": | 	case "host": | ||||||
| 		ctx = sx.ContextWithHash(ctx, &sx.Hash{Source: addr.String()}) | 		ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr.String()}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cc, err := h.router.Dial(ctx, "tcp", addr.String()) | 	cc, err := h.router.Dial(ctx, "tcp", addr.String()) | ||||||
|  | |||||||
| @ -44,7 +44,7 @@ func (h *tunHandler) handleClient(ctx context.Context, conn net.Conn, raddr stri | |||||||
| 			ctx, cancel := context.WithCancel(ctx) | 			ctx, cancel := context.WithCancel(ctx) | ||||||
| 			defer cancel() | 			defer cancel() | ||||||
|  |  | ||||||
| 			go h.keepAlive(ctx, cc, ips) | 			go h.keepalive(ctx, cc, ips) | ||||||
|  |  | ||||||
| 			return h.transportClient(conn, cc, log) | 			return h.transportClient(conn, cc, log) | ||||||
| 		}() | 		}() | ||||||
| @ -57,7 +57,7 @@ func (h *tunHandler) handleClient(ctx context.Context, conn net.Conn, raddr stri | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (h *tunHandler) keepAlive(ctx context.Context, conn net.Conn, ips []net.IP) { | func (h *tunHandler) keepalive(ctx context.Context, conn net.Conn, ips []net.IP) { | ||||||
| 	// handshake | 	// handshake | ||||||
| 	keepAliveData := bufpool.Get(keepAliveHeaderLength + len(ips)*net.IPv6len) | 	keepAliveData := bufpool.Get(keepAliveHeaderLength + len(ips)*net.IPv6len) | ||||||
| 	defer bufpool.Put(keepAliveData) | 	defer bufpool.Put(keepAliveData) | ||||||
| @ -130,7 +130,7 @@ func (h *tunHandler) transportClient(tun io.ReadWriter, conn net.Conn, log logge | |||||||
| 						ipProtocol(waterutil.IPProtocol(header.NextHeader)), | 						ipProtocol(waterutil.IPProtocol(header.NextHeader)), | ||||||
| 						header.PayloadLen, header.TrafficClass) | 						header.PayloadLen, header.TrafficClass) | ||||||
| 				} else { | 				} else { | ||||||
| 					log.Warn("unknown packet, discarded") | 					log.Warnf("unknown packet, discarded(%d)", n) | ||||||
| 					return nil | 					return nil | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | |||||||
| @ -9,9 +9,10 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/go-gost/core/chain" | 	"github.com/go-gost/core/chain" | ||||||
| 	"github.com/go-gost/core/hop" |  | ||||||
| 	"github.com/go-gost/core/handler" | 	"github.com/go-gost/core/handler" | ||||||
|  | 	"github.com/go-gost/core/hop" | ||||||
| 	md "github.com/go-gost/core/metadata" | 	md "github.com/go-gost/core/metadata" | ||||||
|  | 	"github.com/go-gost/core/router" | ||||||
| 	tun_util "github.com/go-gost/x/internal/util/tun" | 	tun_util "github.com/go-gost/x/internal/util/tun" | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| 	"github.com/songgao/water/waterutil" | 	"github.com/songgao/water/waterutil" | ||||||
| @ -108,17 +109,20 @@ func (h *tunHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler. | |||||||
| 	return h.handleServer(ctx, conn, config, log) | 	return h.handleServer(ctx, conn, config, log) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (h *tunHandler) findRouteFor(dst net.IP, routes ...tun_util.Route) net.Addr { | func (h *tunHandler) findRouteFor(ctx context.Context, dst net.IP, router router.Router) net.Addr { | ||||||
| 	if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok { | 	if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok { | ||||||
| 		return v.(net.Addr) | 		return v.(net.Addr) | ||||||
| 	} | 	} | ||||||
| 	for _, route := range routes { |  | ||||||
| 		if route.Net.Contains(dst) && route.Gateway != nil { | 	if router == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if route := router.GetRoute(ctx, dst); route != nil && route.Gateway != nil { | ||||||
| 		if v, ok := h.routes.Load(ipToTunRouteKey(route.Gateway)); ok { | 		if v, ok := h.routes.Load(ipToTunRouteKey(route.Gateway)); ok { | ||||||
| 			return v.(net.Addr) | 			return v.(net.Addr) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	} |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -78,11 +78,11 @@ func (h *tunHandler) transportServer(ctx context.Context, tun io.ReadWriter, con | |||||||
| 						ipProtocol(waterutil.IPProtocol(header.NextHeader)), | 						ipProtocol(waterutil.IPProtocol(header.NextHeader)), | ||||||
| 						header.PayloadLen, header.TrafficClass) | 						header.PayloadLen, header.TrafficClass) | ||||||
| 				} else { | 				} else { | ||||||
| 					log.Warn("unknown packet, discarded") | 					log.Warnf("unknown packet, discarded(%d)", n) | ||||||
| 					return nil | 					return nil | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				addr := h.findRouteFor(dst, config.Routes...) | 				addr := h.findRouteFor(ctx, dst, config.Router) | ||||||
| 				if addr == nil { | 				if addr == nil { | ||||||
| 					log.Debugf("no route for %s -> %s, packet discarded", src, dst) | 					log.Debugf("no route for %s -> %s, packet discarded", src, dst) | ||||||
| 					return nil | 					return nil | ||||||
| @ -199,11 +199,11 @@ func (h *tunHandler) transportServer(ctx context.Context, tun io.ReadWriter, con | |||||||
| 						ipProtocol(waterutil.IPProtocol(header.NextHeader)), | 						ipProtocol(waterutil.IPProtocol(header.NextHeader)), | ||||||
| 						header.PayloadLen, header.TrafficClass) | 						header.PayloadLen, header.TrafficClass) | ||||||
| 				} else { | 				} else { | ||||||
| 					log.Warn("unknown packet, discarded") | 					log.Warnf("unknown packet, discarded(%d): % x", n, b[:n]) | ||||||
| 					return nil | 					return nil | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if addr := h.findRouteFor(dst, config.Routes...); addr != nil { | 				if addr := h.findRouteFor(ctx, dst, config.Router); addr != nil { | ||||||
| 					log.Debugf("find route: %s -> %s", dst, addr) | 					log.Debugf("find route: %s -> %s", dst, addr) | ||||||
|  |  | ||||||
| 					_, err := conn.WriteTo(b[:n], addr) | 					_, err := conn.WriteTo(b[:n], addr) | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ import ( | |||||||
| 	"encoding/hex" | 	"encoding/hex" | ||||||
| 	"net" | 	"net" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/ingress" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/core/sd" | 	"github.com/go-gost/core/sd" | ||||||
| 	"github.com/go-gost/relay" | 	"github.com/go-gost/relay" | ||||||
| @ -56,7 +57,10 @@ func (h *tunnelHandler) handleBind(ctx context.Context, conn net.Conn, network, | |||||||
|  |  | ||||||
| 	h.pool.Add(tunnelID, NewConnector(connectorID, tunnelID, h.id, session, h.md.sd), h.md.tunnelTTL) | 	h.pool.Add(tunnelID, NewConnector(connectorID, tunnelID, h.id, session, h.md.sd), h.md.tunnelTTL) | ||||||
| 	if h.md.ingress != nil { | 	if h.md.ingress != nil { | ||||||
| 		h.md.ingress.Set(ctx, addr, tunnelID.String()) | 		h.md.ingress.SetRule(ctx, &ingress.Rule{ | ||||||
|  | 			Hostname: addr, | ||||||
|  | 			Endpoint: tunnelID.String(), | ||||||
|  | 		}) | ||||||
| 	} | 	} | ||||||
| 	if h.md.sd != nil { | 	if h.md.sd != nil { | ||||||
| 		err := h.md.sd.Register(ctx, &sd.Service{ | 		err := h.md.sd.Register(ctx, &sd.Service{ | ||||||
|  | |||||||
| @ -6,9 +6,12 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/limiter/traffic" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/relay" | 	"github.com/go-gost/relay" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	xnet "github.com/go-gost/x/internal/net" | 	xnet "github.com/go-gost/x/internal/net" | ||||||
|  | 	"github.com/go-gost/x/limiter/traffic/wrapper" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (h *tunnelHandler) handleConnect(ctx context.Context, req *relay.Request, conn net.Conn, network, srcAddr string, dstAddr string, tunnelID relay.TunnelID, log logger.Logger) error { | func (h *tunnelHandler) handleConnect(ctx context.Context, req *relay.Request, conn net.Conn, network, srcAddr string, dstAddr string, tunnelID relay.TunnelID, log logger.Logger) error { | ||||||
| @ -41,7 +44,9 @@ func (h *tunnelHandler) handleConnect(ctx context.Context, req *relay.Request, c | |||||||
| 	if !h.md.directTunnel { | 	if !h.md.directTunnel { | ||||||
| 		var tid relay.TunnelID | 		var tid relay.TunnelID | ||||||
| 		if ingress := h.md.ingress; ingress != nil && host != "" { | 		if ingress := h.md.ingress; ingress != nil && host != "" { | ||||||
| 			tid = parseTunnelID(ingress.Get(ctx, host)) | 			if rule := ingress.GetRule(ctx, host); rule != nil { | ||||||
|  | 				tid = parseTunnelID(rule.Endpoint) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 		if !tid.Equal(tunnelID) { | 		if !tid.Equal(tunnelID) { | ||||||
| 			resp.Status = relay.StatusHostUnreachable | 			resp.Status = relay.StatusHostUnreachable | ||||||
| @ -95,9 +100,16 @@ func (h *tunnelHandler) handleConnect(ctx context.Context, req *relay.Request, c | |||||||
| 		req.WriteTo(cc) | 		req.WriteTo(cc) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	rw := wrapper.WrapReadWriter(h.options.Limiter, conn, tunnelID.String(), | ||||||
|  | 		traffic.NetworkOption(network), | ||||||
|  | 		traffic.AddrOption(dstAddr), | ||||||
|  | 		traffic.ClientOption(string(ctxvalue.ClientIDFromContext(ctx))), | ||||||
|  | 		traffic.SrcOption(conn.RemoteAddr().String()), | ||||||
|  | 	) | ||||||
|  |  | ||||||
| 	t := time.Now() | 	t := time.Now() | ||||||
| 	log.Debugf("%s <-> %s", conn.RemoteAddr(), cc.RemoteAddr()) | 	log.Debugf("%s <-> %s", conn.RemoteAddr(), cc.RemoteAddr()) | ||||||
| 	xnet.Transport(conn, cc) | 	xnet.Transport(rw, cc) | ||||||
| 	log.WithFields(map[string]any{ | 	log.WithFields(map[string]any{ | ||||||
| 		"duration": time.Since(t), | 		"duration": time.Since(t), | ||||||
| 	}).Debugf("%s >-< %s", conn.RemoteAddr(), cc.RemoteAddr()) | 	}).Debugf("%s >-< %s", conn.RemoteAddr(), cc.RemoteAddr()) | ||||||
|  | |||||||
| @ -85,7 +85,9 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error { | |||||||
|  |  | ||||||
| 			var tunnelID relay.TunnelID | 			var tunnelID relay.TunnelID | ||||||
| 			if ep.ingress != nil { | 			if ep.ingress != nil { | ||||||
| 				tunnelID = parseTunnelID(ep.ingress.Get(ctx, req.Host)) | 				if rule := ep.ingress.GetRule(ctx, req.Host); rule != nil { | ||||||
|  | 					tunnelID = parseTunnelID(rule.Endpoint) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 			if tunnelID.IsZero() { | 			if tunnelID.IsZero() { | ||||||
| 				err := fmt.Errorf("no route to host %s", req.Host) | 				err := fmt.Errorf("no route to host %s", req.Host) | ||||||
|  | |||||||
| @ -15,8 +15,8 @@ import ( | |||||||
| 	"github.com/go-gost/core/recorder" | 	"github.com/go-gost/core/recorder" | ||||||
| 	"github.com/go-gost/core/service" | 	"github.com/go-gost/core/service" | ||||||
| 	"github.com/go-gost/relay" | 	"github.com/go-gost/relay" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	xnet "github.com/go-gost/x/internal/net" | 	xnet "github.com/go-gost/x/internal/net" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" |  | ||||||
| 	xrecorder "github.com/go-gost/x/recorder" | 	xrecorder "github.com/go-gost/x/recorder" | ||||||
| 	"github.com/go-gost/x/registry" | 	"github.com/go-gost/x/registry" | ||||||
| 	xservice "github.com/go-gost/x/service" | 	xservice "github.com/go-gost/x/service" | ||||||
| @ -169,8 +169,6 @@ func (h *tunnelHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl | |||||||
| 		}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) | 		}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) | ||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
| 	ctx = auth_util.ContextWithClientAddr(ctx, auth_util.ClientAddr(conn.RemoteAddr().String())) |  | ||||||
|  |  | ||||||
| 	if !h.checkRateLimit(conn.RemoteAddr()) { | 	if !h.checkRateLimit(conn.RemoteAddr()) { | ||||||
| 		return ErrRateLimit | 		return ErrRateLimit | ||||||
| 	} | 	} | ||||||
| @ -238,13 +236,13 @@ func (h *tunnelHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if h.options.Auther != nil { | 	if h.options.Auther != nil { | ||||||
| 		id, ok := h.options.Auther.Authenticate(ctx, user, pass) | 		clientID, ok := h.options.Auther.Authenticate(ctx, user, pass) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			resp.Status = relay.StatusUnauthorized | 			resp.Status = relay.StatusUnauthorized | ||||||
| 			resp.WriteTo(conn) | 			resp.WriteTo(conn) | ||||||
| 			return ErrUnauthorized | 			return ErrUnauthorized | ||||||
| 		} | 		} | ||||||
| 		ctx = auth_util.ContextWithID(ctx, auth_util.ID(id)) | 		ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	switch req.Cmd & relay.CmdMask { | 	switch req.Cmd & relay.CmdMask { | ||||||
|  | |||||||
| @ -45,13 +45,13 @@ func (h *tunnelHandler) parseMetadata(md mdata.Metadata) (err error) { | |||||||
|  |  | ||||||
| 	h.md.ingress = registry.IngressRegistry().Get(mdutil.GetString(md, "ingress")) | 	h.md.ingress = registry.IngressRegistry().Get(mdutil.GetString(md, "ingress")) | ||||||
| 	if h.md.ingress == nil { | 	if h.md.ingress == nil { | ||||||
| 		var rules []xingress.Rule | 		var rules []*ingress.Rule | ||||||
| 		for _, s := range strings.Split(mdutil.GetString(md, "tunnel"), ",") { | 		for _, s := range strings.Split(mdutil.GetString(md, "tunnel"), ",") { | ||||||
| 			ss := strings.SplitN(s, ":", 2) | 			ss := strings.SplitN(s, ":", 2) | ||||||
| 			if len(ss) != 2 { | 			if len(ss) != 2 { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			rules = append(rules, xingress.Rule{ | 			rules = append(rules, &ingress.Rule{ | ||||||
| 				Hostname: ss[0], | 				Hostname: ss[0], | ||||||
| 				Endpoint: ss[1], | 				Endpoint: ss[1], | ||||||
| 			}) | 			}) | ||||||
| @ -61,6 +61,7 @@ func (h *tunnelHandler) parseMetadata(md mdata.Metadata) (err error) { | |||||||
| 				xingress.RulesOption(rules), | 				xingress.RulesOption(rules), | ||||||
| 				xingress.LoggerOption(logger.Default().WithFields(map[string]any{ | 				xingress.LoggerOption(logger.Default().WithFields(map[string]any{ | ||||||
| 					"kind":    "ingress", | 					"kind":    "ingress", | ||||||
|  | 					"ingress": "@internal", | ||||||
| 				})), | 				})), | ||||||
| 			) | 			) | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -13,8 +13,8 @@ import ( | |||||||
| 	"github.com/go-gost/plugin/hop/proto" | 	"github.com/go-gost/plugin/hop/proto" | ||||||
| 	"github.com/go-gost/x/config" | 	"github.com/go-gost/x/config" | ||||||
| 	node_parser "github.com/go-gost/x/config/parsing/node" | 	node_parser "github.com/go-gost/x/config/parsing/node" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	"github.com/go-gost/x/internal/plugin" | 	"github.com/go-gost/x/internal/plugin" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" |  | ||||||
| 	"google.golang.org/grpc" | 	"google.golang.org/grpc" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -68,7 +68,8 @@ func (p *grpcPlugin) Select(ctx context.Context, opts ...hop.SelectOption) *chai | |||||||
| 			Addr:    options.Addr, | 			Addr:    options.Addr, | ||||||
| 			Host:    options.Host, | 			Host:    options.Host, | ||||||
| 			Path:    options.Path, | 			Path:    options.Path, | ||||||
| 			Client:  string(auth_util.IDFromContext(ctx)), | 			Client:  string(ctxvalue.ClientIDFromContext(ctx)), | ||||||
|  | 			Src:     string(ctxvalue.ClientAddrFromContext(ctx)), | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		p.log.Error(err) | 		p.log.Error(err) | ||||||
| @ -106,6 +107,7 @@ type httpPluginRequest struct { | |||||||
| 	Host    string `json:"host"` | 	Host    string `json:"host"` | ||||||
| 	Path    string `json:"path"` | 	Path    string `json:"path"` | ||||||
| 	Client  string `json:"client"` | 	Client  string `json:"client"` | ||||||
|  | 	Src     string `json:"src"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type httpPluginResponse struct { | type httpPluginResponse struct { | ||||||
| @ -154,7 +156,8 @@ func (p *httpPlugin) Select(ctx context.Context, opts ...hop.SelectOption) *chai | |||||||
| 		Addr:    options.Addr, | 		Addr:    options.Addr, | ||||||
| 		Host:    options.Host, | 		Host:    options.Host, | ||||||
| 		Path:    options.Path, | 		Path:    options.Path, | ||||||
| 		Client:  string(auth_util.IDFromContext(ctx)), | 		Client:  string(ctxvalue.ClientIDFromContext(ctx)), | ||||||
|  | 		Src:     string(ctxvalue.ClientAddrFromContext(ctx)), | ||||||
| 	} | 	} | ||||||
| 	v, err := json.Marshal(&rb) | 	v, err := json.Marshal(&rb) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | |||||||
| @ -11,8 +11,8 @@ import ( | |||||||
| 	"github.com/go-gost/core/hosts" | 	"github.com/go-gost/core/hosts" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/plugin/hosts/proto" | 	"github.com/go-gost/plugin/hosts/proto" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	"github.com/go-gost/x/internal/plugin" | 	"github.com/go-gost/x/internal/plugin" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" |  | ||||||
| 	"google.golang.org/grpc" | 	"google.golang.org/grpc" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -58,7 +58,7 @@ func (p *grpcPlugin) Lookup(ctx context.Context, network, host string, opts ...h | |||||||
| 		&proto.LookupRequest{ | 		&proto.LookupRequest{ | ||||||
| 			Network: network, | 			Network: network, | ||||||
| 			Host:    host, | 			Host:    host, | ||||||
| 			Client:  string(auth_util.IDFromContext(ctx)), | 			Client:  string(ctxvalue.ClientIDFromContext(ctx)), | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		p.log.Error(err) | 		p.log.Error(err) | ||||||
| @ -126,7 +126,7 @@ func (p *httpPlugin) Lookup(ctx context.Context, network, host string, opts ...h | |||||||
| 	rb := httpPluginRequest{ | 	rb := httpPluginRequest{ | ||||||
| 		Network: network, | 		Network: network, | ||||||
| 		Host:    host, | 		Host:    host, | ||||||
| 		Client:  string(auth_util.IDFromContext(ctx)), | 		Client:  string(ctxvalue.ClientIDFromContext(ctx)), | ||||||
| 	} | 	} | ||||||
| 	v, err := json.Marshal(&rb) | 	v, err := json.Marshal(&rb) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | |||||||
| @ -14,13 +14,8 @@ import ( | |||||||
| 	"github.com/go-gost/x/internal/loader" | 	"github.com/go-gost/x/internal/loader" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Rule struct { |  | ||||||
| 	Hostname string |  | ||||||
| 	Endpoint string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type options struct { | type options struct { | ||||||
| 	rules       []Rule | 	rules       []*ingress.Rule | ||||||
| 	fileLoader  loader.Loader | 	fileLoader  loader.Loader | ||||||
| 	redisLoader loader.Loader | 	redisLoader loader.Loader | ||||||
| 	httpLoader  loader.Loader | 	httpLoader  loader.Loader | ||||||
| @ -30,7 +25,7 @@ type options struct { | |||||||
|  |  | ||||||
| type Option func(opts *options) | type Option func(opts *options) | ||||||
|  |  | ||||||
| func RulesOption(rules []Rule) Option { | func RulesOption(rules []*ingress.Rule) Option { | ||||||
| 	return func(opts *options) { | 	return func(opts *options) { | ||||||
| 		opts.rules = rules | 		opts.rules = rules | ||||||
| 	} | 	} | ||||||
| @ -67,7 +62,7 @@ func LoggerOption(logger logger.Logger) Option { | |||||||
| } | } | ||||||
|  |  | ||||||
| type localIngress struct { | type localIngress struct { | ||||||
| 	rules      map[string]Rule | 	rules      map[string]*ingress.Rule | ||||||
| 	cancelFunc context.CancelFunc | 	cancelFunc context.CancelFunc | ||||||
| 	options    options | 	options    options | ||||||
| 	mu         sync.RWMutex | 	mu         sync.RWMutex | ||||||
| @ -119,9 +114,9 @@ func (ing *localIngress) periodReload(ctx context.Context) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (ing *localIngress) reload(ctx context.Context) error { | func (ing *localIngress) reload(ctx context.Context) error { | ||||||
| 	rules := make(map[string]Rule) | 	rules := make(map[string]*ingress.Rule) | ||||||
|  |  | ||||||
| 	fn := func(rule Rule) { | 	fn := func(rule *ingress.Rule) { | ||||||
| 		if rule.Hostname == "" || rule.Endpoint == "" { | 		if rule.Hostname == "" || rule.Endpoint == "" { | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| @ -152,7 +147,7 @@ func (ing *localIngress) reload(ctx context.Context) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (ing *localIngress) load(ctx context.Context) (rules []Rule, err error) { | func (ing *localIngress) load(ctx context.Context) (rules []*ingress.Rule, err error) { | ||||||
| 	if ing.options.fileLoader != nil { | 	if ing.options.fileLoader != nil { | ||||||
| 		if lister, ok := ing.options.fileLoader.(loader.Lister); ok { | 		if lister, ok := ing.options.fileLoader.(loader.Lister); ok { | ||||||
| 			list, er := lister.List(ctx) | 			list, er := lister.List(ctx) | ||||||
| @ -203,7 +198,7 @@ func (ing *localIngress) load(ctx context.Context) (rules []Rule, err error) { | |||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func (ing *localIngress) parseRules(r io.Reader) (rules []Rule, err error) { | func (ing *localIngress) parseRules(r io.Reader) (rules []*ingress.Rule, err error) { | ||||||
| 	if r == nil { | 	if r == nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @ -219,9 +214,9 @@ func (ing *localIngress) parseRules(r io.Reader) (rules []Rule, err error) { | |||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func (ing *localIngress) Get(ctx context.Context, host string, opts ...ingress.GetOption) string { | func (ing *localIngress) GetRule(ctx context.Context, host string, opts ...ingress.Option) *ingress.Rule { | ||||||
| 	if host == "" || ing == nil { | 	if host == "" || ing == nil { | ||||||
| 		return "" | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// try to strip the port | 	// try to strip the port | ||||||
| @ -229,22 +224,18 @@ func (ing *localIngress) Get(ctx context.Context, host string, opts ...ingress.G | |||||||
| 		host = v | 		host = v | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if ing == nil || len(ing.rules) == 0 { |  | ||||||
| 		return "" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ing.options.logger.Debugf("ingress: lookup %s", host) | 	ing.options.logger.Debugf("ingress: lookup %s", host) | ||||||
| 	ep := ing.lookup(host) | 	ep := ing.lookup(host) | ||||||
| 	if ep == "" { | 	if ep == nil { | ||||||
| 		ep = ing.lookup("." + host) | 		ep = ing.lookup("." + host) | ||||||
| 	} | 	} | ||||||
| 	if ep == "" { | 	if ep == nil { | ||||||
| 		s := host | 		s := host | ||||||
| 		for { | 		for { | ||||||
| 			if index := strings.IndexByte(s, '.'); index > 0 { | 			if index := strings.IndexByte(s, '.'); index > 0 { | ||||||
| 				ep = ing.lookup(s[index:]) | 				ep = ing.lookup(s[index:]) | ||||||
| 				s = s[index+1:] | 				s = s[index+1:] | ||||||
| 				if ep == "" { | 				if ep == nil { | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @ -252,29 +243,29 @@ func (ing *localIngress) Get(ctx context.Context, host string, opts ...ingress.G | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if ep != "" { | 	if ep != nil { | ||||||
| 		ing.options.logger.Debugf("ingress: %s -> %s", host, ep) | 		ing.options.logger.Debugf("ingress: %s -> %s", host, ep) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return ep | 	return ep | ||||||
| } | } | ||||||
|  |  | ||||||
| func (ing *localIngress) Set(ctx context.Context, host, endpoint string, opts ...ingress.SetOption) { | func (ing *localIngress) SetRule(ctx context.Context, rule *ingress.Rule, opts ...ingress.Option) bool { | ||||||
|  | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
| func (ing *localIngress) lookup(host string) string { | func (ing *localIngress) lookup(host string) *ingress.Rule { | ||||||
| 	if ing == nil || len(ing.rules) == 0 { | 	if ing == nil { | ||||||
| 		return "" | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ing.mu.RLock() | 	ing.mu.RLock() | ||||||
| 	defer ing.mu.RUnlock() | 	defer ing.mu.RUnlock() | ||||||
|  |  | ||||||
| 	return ing.rules[host].Endpoint | 	return ing.rules[host] | ||||||
| } | } | ||||||
|  |  | ||||||
| func (ing *localIngress) parseLine(s string) (rule Rule) { | func (ing *localIngress) parseLine(s string) (rule *ingress.Rule) { | ||||||
| 	line := strings.Replace(s, "\t", " ", -1) | 	line := strings.Replace(s, "\t", " ", -1) | ||||||
| 	line = strings.TrimSpace(line) | 	line = strings.TrimSpace(line) | ||||||
| 	if n := strings.IndexByte(line, '#'); n >= 0 { | 	if n := strings.IndexByte(line, '#'); n >= 0 { | ||||||
| @ -290,7 +281,7 @@ func (ing *localIngress) parseLine(s string) (rule Rule) { | |||||||
| 		return // invalid lines are ignored | 		return // invalid lines are ignored | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return Rule{ | 	return &ingress.Rule{ | ||||||
| 		Hostname: sp[0], | 		Hostname: sp[0], | ||||||
| 		Endpoint: sp[1], | 		Endpoint: sp[1], | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -46,31 +46,42 @@ func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) ingress.Ingr | |||||||
| 	return p | 	return p | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *grpcPlugin) Get(ctx context.Context, host string, opts ...ingress.GetOption) string { | func (p *grpcPlugin) GetRule(ctx context.Context, host string, opts ...ingress.Option) *ingress.Rule { | ||||||
| 	if p.client == nil { | 	if p.client == nil { | ||||||
| 		return "" | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	r, err := p.client.Get(ctx, | 	r, err := p.client.GetRule(ctx, | ||||||
| 		&proto.GetRequest{ | 		&proto.GetRuleRequest{ | ||||||
| 			Host: host, | 			Host: host, | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		p.log.Error(err) | 		p.log.Error(err) | ||||||
| 		return "" | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if r.Endpoint == "" { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return &ingress.Rule{ | ||||||
|  | 		Hostname: host, | ||||||
|  | 		Endpoint: r.Endpoint, | ||||||
| 	} | 	} | ||||||
| 	return r.GetEndpoint() |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *grpcPlugin) Set(ctx context.Context, host, endpoint string, opts ...ingress.SetOption) { | func (p *grpcPlugin) SetRule(ctx context.Context, rule *ingress.Rule, opts ...ingress.Option) bool { | ||||||
| 	if p.client == nil { | 	if p.client == nil || rule == nil { | ||||||
| 		return | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	p.client.Set(ctx, &proto.SetRequest{ | 	r, _ := p.client.SetRule(ctx, &proto.SetRuleRequest{ | ||||||
| 		Host:     host, | 		Host:     rule.Hostname, | ||||||
| 		Endpoint: endpoint, | 		Endpoint: rule.Endpoint, | ||||||
| 	}) | 	}) | ||||||
|  | 	if r == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return r.Ok | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *grpcPlugin) Close() error { | func (p *grpcPlugin) Close() error { | ||||||
| @ -80,20 +91,21 @@ func (p *grpcPlugin) Close() error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| type httpPluginGetRequest struct { | type httpPluginGetRuleRequest struct { | ||||||
| 	Host string `json:"host"` | 	Host string `json:"host"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type httpPluginGetResponse struct { | type httpPluginGetRuleResponse struct { | ||||||
| 	Endpoint string `json:"endpoint"` | 	Endpoint string `json:"endpoint"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type httpPluginSetRequest struct { | type httpPluginSetRuleRequest struct { | ||||||
| 	Host     string `json:"host"` | 	Host     string `json:"host"` | ||||||
| 	Endpoint string `json:"endpoint"` | 	Endpoint string `json:"endpoint"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type httpPluginSetResponse struct { | type httpPluginSetRuleResponse struct { | ||||||
|  | 	OK bool `json:"ok"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type httpPlugin struct { | type httpPlugin struct { | ||||||
| @ -121,14 +133,14 @@ func NewHTTPPlugin(name string, url string, opts ...plugin.Option) ingress.Ingre | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *httpPlugin) Get(ctx context.Context, host string, opts ...ingress.GetOption) (endpoint string) { | func (p *httpPlugin) GetRule(ctx context.Context, host string, opts ...ingress.Option) *ingress.Rule { | ||||||
| 	if p.client == nil { | 	if p.client == nil { | ||||||
| 		return | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.url, nil) | 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.url, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return nil | ||||||
| 	} | 	} | ||||||
| 	if p.header != nil { | 	if p.header != nil { | ||||||
| 		req.Header = p.header.Clone() | 		req.Header = p.header.Clone() | ||||||
| @ -141,38 +153,44 @@ func (p *httpPlugin) Get(ctx context.Context, host string, opts ...ingress.GetOp | |||||||
|  |  | ||||||
| 	resp, err := p.client.Do(req) | 	resp, err := p.client.Do(req) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return nil | ||||||
| 	} | 	} | ||||||
| 	defer resp.Body.Close() | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
| 	if resp.StatusCode != http.StatusOK { | 	if resp.StatusCode != http.StatusOK { | ||||||
| 		return | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	res := httpPluginGetResponse{} | 	res := httpPluginGetRuleResponse{} | ||||||
| 	if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { | 	if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { | ||||||
| 		return | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if res.Endpoint == "" { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return &ingress.Rule{ | ||||||
|  | 		Hostname: host, | ||||||
|  | 		Endpoint: res.Endpoint, | ||||||
| 	} | 	} | ||||||
| 	return res.Endpoint |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *httpPlugin) Set(ctx context.Context, host, endpoint string, opts ...ingress.SetOption) { | func (p *httpPlugin) SetRule(ctx context.Context, rule *ingress.Rule, opts ...ingress.Option) bool { | ||||||
| 	if p.client == nil { | 	if p.client == nil || rule == nil { | ||||||
| 		return | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	rb := httpPluginSetRequest{ | 	rb := httpPluginSetRuleRequest{ | ||||||
| 		Host:     host, | 		Host:     rule.Hostname, | ||||||
| 		Endpoint: endpoint, | 		Endpoint: rule.Endpoint, | ||||||
| 	} | 	} | ||||||
| 	v, err := json.Marshal(&rb) | 	v, err := json.Marshal(&rb) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	req, err := http.NewRequestWithContext(ctx, http.MethodPut, p.url, bytes.NewReader(v)) | 	req, err := http.NewRequestWithContext(ctx, http.MethodPut, p.url, bytes.NewReader(v)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if p.header != nil { | 	if p.header != nil { | ||||||
| @ -181,16 +199,17 @@ func (p *httpPlugin) Set(ctx context.Context, host, endpoint string, opts ...ing | |||||||
| 	req.Header.Set("Content-Type", "application/json") | 	req.Header.Set("Content-Type", "application/json") | ||||||
| 	resp, err := p.client.Do(req) | 	resp, err := p.client.Do(req) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return false | ||||||
| 	} | 	} | ||||||
| 	defer resp.Body.Close() | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
| 	if resp.StatusCode != http.StatusOK { | 	if resp.StatusCode != http.StatusOK { | ||||||
| 		return | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	res := httpPluginSetResponse{} | 	res := httpPluginSetRuleResponse{} | ||||||
| 	if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { | 	if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { | ||||||
| 		return | 		return false | ||||||
| 	} | 	} | ||||||
|  | 	return res.OK | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										76
									
								
								internal/ctx/value.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								internal/ctx/value.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | |||||||
|  | package ctx | ||||||
|  |  | ||||||
|  | import "context" | ||||||
|  |  | ||||||
|  | // clientAddrKey saves the client address. | ||||||
|  | type clientAddrKey struct{} | ||||||
|  |  | ||||||
|  | type ClientAddr string | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	keyClientAddr clientAddrKey | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func ContextWithClientAddr(ctx context.Context, addr ClientAddr) context.Context { | ||||||
|  | 	return context.WithValue(ctx, keyClientAddr, addr) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ClientAddrFromContext(ctx context.Context) ClientAddr { | ||||||
|  | 	v, _ := ctx.Value(keyClientAddr).(ClientAddr) | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // sidKey saves the session ID. | ||||||
|  | type sidKey struct{} | ||||||
|  | type Sid string | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	keySid sidKey | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func ContextWithSid(ctx context.Context, sid Sid) context.Context { | ||||||
|  | 	return context.WithValue(ctx, keySid, sid) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func SidFromContext(ctx context.Context) Sid { | ||||||
|  | 	v, _ := ctx.Value(keySid).(Sid) | ||||||
|  | 	return v | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // hashKey saves the hash source for Selector. | ||||||
|  | type hashKey struct{} | ||||||
|  |  | ||||||
|  | type Hash struct { | ||||||
|  | 	Source string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	clientHashKey = &hashKey{} | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func ContextWithHash(ctx context.Context, hash *Hash) context.Context { | ||||||
|  | 	return context.WithValue(ctx, clientHashKey, hash) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func HashFromContext(ctx context.Context) *Hash { | ||||||
|  | 	if v, _ := ctx.Value(clientHashKey).(*Hash); v != nil { | ||||||
|  | 		return v | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type clientIDKey struct{} | ||||||
|  | type ClientID string | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	keyClientID = &clientIDKey{} | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func ContextWithClientID(ctx context.Context, clientID ClientID) context.Context { | ||||||
|  | 	return context.WithValue(ctx, keyClientID, clientID) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ClientIDFromContext(ctx context.Context) ClientID { | ||||||
|  | 	v, _ := ctx.Value(keyClientID).(ClientID) | ||||||
|  | 	return v | ||||||
|  | } | ||||||
| @ -1,11 +1,11 @@ | |||||||
| package matcher | package matcher | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" |  | ||||||
| 	"net" | 	"net" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	xnet "github.com/go-gost/x/internal/net" | ||||||
| 	"github.com/gobwas/glob" | 	"github.com/gobwas/glob" | ||||||
| 	"github.com/yl2chen/cidranger" | 	"github.com/yl2chen/cidranger" | ||||||
| ) | ) | ||||||
| @ -40,7 +40,7 @@ func (m *ipMatcher) Match(ip string) bool { | |||||||
| } | } | ||||||
|  |  | ||||||
| type addrMatcher struct { | type addrMatcher struct { | ||||||
| 	addrs map[string]*PortRange | 	addrs map[string]*xnet.PortRange | ||||||
| } | } | ||||||
|  |  | ||||||
| // AddrMatcher creates a Matcher with a list of HOST:PORT addresses. | // AddrMatcher creates a Matcher with a list of HOST:PORT addresses. | ||||||
| @ -50,7 +50,7 @@ type addrMatcher struct { | |||||||
| // The PORT can be a single port number or port range MIN-MAX(e.g. 0-65535). | // The PORT can be a single port number or port range MIN-MAX(e.g. 0-65535). | ||||||
| func AddrMatcher(addrs []string) Matcher { | func AddrMatcher(addrs []string) Matcher { | ||||||
| 	matcher := &addrMatcher{ | 	matcher := &addrMatcher{ | ||||||
| 		addrs: make(map[string]*PortRange), | 		addrs: make(map[string]*xnet.PortRange), | ||||||
| 	} | 	} | ||||||
| 	for _, addr := range addrs { | 	for _, addr := range addrs { | ||||||
| 		host, port, _ := net.SplitHostPort(addr) | 		host, port, _ := net.SplitHostPort(addr) | ||||||
| @ -58,7 +58,10 @@ func AddrMatcher(addrs []string) Matcher { | |||||||
| 			matcher.addrs[addr] = nil | 			matcher.addrs[addr] = nil | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		pr, _ := parsePortRange(port) | 		pr := &xnet.PortRange{} | ||||||
|  | 		if err := pr.Parse(port); err != nil { | ||||||
|  | 			pr = nil | ||||||
|  | 		} | ||||||
| 		matcher.addrs[host] = pr | 		matcher.addrs[host] = pr | ||||||
| 	} | 	} | ||||||
| 	return matcher | 	return matcher | ||||||
| @ -75,13 +78,13 @@ func (m *addrMatcher) Match(addr string) bool { | |||||||
| 	port, _ := strconv.Atoi(sp) | 	port, _ := strconv.Atoi(sp) | ||||||
|  |  | ||||||
| 	if pr, ok := m.addrs[host]; ok { | 	if pr, ok := m.addrs[host]; ok { | ||||||
| 		if pr == nil || pr.contains(port) { | 		if pr == nil || pr.Contains(port) { | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if pr, ok := m.addrs["."+host]; ok { | 	if pr, ok := m.addrs["."+host]; ok { | ||||||
| 		if pr == nil || pr.contains(port) { | 		if pr == nil || pr.Contains(port) { | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @ -89,7 +92,7 @@ func (m *addrMatcher) Match(addr string) bool { | |||||||
| 	for { | 	for { | ||||||
| 		if index := strings.IndexByte(host, '.'); index > 0 { | 		if index := strings.IndexByte(host, '.'); index > 0 { | ||||||
| 			if pr, ok := m.addrs[host[index:]]; ok { | 			if pr, ok := m.addrs[host[index:]]; ok { | ||||||
| 				if pr == nil || pr.contains(port) { | 				if pr == nil || pr.Contains(port) { | ||||||
| 					return true | 					return true | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @ -172,7 +175,7 @@ func (m *domainMatcher) Match(domain string) bool { | |||||||
|  |  | ||||||
| type wildcardMatcherPattern struct { | type wildcardMatcherPattern struct { | ||||||
| 	glob glob.Glob | 	glob glob.Glob | ||||||
| 	pr   *PortRange | 	pr   *xnet.PortRange | ||||||
| } | } | ||||||
| type wildcardMatcher struct { | type wildcardMatcher struct { | ||||||
| 	patterns []wildcardMatcherPattern | 	patterns []wildcardMatcherPattern | ||||||
| @ -187,7 +190,11 @@ func WildcardMatcher(patterns []string) Matcher { | |||||||
| 		if host == "" { | 		if host == "" { | ||||||
| 			host = pattern | 			host = pattern | ||||||
| 		} | 		} | ||||||
| 		pr, _ := parsePortRange(port) | 		pr := &xnet.PortRange{} | ||||||
|  | 		if err := pr.Parse(port); err != nil { | ||||||
|  | 			pr = nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		matcher.patterns = append(matcher.patterns, wildcardMatcherPattern{ | 		matcher.patterns = append(matcher.patterns, wildcardMatcherPattern{ | ||||||
| 			glob: glob.MustCompile(host), | 			glob: glob.MustCompile(host), | ||||||
| 			pr:   pr, | 			pr:   pr, | ||||||
| @ -208,8 +215,8 @@ func (m *wildcardMatcher) Match(addr string) bool { | |||||||
| 	} | 	} | ||||||
| 	port, _ := strconv.Atoi(sp) | 	port, _ := strconv.Atoi(sp) | ||||||
| 	for _, pattern := range m.patterns { | 	for _, pattern := range m.patterns { | ||||||
| 		if pattern.glob.Match(addr) { | 		if pattern.glob.Match(host) { | ||||||
| 			if pattern.pr == nil || pattern.pr.contains(port) { | 			if pattern.pr == nil || pattern.pr.Contains(port) { | ||||||
| 				return true | 				return true | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @ -217,44 +224,3 @@ func (m *wildcardMatcher) Match(addr string) bool { | |||||||
|  |  | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
| type PortRange struct { |  | ||||||
| 	Min int |  | ||||||
| 	Max int |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ParsePortRange parses the s to a PortRange. |  | ||||||
| // The s can be a single port number and will be converted to port range port-port. |  | ||||||
| func parsePortRange(s string) (*PortRange, error) { |  | ||||||
| 	minmax := strings.Split(s, "-") |  | ||||||
| 	switch len(minmax) { |  | ||||||
| 	case 1: |  | ||||||
| 		port, err := strconv.Atoi(s) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		if port < 0 || port > 65535 { |  | ||||||
| 			return nil, fmt.Errorf("invalid port: %s", s) |  | ||||||
| 		} |  | ||||||
| 		return &PortRange{Min: port, Max: port}, nil |  | ||||||
|  |  | ||||||
| 	case 2: |  | ||||||
| 		min, err := strconv.Atoi(minmax[0]) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		max, err := strconv.Atoi(minmax[1]) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return &PortRange{Min: min, Max: max}, nil |  | ||||||
|  |  | ||||||
| 	default: |  | ||||||
| 		return nil, fmt.Errorf("invalid range: %s", s) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (pr *PortRange) contains(port int) bool { |  | ||||||
| 	return port >= pr.Min && port <= pr.Max |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										77
									
								
								internal/net/addr.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								internal/net/addr.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | |||||||
|  | package net | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"net" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // AddrPortRange is the network address with port range supported. | ||||||
|  | // e.g. 192.168.1.1:0-65535 | ||||||
|  | type AddrPortRange string | ||||||
|  |  | ||||||
|  | func (p AddrPortRange) Addrs() (addrs []string) { | ||||||
|  | 	// ignore url scheme, e.g. http://, tls://, tcp://. | ||||||
|  | 	if strings.Contains(string(p), "://") { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	h, sp, err := net.SplitHostPort(string(p)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	pr := PortRange{} | ||||||
|  | 	pr.Parse(sp) | ||||||
|  |  | ||||||
|  | 	for i := pr.Min; i <= pr.Max; i++ { | ||||||
|  | 		addrs = append(addrs, net.JoinHostPort(h, strconv.Itoa(i))) | ||||||
|  | 	} | ||||||
|  | 	return addrs | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Port range is a range of port list. | ||||||
|  | type PortRange struct { | ||||||
|  | 	Min int | ||||||
|  | 	Max int | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Parse parses the s to PortRange. | ||||||
|  | // The s can be a single port number and will be converted to port range port-port. | ||||||
|  | func (pr *PortRange) Parse(s string) error { | ||||||
|  | 	minmax := strings.Split(s, "-") | ||||||
|  | 	switch len(minmax) { | ||||||
|  | 	case 1: | ||||||
|  | 		port, err := strconv.Atoi(s) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		if port < 0 || port > 65535 { | ||||||
|  | 			return fmt.Errorf("invalid port: %s", s) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		pr.Min, pr.Max = port, port | ||||||
|  | 		return nil | ||||||
|  |  | ||||||
|  | 	case 2: | ||||||
|  | 		min, err := strconv.Atoi(minmax[0]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		max, err := strconv.Atoi(minmax[1]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		pr.Min, pr.Max = min, max | ||||||
|  | 		return nil | ||||||
|  |  | ||||||
|  | 	default: | ||||||
|  | 		return fmt.Errorf("invalid range: %s", s) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (pr *PortRange) Contains(port int) bool { | ||||||
|  | 	return port >= pr.Min && port <= pr.Max | ||||||
|  | } | ||||||
| @ -39,7 +39,7 @@ func (r *Relay) SetBufferSize(n int) { | |||||||
| 	r.bufferSize = n | 	r.bufferSize = n | ||||||
| } | } | ||||||
|  |  | ||||||
| func (r *Relay) Run() (err error) { | func (r *Relay) Run(ctx context.Context) (err error) { | ||||||
| 	bufSize := r.bufferSize | 	bufSize := r.bufferSize | ||||||
| 	if bufSize <= 0 { | 	if bufSize <= 0 { | ||||||
| 		bufSize = 4096 | 		bufSize = 4096 | ||||||
| @ -58,7 +58,7 @@ func (r *Relay) Run() (err error) { | |||||||
| 					return err | 					return err | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if r.bypass != nil && r.bypass.Contains(context.Background(), "udp", raddr.String()) { | 				if r.bypass != nil && r.bypass.Contains(ctx, "udp", raddr.String()) { | ||||||
| 					if r.logger != nil { | 					if r.logger != nil { | ||||||
| 						r.logger.Warn("bypass: ", raddr) | 						r.logger.Warn("bypass: ", raddr) | ||||||
| 					} | 					} | ||||||
| @ -96,7 +96,7 @@ func (r *Relay) Run() (err error) { | |||||||
| 					return err | 					return err | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				if r.bypass != nil && r.bypass.Contains(context.Background(), "udp", raddr.String()) { | 				if r.bypass != nil && r.bypass.Contains(ctx, "udp", raddr.String()) { | ||||||
| 					if r.logger != nil { | 					if r.logger != nil { | ||||||
| 						r.logger.Warn("bypass: ", raddr) | 						r.logger.Warn("bypass: ", raddr) | ||||||
| 					} | 					} | ||||||
|  | |||||||
| @ -1,34 +0,0 @@ | |||||||
| package auth |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type idKey struct{} |  | ||||||
| type ID string |  | ||||||
|  |  | ||||||
| type addrKey struct{} |  | ||||||
| type ClientAddr string |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	clientIDKey   = &idKey{} |  | ||||||
| 	clientAddrKey = &addrKey{} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func ContextWithID(ctx context.Context, id ID) context.Context { |  | ||||||
| 	return context.WithValue(ctx, clientIDKey, id) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func IDFromContext(ctx context.Context) ID { |  | ||||||
| 	v, _ := ctx.Value(clientIDKey).(ID) |  | ||||||
| 	return v |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func ContextWithClientAddr(ctx context.Context, addr ClientAddr) context.Context { |  | ||||||
| 	return context.WithValue(ctx, clientAddrKey, addr) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func ClientAddrFromContext(ctx context.Context) ClientAddr { |  | ||||||
| 	v, _ := ctx.Value(clientAddrKey).(ClientAddr) |  | ||||||
| 	return v |  | ||||||
| } |  | ||||||
| @ -6,8 +6,32 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/go-gost/core/common/bufpool" | 	"github.com/go-gost/core/common/bufpool" | ||||||
| 	"github.com/go-gost/gosocks5" | 	"github.com/go-gost/gosocks5" | ||||||
|  | 	"github.com/go-gost/relay" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | func StatusText(code uint8) string { | ||||||
|  | 	switch code { | ||||||
|  | 	case relay.StatusBadRequest: | ||||||
|  | 		return "Bad Request" | ||||||
|  | 	case relay.StatusForbidden: | ||||||
|  | 		return "Forbidden" | ||||||
|  | 	case relay.StatusHostUnreachable: | ||||||
|  | 		return "Host Unreachable" | ||||||
|  | 	case relay.StatusInternalServerError: | ||||||
|  | 		return "Internal Server Error" | ||||||
|  | 	case relay.StatusNetworkUnreachable: | ||||||
|  | 		return "Network Unreachable" | ||||||
|  | 	case relay.StatusServiceUnavailable: | ||||||
|  | 		return "Service Unavailable" | ||||||
|  | 	case relay.StatusTimeout: | ||||||
|  | 		return "Timeout" | ||||||
|  | 	case relay.StatusUnauthorized: | ||||||
|  | 		return "Unauthorized" | ||||||
|  | 	default: | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| type udpTunConn struct { | type udpTunConn struct { | ||||||
| 	net.Conn | 	net.Conn | ||||||
| 	taddr net.Addr | 	taddr net.Addr | ||||||
|  | |||||||
| @ -1,26 +0,0 @@ | |||||||
| package selector |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type hashKey struct{} |  | ||||||
|  |  | ||||||
| type Hash struct { |  | ||||||
| 	Source string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	clientHashKey = &hashKey{} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func ContextWithHash(ctx context.Context, hash *Hash) context.Context { |  | ||||||
| 	return context.WithValue(ctx, clientHashKey, hash) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func HashFromContext(ctx context.Context) *Hash { |  | ||||||
| 	if v, _ := ctx.Value(clientHashKey).(*Hash); v != nil { |  | ||||||
| 		return v |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| @ -1,12 +1,10 @@ | |||||||
| package tun | package tun | ||||||
|  |  | ||||||
| import "net" | import ( | ||||||
|  | 	"net" | ||||||
|  |  | ||||||
| // Route is an IP routing entry | 	"github.com/go-gost/core/router" | ||||||
| type Route struct { | ) | ||||||
| 	Net     net.IPNet |  | ||||||
| 	Gateway net.IP |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Config struct { | type Config struct { | ||||||
| 	Name string | 	Name string | ||||||
| @ -15,5 +13,5 @@ type Config struct { | |||||||
| 	Peer    string | 	Peer    string | ||||||
| 	MTU     int | 	MTU     int | ||||||
| 	Gateway net.IP | 	Gateway net.IP | ||||||
| 	Routes  []Route | 	Router  router.Router | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										235
									
								
								limiter/traffic/plugin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								limiter/traffic/plugin.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,235 @@ | |||||||
|  | package traffic | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
|  | 	"net/http" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/limiter/traffic" | ||||||
|  | 	"github.com/go-gost/core/logger" | ||||||
|  | 	"github.com/go-gost/plugin/limiter/traffic/proto" | ||||||
|  | 	"github.com/go-gost/x/internal/plugin" | ||||||
|  | 	"google.golang.org/grpc" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type grpcPlugin struct { | ||||||
|  | 	conn   grpc.ClientConnInterface | ||||||
|  | 	client proto.LimiterClient | ||||||
|  | 	log    logger.Logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewGRPCPlugin creates a traffic limiter plugin based on gRPC. | ||||||
|  | func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) traffic.TrafficLimiter { | ||||||
|  | 	var options plugin.Options | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt(&options) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	log := logger.Default().WithFields(map[string]any{ | ||||||
|  | 		"kind":    "limiter", | ||||||
|  | 		"limiter": name, | ||||||
|  | 	}) | ||||||
|  | 	conn, err := plugin.NewGRPCConn(addr, &options) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	p := &grpcPlugin{ | ||||||
|  | 		conn: conn, | ||||||
|  | 		log:  log, | ||||||
|  | 	} | ||||||
|  | 	if conn != nil { | ||||||
|  | 		p.client = proto.NewLimiterClient(conn) | ||||||
|  | 	} | ||||||
|  | 	return p | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *grpcPlugin) In(ctx context.Context, key string, opts ...traffic.Option) traffic.Limiter { | ||||||
|  | 	if p.client == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var options traffic.Options | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt(&options) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r, err := p.client.Limit(ctx, | ||||||
|  | 		&proto.LimitRequest{ | ||||||
|  | 			Network: options.Network, | ||||||
|  | 			Addr:    options.Addr, | ||||||
|  | 			Client:  options.Client, | ||||||
|  | 			Src:     options.Src, | ||||||
|  | 		}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		p.log.Error(err) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return NewLimiter(int(r.In)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *grpcPlugin) Out(ctx context.Context, key string, opts ...traffic.Option) traffic.Limiter { | ||||||
|  | 	if p.client == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var options traffic.Options | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt(&options) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r, err := p.client.Limit(ctx, | ||||||
|  | 		&proto.LimitRequest{ | ||||||
|  | 			Network: options.Network, | ||||||
|  | 			Addr:    options.Addr, | ||||||
|  | 			Client:  options.Client, | ||||||
|  | 			Src:     options.Src, | ||||||
|  | 		}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		p.log.Error(err) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return NewLimiter(int(r.Out)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *grpcPlugin) Close() error { | ||||||
|  | 	if closer, ok := p.conn.(io.Closer); ok { | ||||||
|  | 		return closer.Close() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type httpPluginRequest struct { | ||||||
|  | 	Network string `json:"network"` | ||||||
|  | 	Addr    string `json:"addr"` | ||||||
|  | 	Client  string `json:"client"` | ||||||
|  | 	Src     string `json:"src"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type httpPluginResponse struct { | ||||||
|  | 	In  int64 `json:"in"` | ||||||
|  | 	Out int64 `json:"out"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type httpPlugin struct { | ||||||
|  | 	url    string | ||||||
|  | 	client *http.Client | ||||||
|  | 	header http.Header | ||||||
|  | 	log    logger.Logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewHTTPPlugin creates a traffic limiter plugin based on HTTP. | ||||||
|  | func NewHTTPPlugin(name string, url string, opts ...plugin.Option) traffic.TrafficLimiter { | ||||||
|  | 	var options plugin.Options | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt(&options) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &httpPlugin{ | ||||||
|  | 		url:    url, | ||||||
|  | 		client: plugin.NewHTTPClient(&options), | ||||||
|  | 		header: options.Header, | ||||||
|  | 		log: logger.Default().WithFields(map[string]any{ | ||||||
|  | 			"kind":    "limiter", | ||||||
|  | 			"limiter": name, | ||||||
|  | 		}), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *httpPlugin) In(ctx context.Context, key string, opts ...traffic.Option) traffic.Limiter { | ||||||
|  | 	if p.client == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var options traffic.Options | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt(&options) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rb := httpPluginRequest{ | ||||||
|  | 		Network: options.Network, | ||||||
|  | 		Addr:    options.Addr, | ||||||
|  | 		Client:  options.Client, | ||||||
|  | 		Src:     options.Src, | ||||||
|  | 	} | ||||||
|  | 	v, err := json.Marshal(&rb) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if p.header != nil { | ||||||
|  | 		req.Header = p.header.Clone() | ||||||
|  | 	} | ||||||
|  | 	req.Header.Set("Content-Type", "application/json") | ||||||
|  | 	resp, err := p.client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	if resp.StatusCode != http.StatusOK { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	res := httpPluginResponse{} | ||||||
|  | 	if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return NewLimiter(int(res.In)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *httpPlugin) Out(ctx context.Context, key string, opts ...traffic.Option) traffic.Limiter { | ||||||
|  | 	if p.client == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var options traffic.Options | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt(&options) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rb := httpPluginRequest{ | ||||||
|  | 		Network: options.Network, | ||||||
|  | 		Addr:    options.Addr, | ||||||
|  | 		Client:  options.Client, | ||||||
|  | 		Src:     options.Src, | ||||||
|  | 	} | ||||||
|  | 	v, err := json.Marshal(&rb) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if p.header != nil { | ||||||
|  | 		req.Header = p.header.Clone() | ||||||
|  | 	} | ||||||
|  | 	req.Header.Set("Content-Type", "application/json") | ||||||
|  | 	resp, err := p.client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	if resp.StatusCode != http.StatusOK { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	res := httpPluginResponse{} | ||||||
|  | 	if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return NewLimiter(int(res.Out)) | ||||||
|  | } | ||||||
| @ -121,7 +121,7 @@ func NewTrafficLimiter(opts ...Option) limiter.TrafficLimiter { | |||||||
|  |  | ||||||
| // In obtains a traffic input limiter based on key. | // In obtains a traffic input limiter based on key. | ||||||
| // The key should be client connection address. | // The key should be client connection address. | ||||||
| func (l *trafficLimiter) In(key string) limiter.Limiter { | func (l *trafficLimiter) In(ctx context.Context, key string, opts ...limiter.Option) limiter.Limiter { | ||||||
| 	var lims []limiter.Limiter | 	var lims []limiter.Limiter | ||||||
|  |  | ||||||
| 	// service level limiter | 	// service level limiter | ||||||
| @ -185,7 +185,7 @@ func (l *trafficLimiter) In(key string) limiter.Limiter { | |||||||
|  |  | ||||||
| // Out obtains a traffic output limiter based on key. | // Out obtains a traffic output limiter based on key. | ||||||
| // The key should be client connection address. | // The key should be client connection address. | ||||||
| func (l *trafficLimiter) Out(key string) limiter.Limiter { | func (l *trafficLimiter) Out(ctx context.Context, key string, opts ...limiter.Option) limiter.Limiter { | ||||||
| 	var lims []limiter.Limiter | 	var lims []limiter.Limiter | ||||||
|  |  | ||||||
| 	// service level limiter | 	// service level limiter | ||||||
|  | |||||||
| @ -26,43 +26,54 @@ type serverConn struct { | |||||||
| 	rbuf       bytes.Buffer | 	rbuf       bytes.Buffer | ||||||
| 	limiter    limiter.TrafficLimiter | 	limiter    limiter.TrafficLimiter | ||||||
| 	limiterIn  limiter.Limiter | 	limiterIn  limiter.Limiter | ||||||
| 	expIn      int64 |  | ||||||
| 	limiterOut limiter.Limiter | 	limiterOut limiter.Limiter | ||||||
|  | 	expIn      int64 | ||||||
| 	expOut     int64 | 	expOut     int64 | ||||||
|  | 	opts       []limiter.Option | ||||||
| } | } | ||||||
|  |  | ||||||
| func WrapConn(limiter limiter.TrafficLimiter, c net.Conn) net.Conn { | func WrapConn(tlimiter limiter.TrafficLimiter, c net.Conn) net.Conn { | ||||||
| 	if limiter == nil { | 	if tlimiter == nil { | ||||||
| 		return c | 		return c | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return &serverConn{ | 	return &serverConn{ | ||||||
| 		Conn:    c, | 		Conn:    c, | ||||||
| 		limiter: limiter, | 		limiter: tlimiter, | ||||||
|  | 		opts: []limiter.Option{ | ||||||
|  | 			limiter.NetworkOption(c.LocalAddr().Network()), | ||||||
|  | 			limiter.SrcOption(c.RemoteAddr().String()), | ||||||
|  | 			limiter.AddrOption(c.LocalAddr().String()), | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *serverConn) getInLimiter(addr net.Addr) limiter.Limiter { | func (c *serverConn) getInLimiter() limiter.Limiter { | ||||||
| 	now := time.Now().UnixNano() | 	now := time.Now().UnixNano() | ||||||
| 	// cache the limiter for 60s | 	// cache the limiter for 60s | ||||||
| 	if c.limiter != nil && time.Duration(now-c.expIn) > 60*time.Second { | 	if c.limiter != nil && time.Duration(now-c.expIn) > 60*time.Second { | ||||||
| 		c.limiterIn = c.limiter.In(addr.String()) | 		if lim := c.limiter.In(context.Background(), c.RemoteAddr().String()); lim != nil { | ||||||
|  | 			c.limiterIn = lim | ||||||
|  | 		} | ||||||
| 		c.expIn = now | 		c.expIn = now | ||||||
| 	} | 	} | ||||||
| 	return c.limiterIn | 	return c.limiterIn | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *serverConn) getOutLimiter(addr net.Addr) limiter.Limiter { | func (c *serverConn) getOutLimiter() limiter.Limiter { | ||||||
| 	now := time.Now().UnixNano() | 	now := time.Now().UnixNano() | ||||||
| 	// cache the limiter for 60s | 	// cache the limiter for 60s | ||||||
| 	if c.limiter != nil && time.Duration(now-c.expOut) > 60*time.Second { | 	if c.limiter != nil && time.Duration(now-c.expOut) > 60*time.Second { | ||||||
| 		c.limiterOut = c.limiter.Out(addr.String()) | 		if lim := c.limiter.Out(context.Background(), c.RemoteAddr().String()); lim != nil { | ||||||
|  | 			c.limiterOut = lim | ||||||
|  | 		} | ||||||
| 		c.expOut = now | 		c.expOut = now | ||||||
| 	} | 	} | ||||||
| 	return c.limiterOut | 	return c.limiterOut | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *serverConn) Read(b []byte) (n int, err error) { | func (c *serverConn) Read(b []byte) (n int, err error) { | ||||||
| 	limiter := c.getInLimiter(c.RemoteAddr()) | 	limiter := c.getInLimiter() | ||||||
| 	if limiter == nil { | 	if limiter == nil { | ||||||
| 		return c.Conn.Read(b) | 		return c.Conn.Read(b) | ||||||
| 	} | 	} | ||||||
| @ -92,7 +103,7 @@ func (c *serverConn) Read(b []byte) (n int, err error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *serverConn) Write(b []byte) (n int, err error) { | func (c *serverConn) Write(b []byte) (n int, err error) { | ||||||
| 	limiter := c.getOutLimiter(c.RemoteAddr()) | 	limiter := c.getOutLimiter() | ||||||
| 	if limiter == nil { | 	if limiter == nil { | ||||||
| 		return c.Conn.Write(b) | 		return c.Conn.Write(b) | ||||||
| 	} | 	} | ||||||
| @ -163,7 +174,7 @@ func (c *packetConn) getInLimiter(addr net.Addr) limiter.Limiter { | |||||||
| 		return lim | 		return lim | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	lim = c.limiter.In(addr.String()) | 	lim = c.limiter.In(context.Background(), addr.String()) | ||||||
| 	c.inLimits.Set(addr.String(), lim, 0) | 	c.inLimits.Set(addr.String(), lim, 0) | ||||||
|  |  | ||||||
| 	return lim | 	return lim | ||||||
| @ -187,7 +198,7 @@ func (c *packetConn) getOutLimiter(addr net.Addr) limiter.Limiter { | |||||||
| 		return lim | 		return lim | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	lim = c.limiter.Out(addr.String()) | 	lim = c.limiter.Out(context.Background(), addr.String()) | ||||||
| 	c.outLimits.Set(addr.String(), lim, 0) | 	c.outLimits.Set(addr.String(), lim, 0) | ||||||
|  |  | ||||||
| 	return lim | 	return lim | ||||||
| @ -266,7 +277,7 @@ func (c *udpConn) getInLimiter(addr net.Addr) limiter.Limiter { | |||||||
| 		return lim | 		return lim | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	lim = c.limiter.In(addr.String()) | 	lim = c.limiter.In(context.Background(), addr.String()) | ||||||
| 	c.inLimits.Set(addr.String(), lim, 0) | 	c.inLimits.Set(addr.String(), lim, 0) | ||||||
|  |  | ||||||
| 	return lim | 	return lim | ||||||
| @ -290,7 +301,7 @@ func (c *udpConn) getOutLimiter(addr net.Addr) limiter.Limiter { | |||||||
| 		return lim | 		return lim | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	lim = c.limiter.Out(addr.String()) | 	lim = c.limiter.Out(context.Background(), addr.String()) | ||||||
| 	c.outLimits.Set(addr.String(), lim, 0) | 	c.outLimits.Set(addr.String(), lim, 0) | ||||||
|  |  | ||||||
| 	return lim | 	return lim | ||||||
|  | |||||||
							
								
								
									
										108
									
								
								limiter/traffic/wrapper/io.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								limiter/traffic/wrapper/io.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | |||||||
|  | package wrapper | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"io" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	limiter "github.com/go-gost/core/limiter/traffic" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // readWriter is an io.ReadWriter with traffic limiter supported. | ||||||
|  | type readWriter struct { | ||||||
|  | 	io.ReadWriter | ||||||
|  | 	rbuf       bytes.Buffer | ||||||
|  | 	limiter    limiter.TrafficLimiter | ||||||
|  | 	limiterIn  limiter.Limiter | ||||||
|  | 	limiterOut limiter.Limiter | ||||||
|  | 	expIn      int64 | ||||||
|  | 	expOut     int64 | ||||||
|  | 	opts       []limiter.Option | ||||||
|  | 	key        string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func WrapReadWriter(limiter limiter.TrafficLimiter, rw io.ReadWriter, key string, opts ...limiter.Option) io.ReadWriter { | ||||||
|  | 	if limiter == nil { | ||||||
|  | 		return rw | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &readWriter{ | ||||||
|  | 		ReadWriter: rw, | ||||||
|  | 		limiter:    limiter, | ||||||
|  | 		opts:       opts, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *readWriter) getInLimiter() limiter.Limiter { | ||||||
|  | 	now := time.Now().UnixNano() | ||||||
|  | 	// cache the limiter for 60s | ||||||
|  | 	if p.limiter != nil && time.Duration(now-p.expIn) > 60*time.Second { | ||||||
|  | 		if lim := p.limiter.In(context.Background(), p.key, p.opts...); lim != nil { | ||||||
|  | 			p.limiterIn = lim | ||||||
|  | 		} | ||||||
|  | 		p.expIn = now | ||||||
|  | 	} | ||||||
|  | 	return p.limiterIn | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *readWriter) getOutLimiter() limiter.Limiter { | ||||||
|  | 	now := time.Now().UnixNano() | ||||||
|  | 	// cache the limiter for 60s | ||||||
|  | 	if p.limiter != nil && time.Duration(now-p.expOut) > 60*time.Second { | ||||||
|  | 		if lim := p.limiter.Out(context.Background(), p.key, p.opts...); lim != nil { | ||||||
|  | 			p.limiterOut = lim | ||||||
|  | 		} | ||||||
|  | 		p.expOut = now | ||||||
|  | 	} | ||||||
|  | 	return p.limiterOut | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *readWriter) Read(b []byte) (n int, err error) { | ||||||
|  | 	limiter := p.getInLimiter() | ||||||
|  | 	if limiter == nil { | ||||||
|  | 		return p.ReadWriter.Read(b) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if p.rbuf.Len() > 0 { | ||||||
|  | 		burst := len(b) | ||||||
|  | 		if p.rbuf.Len() < burst { | ||||||
|  | 			burst = p.rbuf.Len() | ||||||
|  | 		} | ||||||
|  | 		lim := limiter.Wait(context.Background(), burst) | ||||||
|  | 		return p.rbuf.Read(b[:lim]) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	nn, err := p.ReadWriter.Read(b) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nn, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	n = limiter.Wait(context.Background(), nn) | ||||||
|  | 	if n < nn { | ||||||
|  | 		if _, err = p.rbuf.Write(b[n:nn]); err != nil { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *readWriter) Write(b []byte) (n int, err error) { | ||||||
|  | 	limiter := p.getOutLimiter() | ||||||
|  | 	if limiter == nil { | ||||||
|  | 		return p.ReadWriter.Write(b) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	nn := 0 | ||||||
|  | 	for len(b) > 0 { | ||||||
|  | 		nn, err = p.ReadWriter.Write(b[:limiter.Wait(context.Background(), len(b))]) | ||||||
|  | 		n += nn | ||||||
|  | 		if err != nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		b = b[nn:] | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
| @ -118,10 +118,10 @@ func (l *http2Listener) Close() (err error) { | |||||||
| 	case <-l.errChan: | 	case <-l.errChan: | ||||||
| 	default: | 	default: | ||||||
| 		err = l.server.Close() | 		err = l.server.Close() | ||||||
| 		l.errChan <- err | 		l.errChan <- http.ErrServerClosed | ||||||
| 		close(l.errChan) | 		close(l.errChan) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func (l *http2Listener) handleFunc(w http.ResponseWriter, r *http.Request) { | func (l *http2Listener) handleFunc(w http.ResponseWriter, r *http.Request) { | ||||||
|  | |||||||
| @ -20,18 +20,17 @@ type metadata struct { | |||||||
| func (l *kcpListener) parseMetadata(md mdata.Metadata) (err error) { | func (l *kcpListener) parseMetadata(md mdata.Metadata) (err error) { | ||||||
| 	const ( | 	const ( | ||||||
| 		backlog    = "backlog" | 		backlog    = "backlog" | ||||||
| 		config     = "config" |  | ||||||
| 		configFile = "c" | 		configFile = "c" | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
| 	if file := mdutil.GetString(md, configFile); file != "" { | 	if file := mdutil.GetString(md, "kcp.configFile", "configFile", "c"); file != "" { | ||||||
| 		l.md.config, err = kcp_util.ParseFromFile(file) | 		l.md.config, err = kcp_util.ParseFromFile(file) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if m := mdutil.GetStringMap(md, config); len(m) > 0 { | 	if m := mdutil.GetStringMap(md, "kcp.config", "config"); len(m) > 0 { | ||||||
| 		b, err := json.Marshal(m) | 		b, err := json.Marshal(m) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| @ -46,6 +45,14 @@ func (l *kcpListener) parseMetadata(md mdata.Metadata) (err error) { | |||||||
| 	if l.md.config == nil { | 	if l.md.config == nil { | ||||||
| 		l.md.config = kcp_util.DefaultConfig | 		l.md.config = kcp_util.DefaultConfig | ||||||
| 	} | 	} | ||||||
|  | 	l.md.config.TCP = mdutil.GetBool(md, "kcp.tcp", "tcp") | ||||||
|  | 	l.md.config.Key = mdutil.GetString(md, "kcp.key") | ||||||
|  | 	l.md.config.Crypt = mdutil.GetString(md, "kcp.crypt") | ||||||
|  | 	l.md.config.Mode = mdutil.GetString(md, "kcp.mode") | ||||||
|  | 	l.md.config.KeepAlive = mdutil.GetInt(md, "kcp.keepalive") | ||||||
|  | 	l.md.config.Interval = mdutil.GetInt(md, "kcp.interval") | ||||||
|  | 	l.md.config.MTU = mdutil.GetInt(md, "kcp.mtu") | ||||||
|  | 	l.md.config.SmuxVer = mdutil.GetInt(md, "kcp.smuxVer") | ||||||
|  |  | ||||||
| 	l.md.backlog = mdutil.GetInt(md, backlog) | 	l.md.backlog = mdutil.GetInt(md, backlog) | ||||||
| 	if l.md.backlog <= 0 { | 	if l.md.backlog <= 0 { | ||||||
|  | |||||||
| @ -50,12 +50,13 @@ func (l *rudpListener) Init(md md.Metadata) (err error) { | |||||||
| 	if xnet.IsIPv4(l.options.Addr) { | 	if xnet.IsIPv4(l.options.Addr) { | ||||||
| 		network = "udp4" | 		network = "udp4" | ||||||
| 	} | 	} | ||||||
| 	laddr, err := net.ResolveUDPAddr(network, l.options.Addr) | 	if laddr, _ := net.ResolveUDPAddr(network, l.options.Addr); laddr != nil { | ||||||
| 	if err != nil { | 		l.laddr = laddr | ||||||
| 		return | 	} | ||||||
|  | 	if l.laddr == nil { | ||||||
|  | 		l.laddr = &bindAddr{addr: l.options.Addr} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	l.laddr = laddr |  | ||||||
| 	l.router = chain.NewRouter( | 	l.router = chain.NewRouter( | ||||||
| 		chain.ChainRouterOption(l.options.Chain), | 		chain.ChainRouterOption(l.options.Chain), | ||||||
| 		chain.LoggerRouterOption(l.logger), | 		chain.LoggerRouterOption(l.logger), | ||||||
| @ -116,3 +117,15 @@ func (l *rudpListener) Close() error { | |||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type bindAddr struct { | ||||||
|  | 	addr string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *bindAddr) Network() string { | ||||||
|  | 	return "tcp" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *bindAddr) String() string { | ||||||
|  | 	return p.addr | ||||||
|  | } | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ import ( | |||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	defaultTTL            = 5 * time.Second | 	defaultTTL            = 5 * time.Second | ||||||
| 	defaultReadBufferSize = 4096 | 	defaultReadBufferSize = 1024 | ||||||
| 	defaultReadQueueSize  = 1024 | 	defaultReadQueueSize  = 1024 | ||||||
| 	defaultBacklog        = 128 | 	defaultBacklog        = 128 | ||||||
| ) | ) | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ import ( | |||||||
| 	"github.com/go-gost/core/listener" | 	"github.com/go-gost/core/listener" | ||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	mdata "github.com/go-gost/core/metadata" | 	mdata "github.com/go-gost/core/metadata" | ||||||
|  | 	"github.com/go-gost/core/router" | ||||||
| 	xnet "github.com/go-gost/x/internal/net" | 	xnet "github.com/go-gost/x/internal/net" | ||||||
| 	limiter "github.com/go-gost/x/limiter/traffic/wrapper" | 	limiter "github.com/go-gost/x/limiter/traffic/wrapper" | ||||||
| 	mdx "github.com/go-gost/x/metadata" | 	mdx "github.com/go-gost/x/metadata" | ||||||
| @ -26,6 +27,7 @@ type tunListener struct { | |||||||
| 	logger  logger.Logger | 	logger  logger.Logger | ||||||
| 	md      metadata | 	md      metadata | ||||||
| 	options listener.Options | 	options listener.Options | ||||||
|  | 	routes []*router.Route | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewListener(opts ...listener.Option) listener.Listener { | func NewListener(opts ...listener.Option) listener.Listener { | ||||||
|  | |||||||
| @ -4,9 +4,13 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/logger" | ||||||
| 	mdata "github.com/go-gost/core/metadata" | 	mdata "github.com/go-gost/core/metadata" | ||||||
| 	mdutil "github.com/go-gost/core/metadata/util" | 	mdutil "github.com/go-gost/core/metadata/util" | ||||||
|  | 	"github.com/go-gost/core/router" | ||||||
| 	tun_util "github.com/go-gost/x/internal/util/tun" | 	tun_util "github.com/go-gost/x/internal/util/tun" | ||||||
|  | 	"github.com/go-gost/x/registry" | ||||||
|  | 	xrouter "github.com/go-gost/x/router" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @ -39,6 +43,7 @@ func (l *tunListener) parseMetadata(md mdata.Metadata) (err error) { | |||||||
| 		Name:   mdutil.GetString(md, name), | 		Name:   mdutil.GetString(md, name), | ||||||
| 		Peer:   mdutil.GetString(md, peer), | 		Peer:   mdutil.GetString(md, peer), | ||||||
| 		MTU:    mdutil.GetInt(md, mtu), | 		MTU:    mdutil.GetInt(md, mtu), | ||||||
|  | 		Router: registry.RouterRegistry().Get(mdutil.GetString(md, "router")), | ||||||
| 	} | 	} | ||||||
| 	if config.MTU <= 0 { | 	if config.MTU <= 0 { | ||||||
| 		config.MTU = defaultMTU | 		config.MTU = defaultMTU | ||||||
| @ -62,35 +67,48 @@ func (l *tunListener) parseMetadata(md mdata.Metadata) (err error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, s := range strings.Split(mdutil.GetString(md, route), ",") { | 	for _, s := range strings.Split(mdutil.GetString(md, route), ",") { | ||||||
| 		var route tun_util.Route |  | ||||||
| 		_, ipNet, _ := net.ParseCIDR(strings.TrimSpace(s)) | 		_, ipNet, _ := net.ParseCIDR(strings.TrimSpace(s)) | ||||||
| 		if ipNet == nil { | 		if ipNet == nil { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		route.Net = *ipNet |  | ||||||
| 		route.Gateway = config.Gateway |  | ||||||
|  |  | ||||||
| 		config.Routes = append(config.Routes, route) | 		l.routes = append(l.routes, &router.Route{ | ||||||
|  | 			Net:     ipNet, | ||||||
|  | 			Gateway: config.Gateway, | ||||||
|  | 		}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, s := range mdutil.GetStrings(md, routes) { | 	for _, s := range mdutil.GetStrings(md, routes) { | ||||||
| 		ss := strings.SplitN(s, " ", 2) | 		ss := strings.SplitN(s, " ", 2) | ||||||
| 		if len(ss) == 2 { | 		if len(ss) == 2 { | ||||||
| 			var route tun_util.Route | 			var route router.Route | ||||||
| 			_, ipNet, _ := net.ParseCIDR(strings.TrimSpace(ss[0])) | 			_, ipNet, _ := net.ParseCIDR(strings.TrimSpace(ss[0])) | ||||||
| 			if ipNet == nil { | 			if ipNet == nil { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			route.Net = *ipNet | 			route.Net = ipNet | ||||||
| 			route.Gateway = net.ParseIP(ss[1]) | 			gw := net.ParseIP(ss[1]) | ||||||
| 			if route.Gateway == nil { | 			if gw == nil { | ||||||
| 				route.Gateway = config.Gateway | 				gw = config.Gateway | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			config.Routes = append(config.Routes, route) | 			l.routes = append(l.routes, &router.Route{ | ||||||
|  | 				Net:     ipNet, | ||||||
|  | 				Gateway: gw, | ||||||
|  | 			}) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if config.Router == nil && len(l.routes) > 0 { | ||||||
|  | 		config.Router = xrouter.NewRouter( | ||||||
|  | 			xrouter.RoutesOption(l.routes), | ||||||
|  | 			xrouter.LoggerOption(logger.Default().WithFields(map[string]any{ | ||||||
|  | 				"kind":   "router", | ||||||
|  | 				"router": "@internal", | ||||||
|  | 			})), | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	l.md.config = config | 	l.md.config = config | ||||||
|  |  | ||||||
| 	return | 	return | ||||||
|  | |||||||
| @ -6,8 +6,6 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	tun_util "github.com/go-gost/x/internal/util/tun" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @ -38,15 +36,15 @@ func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net. | |||||||
| 		ip = l.md.config.Net[0].IP | 		ip = l.md.config.Net[0].IP | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err = l.addRoutes(name, l.md.config.Routes...); err != nil { | 	if err = l.addRoutes(name); err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func (l *tunListener) addRoutes(ifName string, routes ...tun_util.Route) error { | func (l *tunListener) addRoutes(ifName string) error { | ||||||
| 	for _, route := range routes { | 	for _, route := range l.routes { | ||||||
| 		cmd := fmt.Sprintf("route add -net %s -interface %s", route.Net.String(), ifName) | 		cmd := fmt.Sprintf("route add -net %s -interface %s", route.Net.String(), ifName) | ||||||
| 		l.logger.Debug(cmd) | 		l.logger.Debug(cmd) | ||||||
| 		args := strings.Split(cmd, " ") | 		args := strings.Split(cmd, " ") | ||||||
|  | |||||||
| @ -6,8 +6,6 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
|  |  | ||||||
| 	"github.com/vishvananda/netlink" | 	"github.com/vishvananda/netlink" | ||||||
|  |  | ||||||
| 	tun_util "github.com/go-gost/x/internal/util/tun" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (l *tunListener) createTun() (dev io.ReadWriteCloser, name string, ip net.IP, err error) { | func (l *tunListener) createTun() (dev io.ReadWriteCloser, name string, ip net.IP, err error) { | ||||||
| @ -42,17 +40,17 @@ func (l *tunListener) createTun() (dev io.ReadWriteCloser, name string, ip net.I | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err = l.addRoutes(ifce, l.md.config.Routes...); err != nil { | 	if err = l.addRoutes(ifce); err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func (l *tunListener) addRoutes(ifce *net.Interface, routes ...tun_util.Route) error { | func (l *tunListener) addRoutes(ifce *net.Interface) error { | ||||||
| 	for _, route := range routes { | 	for _, route := range l.routes { | ||||||
| 		r := netlink.Route{ | 		r := netlink.Route{ | ||||||
| 			Dst: &route.Net, | 			Dst: route.Net, | ||||||
| 			Gw:  route.Gateway, | 			Gw:  route.Gateway, | ||||||
| 		} | 		} | ||||||
| 		if r.Gw == nil { | 		if r.Gw == nil { | ||||||
|  | |||||||
| @ -8,8 +8,6 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	tun_util "github.com/go-gost/x/internal/util/tun" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @ -38,15 +36,15 @@ func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net. | |||||||
| 		ip = l.md.config.Net[0].IP | 		ip = l.md.config.Net[0].IP | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err = l.addRoutes(name, l.md.config.Routes...); err != nil { | 	if err = l.addRoutes(name); err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func (l *tunListener) addRoutes(ifName string, routes ...tun_util.Route) error { | func (l *tunListener) addRoutes(ifName string) error { | ||||||
| 	for _, route := range routes { | 	for _, route := range l.routes { | ||||||
| 		cmd := fmt.Sprintf("route add -net %s -interface %s", route.Net.String(), ifName) | 		cmd := fmt.Sprintf("route add -net %s -interface %s", route.Net.String(), ifName) | ||||||
| 		l.logger.Debug(cmd) | 		l.logger.Debug(cmd) | ||||||
| 		args := strings.Split(cmd, " ") | 		args := strings.Split(cmd, " ") | ||||||
|  | |||||||
| @ -6,8 +6,6 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	tun_util "github.com/go-gost/x/internal/util/tun" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @ -38,15 +36,15 @@ func (l *tunListener) createTun() (ifce io.ReadWriteCloser, name string, ip net. | |||||||
| 		ip = ipNet.IP | 		ip = ipNet.IP | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err = l.addRoutes(name, l.md.config.Gateway, l.md.config.Routes...); err != nil { | 	if err = l.addRoutes(name, l.md.config.Gateway); err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| func (l *tunListener) addRoutes(ifName string, gw net.IP, routes ...tun_util.Route) error { | func (l *tunListener) addRoutes(ifName string, gw net.IP) error { | ||||||
| 	for _, route := range routes { | 	for _, route := range l.routes { | ||||||
| 		l.deleteRoute(ifName, route.Net.String()) | 		l.deleteRoute(ifName, route.Net.String()) | ||||||
|  |  | ||||||
| 		cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=%s store=active", | 		cmd := fmt.Sprintf("netsh interface ip add route prefix=%s interface=%s store=active", | ||||||
|  | |||||||
| @ -10,28 +10,35 @@ import ( | |||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type LoggerOptions struct { | type Options struct { | ||||||
|  | 	Name   string | ||||||
| 	Output io.Writer | 	Output io.Writer | ||||||
| 	Format logger.LogFormat | 	Format logger.LogFormat | ||||||
| 	Level  logger.LogLevel | 	Level  logger.LogLevel | ||||||
| } | } | ||||||
|  |  | ||||||
| type LoggerOption func(opts *LoggerOptions) | type Option func(opts *Options) | ||||||
|  |  | ||||||
| func OutputLoggerOption(out io.Writer) LoggerOption { | func NameOption(name string) Option { | ||||||
| 	return func(opts *LoggerOptions) { | 	return func(opts *Options) { | ||||||
|  | 		opts.Name = name | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func OutputOption(out io.Writer) Option { | ||||||
|  | 	return func(opts *Options) { | ||||||
| 		opts.Output = out | 		opts.Output = out | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func FormatLoggerOption(format logger.LogFormat) LoggerOption { | func FormatOption(format logger.LogFormat) Option { | ||||||
| 	return func(opts *LoggerOptions) { | 	return func(opts *Options) { | ||||||
| 		opts.Format = format | 		opts.Format = format | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func LevelLoggerOption(level logger.LogLevel) LoggerOption { | func LevelOption(level logger.LogLevel) Option { | ||||||
| 	return func(opts *LoggerOptions) { | 	return func(opts *Options) { | ||||||
| 		opts.Level = level | 		opts.Level = level | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @ -40,8 +47,8 @@ type logrusLogger struct { | |||||||
| 	logger *logrus.Entry | 	logger *logrus.Entry | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewLogger(opts ...LoggerOption) logger.Logger { | func NewLogger(opts ...Option) logger.Logger { | ||||||
| 	var options LoggerOptions | 	var options Options | ||||||
| 	for _, opt := range opts { | 	for _, opt := range opts { | ||||||
| 		opt(&options) | 		opt(&options) | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -30,19 +30,19 @@ type ingressWrapper struct { | |||||||
| 	r    *ingressRegistry | 	r    *ingressRegistry | ||||||
| } | } | ||||||
|  |  | ||||||
| func (w *ingressWrapper) Get(ctx context.Context, host string, opts ...ingress.GetOption) string { | func (w *ingressWrapper) GetRule(ctx context.Context, host string, opts ...ingress.Option) *ingress.Rule { | ||||||
| 	v := w.r.get(w.name) | 	v := w.r.get(w.name) | ||||||
| 	if v == nil { | 	if v == nil { | ||||||
| 		return "" | 		return nil | ||||||
| 	} | 	} | ||||||
| 	return v.Get(ctx, host, opts...) | 	return v.GetRule(ctx, host, opts...) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (w *ingressWrapper) Set(ctx context.Context, host, endpoint string, opts ...ingress.SetOption) { | func (w *ingressWrapper) SetRule(ctx context.Context, rule *ingress.Rule, opts ...ingress.Option) bool { | ||||||
| 	v := w.r.get(w.name) | 	v := w.r.get(w.name) | ||||||
| 	if v == nil { | 	if v == nil { | ||||||
| 		return | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	v.Set(ctx, host, endpoint, opts...) | 	return v.SetRule(ctx, rule, opts...) | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| package registry | package registry | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
|  |  | ||||||
| 	"github.com/go-gost/core/limiter/conn" | 	"github.com/go-gost/core/limiter/conn" | ||||||
| 	"github.com/go-gost/core/limiter/rate" | 	"github.com/go-gost/core/limiter/rate" | ||||||
| 	"github.com/go-gost/core/limiter/traffic" | 	"github.com/go-gost/core/limiter/traffic" | ||||||
| @ -30,20 +32,20 @@ type trafficLimiterWrapper struct { | |||||||
| 	r    *trafficLimiterRegistry | 	r    *trafficLimiterRegistry | ||||||
| } | } | ||||||
|  |  | ||||||
| func (w *trafficLimiterWrapper) In(key string) traffic.Limiter { | func (w *trafficLimiterWrapper) In(ctx context.Context, key string, opts ...traffic.Option) traffic.Limiter { | ||||||
| 	v := w.r.get(w.name) | 	v := w.r.get(w.name) | ||||||
| 	if v == nil { | 	if v == nil { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	return v.In(key) | 	return v.In(ctx, key, opts...) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (w *trafficLimiterWrapper) Out(key string) traffic.Limiter { | func (w *trafficLimiterWrapper) Out(ctx context.Context, key string, opts ...traffic.Option) traffic.Limiter { | ||||||
| 	v := w.r.get(w.name) | 	v := w.r.get(w.name) | ||||||
| 	if v == nil { | 	if v == nil { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	return v.Out(key) | 	return v.Out(ctx, key, opts...) | ||||||
| } | } | ||||||
|  |  | ||||||
| type connLimiterRegistry struct { | type connLimiterRegistry struct { | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								registry/logger.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								registry/logger.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | package registry | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/go-gost/core/logger" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type loggerRegistry struct { | ||||||
|  | 	registry[logger.Logger] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *loggerRegistry) Register(name string, v logger.Logger) error { | ||||||
|  | 	return r.registry.Register(name, v) | ||||||
|  | } | ||||||
| @ -15,9 +15,11 @@ import ( | |||||||
| 	"github.com/go-gost/core/limiter/conn" | 	"github.com/go-gost/core/limiter/conn" | ||||||
| 	"github.com/go-gost/core/limiter/rate" | 	"github.com/go-gost/core/limiter/rate" | ||||||
| 	"github.com/go-gost/core/limiter/traffic" | 	"github.com/go-gost/core/limiter/traffic" | ||||||
|  | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/core/recorder" | 	"github.com/go-gost/core/recorder" | ||||||
| 	reg "github.com/go-gost/core/registry" | 	reg "github.com/go-gost/core/registry" | ||||||
| 	"github.com/go-gost/core/resolver" | 	"github.com/go-gost/core/resolver" | ||||||
|  | 	"github.com/go-gost/core/router" | ||||||
| 	"github.com/go-gost/core/sd" | 	"github.com/go-gost/core/sd" | ||||||
| 	"github.com/go-gost/core/service" | 	"github.com/go-gost/core/service" | ||||||
| ) | ) | ||||||
| @ -46,7 +48,10 @@ var ( | |||||||
| 	rateLimiterReg    reg.Registry[rate.RateLimiter]       = new(rateLimiterRegistry) | 	rateLimiterReg    reg.Registry[rate.RateLimiter]       = new(rateLimiterRegistry) | ||||||
|  |  | ||||||
| 	ingressReg reg.Registry[ingress.Ingress] = new(ingressRegistry) | 	ingressReg reg.Registry[ingress.Ingress] = new(ingressRegistry) | ||||||
|  | 	routerReg  reg.Registry[router.Router]   = new(routerRegistry) | ||||||
| 	sdReg      reg.Registry[sd.SD]           = new(sdRegistry) | 	sdReg      reg.Registry[sd.SD]           = new(sdRegistry) | ||||||
|  |  | ||||||
|  | 	loggerReg reg.Registry[logger.Logger] = new(loggerRegistry) | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type registry[T any] struct { | type registry[T any] struct { | ||||||
| @ -166,6 +171,14 @@ func IngressRegistry() reg.Registry[ingress.Ingress] { | |||||||
| 	return ingressReg | 	return ingressReg | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func RouterRegistry() reg.Registry[router.Router] { | ||||||
|  | 	return routerReg | ||||||
|  | } | ||||||
|  |  | ||||||
| func SDRegistry() reg.Registry[sd.SD] { | func SDRegistry() reg.Registry[sd.SD] { | ||||||
| 	return sdReg | 	return sdReg | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func LoggerRegistry() reg.Registry[logger.Logger] { | ||||||
|  | 	return loggerReg | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										40
									
								
								registry/router.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								registry/router.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | package registry | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"net" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/router" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type routerRegistry struct { | ||||||
|  | 	registry[router.Router] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *routerRegistry) Register(name string, v router.Router) error { | ||||||
|  | 	return r.registry.Register(name, v) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *routerRegistry) Get(name string) router.Router { | ||||||
|  | 	if name != "" { | ||||||
|  | 		return &routerWrapper{name: name, r: r} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *routerRegistry) get(name string) router.Router { | ||||||
|  | 	return r.registry.Get(name) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type routerWrapper struct { | ||||||
|  | 	name string | ||||||
|  | 	r    *routerRegistry | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (w *routerWrapper) GetRoute(ctx context.Context, dst net.IP, opts ...router.Option) *router.Route { | ||||||
|  | 	v := w.r.get(w.name) | ||||||
|  | 	if v == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return v.GetRoute(ctx, dst, opts...) | ||||||
|  | } | ||||||
| @ -13,8 +13,8 @@ import ( | |||||||
| 	"github.com/go-gost/core/logger" | 	"github.com/go-gost/core/logger" | ||||||
| 	"github.com/go-gost/core/resolver" | 	"github.com/go-gost/core/resolver" | ||||||
| 	"github.com/go-gost/plugin/resolver/proto" | 	"github.com/go-gost/plugin/resolver/proto" | ||||||
|  | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	"github.com/go-gost/x/internal/plugin" | 	"github.com/go-gost/x/internal/plugin" | ||||||
| 	auth_util "github.com/go-gost/x/internal/util/auth" |  | ||||||
| 	"google.golang.org/grpc" | 	"google.golang.org/grpc" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -60,7 +60,7 @@ func (p *grpcPlugin) Resolve(ctx context.Context, network, host string, opts ... | |||||||
| 		&proto.ResolveRequest{ | 		&proto.ResolveRequest{ | ||||||
| 			Network: network, | 			Network: network, | ||||||
| 			Host:    host, | 			Host:    host, | ||||||
| 			Client:  string(auth_util.IDFromContext(ctx)), | 			Client:  string(ctxvalue.ClientIDFromContext(ctx)), | ||||||
| 		}) | 		}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		p.log.Error(err) | 		p.log.Error(err) | ||||||
| @ -127,7 +127,7 @@ func (p *httpPlugin) Resolve(ctx context.Context, network, host string, opts ... | |||||||
| 	rb := httpPluginRequest{ | 	rb := httpPluginRequest{ | ||||||
| 		Network: network, | 		Network: network, | ||||||
| 		Host:    host, | 		Host:    host, | ||||||
| 		Client:  string(auth_util.IDFromContext(ctx)), | 		Client:  string(ctxvalue.ClientIDFromContext(ctx)), | ||||||
| 	} | 	} | ||||||
| 	v, err := json.Marshal(&rb) | 	v, err := json.Marshal(&rb) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | |||||||
							
								
								
									
										141
									
								
								router/plugin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								router/plugin.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,141 @@ | |||||||
|  | package router | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
|  | 	"net" | ||||||
|  | 	"net/http" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/logger" | ||||||
|  | 	"github.com/go-gost/core/router" | ||||||
|  | 	"github.com/go-gost/plugin/router/proto" | ||||||
|  | 	"github.com/go-gost/x/internal/plugin" | ||||||
|  | 	"google.golang.org/grpc" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type grpcPlugin struct { | ||||||
|  | 	conn   grpc.ClientConnInterface | ||||||
|  | 	client proto.RouterClient | ||||||
|  | 	log    logger.Logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewGRPCPlugin creates an Router plugin based on gRPC. | ||||||
|  | func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) router.Router { | ||||||
|  | 	var options plugin.Options | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt(&options) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	log := logger.Default().WithFields(map[string]any{ | ||||||
|  | 		"kind":   "router", | ||||||
|  | 		"router": name, | ||||||
|  | 	}) | ||||||
|  | 	conn, err := plugin.NewGRPCConn(addr, &options) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	p := &grpcPlugin{ | ||||||
|  | 		conn: conn, | ||||||
|  | 		log:  log, | ||||||
|  | 	} | ||||||
|  | 	if conn != nil { | ||||||
|  | 		p.client = proto.NewRouterClient(conn) | ||||||
|  | 	} | ||||||
|  | 	return p | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *grpcPlugin) GetRoute(ctx context.Context, dst net.IP, opts ...router.Option) *router.Route { | ||||||
|  | 	if p.client == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r, err := p.client.GetRoute(ctx, | ||||||
|  | 		&proto.GetRouteRequest{ | ||||||
|  | 			Dst: dst.String(), | ||||||
|  | 		}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		p.log.Error(err) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ParseRoute(r.Net, r.Gateway) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *grpcPlugin) Close() error { | ||||||
|  | 	if closer, ok := p.conn.(io.Closer); ok { | ||||||
|  | 		return closer.Close() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type httpPluginGetRouteRequest struct { | ||||||
|  | 	Dst string `json:"dst"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type httpPluginGetRouteResponse struct { | ||||||
|  | 	Net     string `json:"net"` | ||||||
|  | 	Gateway string `json:"gateway"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type httpPlugin struct { | ||||||
|  | 	url    string | ||||||
|  | 	client *http.Client | ||||||
|  | 	header http.Header | ||||||
|  | 	log    logger.Logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewHTTPPlugin creates an Router plugin based on HTTP. | ||||||
|  | func NewHTTPPlugin(name string, url string, opts ...plugin.Option) router.Router { | ||||||
|  | 	var options plugin.Options | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt(&options) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &httpPlugin{ | ||||||
|  | 		url:    url, | ||||||
|  | 		client: plugin.NewHTTPClient(&options), | ||||||
|  | 		header: options.Header, | ||||||
|  | 		log: logger.Default().WithFields(map[string]any{ | ||||||
|  | 			"kind":   "router", | ||||||
|  | 			"router": name, | ||||||
|  | 		}), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *httpPlugin) GetRoute(ctx context.Context, dst net.IP, opts ...router.Option) *router.Route { | ||||||
|  | 	if p.client == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.url, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if p.header != nil { | ||||||
|  | 		req.Header = p.header.Clone() | ||||||
|  | 	} | ||||||
|  | 	req.Header.Set("Content-Type", "application/json") | ||||||
|  |  | ||||||
|  | 	q := req.URL.Query() | ||||||
|  | 	q.Set("dst", dst.String()) | ||||||
|  | 	req.URL.RawQuery = q.Encode() | ||||||
|  |  | ||||||
|  | 	resp, err := p.client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
|  | 	if resp.StatusCode != http.StatusOK { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	res := httpPluginGetRouteResponse{} | ||||||
|  | 	if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ParseRoute(res.Net, res.Gateway) | ||||||
|  | } | ||||||
							
								
								
									
										261
									
								
								router/router.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								router/router.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,261 @@ | |||||||
|  | package router | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"context" | ||||||
|  | 	"io" | ||||||
|  | 	"net" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/go-gost/core/logger" | ||||||
|  | 	"github.com/go-gost/core/router" | ||||||
|  | 	"github.com/go-gost/x/internal/loader" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type options struct { | ||||||
|  | 	routes      []*router.Route | ||||||
|  | 	fileLoader  loader.Loader | ||||||
|  | 	redisLoader loader.Loader | ||||||
|  | 	httpLoader  loader.Loader | ||||||
|  | 	period      time.Duration | ||||||
|  | 	logger      logger.Logger | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Option func(opts *options) | ||||||
|  |  | ||||||
|  | func RoutesOption(routes []*router.Route) Option { | ||||||
|  | 	return func(opts *options) { | ||||||
|  | 		opts.routes = routes | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ReloadPeriodOption(period time.Duration) Option { | ||||||
|  | 	return func(opts *options) { | ||||||
|  | 		opts.period = period | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func FileLoaderOption(fileLoader loader.Loader) Option { | ||||||
|  | 	return func(opts *options) { | ||||||
|  | 		opts.fileLoader = fileLoader | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func RedisLoaderOption(redisLoader loader.Loader) Option { | ||||||
|  | 	return func(opts *options) { | ||||||
|  | 		opts.redisLoader = redisLoader | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func HTTPLoaderOption(httpLoader loader.Loader) Option { | ||||||
|  | 	return func(opts *options) { | ||||||
|  | 		opts.httpLoader = httpLoader | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func LoggerOption(logger logger.Logger) Option { | ||||||
|  | 	return func(opts *options) { | ||||||
|  | 		opts.logger = logger | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type localRouter struct { | ||||||
|  | 	routes     []*router.Route | ||||||
|  | 	cancelFunc context.CancelFunc | ||||||
|  | 	options    options | ||||||
|  | 	mu         sync.RWMutex | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewRouter creates and initializes a new Router. | ||||||
|  | func NewRouter(opts ...Option) router.Router { | ||||||
|  | 	var options options | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt(&options) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx, cancel := context.WithCancel(context.TODO()) | ||||||
|  |  | ||||||
|  | 	r := &localRouter{ | ||||||
|  | 		cancelFunc: cancel, | ||||||
|  | 		options:    options, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := r.reload(ctx); err != nil { | ||||||
|  | 		options.logger.Warnf("reload: %v", err) | ||||||
|  | 	} | ||||||
|  | 	if r.options.period > 0 { | ||||||
|  | 		go r.periodReload(ctx) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return r | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *localRouter) periodReload(ctx context.Context) error { | ||||||
|  | 	period := p.options.period | ||||||
|  | 	if period < time.Second { | ||||||
|  | 		period = time.Second | ||||||
|  | 	} | ||||||
|  | 	ticker := time.NewTicker(period) | ||||||
|  | 	defer ticker.Stop() | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		select { | ||||||
|  | 		case <-ticker.C: | ||||||
|  | 			if err := p.reload(ctx); err != nil { | ||||||
|  | 				p.options.logger.Warnf("reload: %v", err) | ||||||
|  | 				// return err | ||||||
|  | 			} | ||||||
|  | 		case <-ctx.Done(): | ||||||
|  | 			return ctx.Err() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *localRouter) reload(ctx context.Context) error { | ||||||
|  | 	routes := r.options.routes | ||||||
|  |  | ||||||
|  | 	v, err := r.load(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	routes = append(routes, v...) | ||||||
|  |  | ||||||
|  | 	r.mu.Lock() | ||||||
|  | 	defer r.mu.Unlock() | ||||||
|  |  | ||||||
|  | 	r.routes = routes | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *localRouter) load(ctx context.Context) (routes []*router.Route, err error) { | ||||||
|  | 	if p.options.fileLoader != nil { | ||||||
|  | 		if lister, ok := p.options.fileLoader.(loader.Lister); ok { | ||||||
|  | 			list, er := lister.List(ctx) | ||||||
|  | 			if er != nil { | ||||||
|  | 				p.options.logger.Warnf("file loader: %v", er) | ||||||
|  | 			} | ||||||
|  | 			for _, s := range list { | ||||||
|  | 				routes = append(routes, p.parseLine(s)) | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			fr, er := p.options.fileLoader.Load(ctx) | ||||||
|  | 			if er != nil { | ||||||
|  | 				p.options.logger.Warnf("file loader: %v", er) | ||||||
|  | 			} | ||||||
|  | 			if v, _ := p.parseRoutes(fr); v != nil { | ||||||
|  | 				routes = append(routes, v...) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if p.options.redisLoader != nil { | ||||||
|  | 		if lister, ok := p.options.redisLoader.(loader.Lister); ok { | ||||||
|  | 			list, er := lister.List(ctx) | ||||||
|  | 			if er != nil { | ||||||
|  | 				p.options.logger.Warnf("redis loader: %v", er) | ||||||
|  | 			} | ||||||
|  | 			for _, v := range list { | ||||||
|  | 				routes = append(routes, p.parseLine(v)) | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			r, er := p.options.redisLoader.Load(ctx) | ||||||
|  | 			if er != nil { | ||||||
|  | 				p.options.logger.Warnf("redis loader: %v", er) | ||||||
|  | 			} | ||||||
|  | 			v, _ := p.parseRoutes(r) | ||||||
|  | 			routes = append(routes, v...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if p.options.httpLoader != nil { | ||||||
|  | 		r, er := p.options.httpLoader.Load(ctx) | ||||||
|  | 		if er != nil { | ||||||
|  | 			p.options.logger.Warnf("http loader: %v", er) | ||||||
|  | 		} | ||||||
|  | 		v, _ := p.parseRoutes(r) | ||||||
|  | 		routes = append(routes, v...) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	p.options.logger.Debugf("load items %d", len(routes)) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *localRouter) parseRoutes(r io.Reader) (routes []*router.Route, err error) { | ||||||
|  | 	if r == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	scanner := bufio.NewScanner(r) | ||||||
|  | 	for scanner.Scan() { | ||||||
|  | 		if route := p.parseLine(scanner.Text()); route != nil { | ||||||
|  | 			routes = append(routes, route) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = scanner.Err() | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *localRouter) GetRoute(ctx context.Context, dst net.IP, opts ...router.Option) *router.Route { | ||||||
|  | 	if dst == nil || p == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	p.mu.RLock() | ||||||
|  | 	routes := p.routes | ||||||
|  | 	p.mu.RUnlock() | ||||||
|  |  | ||||||
|  | 	for _, route := range routes { | ||||||
|  | 		if route.Net != nil && route.Net.Contains(dst) { | ||||||
|  | 			return route | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (*localRouter) parseLine(s string) (route *router.Route) { | ||||||
|  | 	line := strings.Replace(s, "\t", " ", -1) | ||||||
|  | 	line = strings.TrimSpace(line) | ||||||
|  | 	if n := strings.IndexByte(line, '#'); n >= 0 { | ||||||
|  | 		line = line[:n] | ||||||
|  | 	} | ||||||
|  | 	var sp []string | ||||||
|  | 	for _, s := range strings.Split(line, " ") { | ||||||
|  | 		if s = strings.TrimSpace(s); s != "" { | ||||||
|  | 			sp = append(sp, s) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(sp) < 2 { | ||||||
|  | 		return // invalid lines are ignored | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ParseRoute(sp[0], sp[1]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p *localRouter) Close() error { | ||||||
|  | 	p.cancelFunc() | ||||||
|  | 	if p.options.fileLoader != nil { | ||||||
|  | 		p.options.fileLoader.Close() | ||||||
|  | 	} | ||||||
|  | 	if p.options.redisLoader != nil { | ||||||
|  | 		p.options.redisLoader.Close() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ParseRoute(dst string, gateway string) *router.Route { | ||||||
|  | 	_, ipNet, _ := net.ParseCIDR(dst) | ||||||
|  | 	if ipNet == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	gw := net.ParseIP(gateway) | ||||||
|  | 	if gw == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &router.Route{ | ||||||
|  | 		Net:     ipNet, | ||||||
|  | 		Gateway: gw, | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -12,7 +12,7 @@ import ( | |||||||
| 	"github.com/go-gost/core/metadata" | 	"github.com/go-gost/core/metadata" | ||||||
| 	mdutil "github.com/go-gost/core/metadata/util" | 	mdutil "github.com/go-gost/core/metadata/util" | ||||||
| 	"github.com/go-gost/core/selector" | 	"github.com/go-gost/core/selector" | ||||||
| 	sx "github.com/go-gost/x/internal/util/selector" | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type roundRobinStrategy[T any] struct { | type roundRobinStrategy[T any] struct { | ||||||
| @ -102,7 +102,7 @@ func (s *hashStrategy[T]) Apply(ctx context.Context, vs ...T) (v T) { | |||||||
| 	if len(vs) == 0 { | 	if len(vs) == 0 { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if h := sx.HashFromContext(ctx); h != nil { | 	if h := ctxvalue.HashFromContext(ctx); h != nil { | ||||||
| 		value := uint64(crc32.ChecksumIEEE([]byte(h.Source))) | 		value := uint64(crc32.ChecksumIEEE([]byte(h.Source))) | ||||||
| 		logger.Default().Tracef("hash %s %d", h.Source, value) | 		logger.Default().Tracef("hash %s %d", h.Source, value) | ||||||
| 		return vs[value%uint64(len(vs))] | 		return vs[value%uint64(len(vs))] | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ import ( | |||||||
| 	"github.com/go-gost/core/metrics" | 	"github.com/go-gost/core/metrics" | ||||||
| 	"github.com/go-gost/core/recorder" | 	"github.com/go-gost/core/recorder" | ||||||
| 	"github.com/go-gost/core/service" | 	"github.com/go-gost/core/service" | ||||||
| 	sx "github.com/go-gost/x/internal/util/selector" | 	ctxvalue "github.com/go-gost/x/internal/ctx" | ||||||
| 	xmetrics "github.com/go-gost/x/metrics" | 	xmetrics "github.com/go-gost/x/metrics" | ||||||
| 	"github.com/rs/xid" | 	"github.com/rs/xid" | ||||||
| ) | ) | ||||||
| @ -145,20 +145,26 @@ func (s *defaultService) Serve() error { | |||||||
| 		} | 		} | ||||||
| 		tempDelay = 0 | 		tempDelay = 0 | ||||||
|  |  | ||||||
| 		host := conn.RemoteAddr().String() | 		clientAddr := conn.RemoteAddr().String() | ||||||
| 		if h, _, _ := net.SplitHostPort(host); h != "" { | 		clientIP := clientAddr | ||||||
| 			host = h | 		if h, _, _ := net.SplitHostPort(clientAddr); h != "" { | ||||||
|  | 			clientIP = h | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		ctx := ctxvalue.ContextWithSid(context.Background(), ctxvalue.Sid(xid.New().String())) | ||||||
|  | 		ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(clientAddr)) | ||||||
|  | 		ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: clientIP}) | ||||||
|  |  | ||||||
| 		for _, rec := range s.options.recorders { | 		for _, rec := range s.options.recorders { | ||||||
| 			if rec.Record == recorder.RecorderServiceClientAddress { | 			if rec.Record == recorder.RecorderServiceClientAddress { | ||||||
| 				if err := rec.Recorder.Record(context.Background(), []byte(host)); err != nil { | 				if err := rec.Recorder.Record(ctx, []byte(clientIP)); err != nil { | ||||||
| 					s.options.logger.Errorf("record %s: %v", rec.Record, err) | 					s.options.logger.Errorf("record %s: %v", rec.Record, err) | ||||||
| 				} | 				} | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		if s.options.admission != nil && | 		if s.options.admission != nil && | ||||||
| 			!s.options.admission.Admit(context.Background(), conn.RemoteAddr().String()) { | 			!s.options.admission.Admit(ctx, conn.RemoteAddr().String()) { | ||||||
| 			conn.Close() | 			conn.Close() | ||||||
| 			s.options.logger.Debugf("admission: %s is denied", conn.RemoteAddr()) | 			s.options.logger.Debugf("admission: %s is denied", conn.RemoteAddr()) | ||||||
| 			continue | 			continue | ||||||
| @ -166,12 +172,12 @@ func (s *defaultService) Serve() error { | |||||||
|  |  | ||||||
| 		go func() { | 		go func() { | ||||||
| 			if v := xmetrics.GetCounter(xmetrics.MetricServiceRequestsCounter, | 			if v := xmetrics.GetCounter(xmetrics.MetricServiceRequestsCounter, | ||||||
| 				metrics.Labels{"service": s.name, "client": host}); v != nil { | 				metrics.Labels{"service": s.name, "client": clientIP}); v != nil { | ||||||
| 				v.Inc() | 				v.Inc() | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if v := xmetrics.GetGauge(xmetrics.MetricServiceRequestsInFlightGauge, | 			if v := xmetrics.GetGauge(xmetrics.MetricServiceRequestsInFlightGauge, | ||||||
| 				metrics.Labels{"service": s.name, "client": host}); v != nil { | 				metrics.Labels{"service": s.name, "client": clientIP}); v != nil { | ||||||
| 				v.Inc() | 				v.Inc() | ||||||
| 				defer v.Dec() | 				defer v.Dec() | ||||||
| 			} | 			} | ||||||
| @ -184,13 +190,10 @@ func (s *defaultService) Serve() error { | |||||||
| 				}() | 				}() | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			ctx := sx.ContextWithHash(context.Background(), &sx.Hash{Source: host}) |  | ||||||
| 			ctx = ContextWithSid(ctx, xid.New().String()) |  | ||||||
|  |  | ||||||
| 			if err := s.handler.Handle(ctx, conn); err != nil { | 			if err := s.handler.Handle(ctx, conn); err != nil { | ||||||
| 				s.options.logger.Error(err) | 				s.options.logger.Error(err) | ||||||
| 				if v := xmetrics.GetCounter(xmetrics.MetricServiceHandlerErrorsCounter, | 				if v := xmetrics.GetCounter(xmetrics.MetricServiceHandlerErrorsCounter, | ||||||
| 					metrics.Labels{"service": s.name, "client": host}); v != nil { | 					metrics.Labels{"service": s.name, "client": clientIP}); v != nil { | ||||||
| 					v.Inc() | 					v.Inc() | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| @ -211,18 +214,3 @@ func (s *defaultService) execCmds(phase string, cmds []string) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| type sidKey struct{} |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	ssid sidKey |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func ContextWithSid(ctx context.Context, sid string) context.Context { |  | ||||||
| 	return context.WithValue(ctx, ssid, sid) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func SidFromContext(ctx context.Context) string { |  | ||||||
| 	v, _ := ctx.Value(ssid).(string) |  | ||||||
| 	return v |  | ||||||
| } |  | ||||||
|  | |||||||
							
								
								
									
										76
									
								
								util/utls.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								util/utls.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | |||||||
|  | package util | ||||||
|  |  | ||||||
|  | import tls "github.com/refraction-networking/utls" | ||||||
|  |  | ||||||
|  | func NewWsSpec() *tls.ClientHelloSpec { | ||||||
|  | 	return &tls.ClientHelloSpec{ | ||||||
|  | 		CipherSuites: []uint16{ | ||||||
|  | 			tls.GREASE_PLACEHOLDER, | ||||||
|  | 			tls.TLS_AES_128_GCM_SHA256, | ||||||
|  | 			tls.TLS_AES_256_GCM_SHA384, | ||||||
|  | 			tls.TLS_CHACHA20_POLY1305_SHA256, | ||||||
|  | 			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | ||||||
|  | 			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | ||||||
|  | 			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, | ||||||
|  | 			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, | ||||||
|  | 			tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, | ||||||
|  | 			tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, | ||||||
|  | 			tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, | ||||||
|  | 			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, | ||||||
|  | 			tls.TLS_RSA_WITH_AES_128_GCM_SHA256, | ||||||
|  | 			tls.TLS_RSA_WITH_AES_256_GCM_SHA384, | ||||||
|  | 			tls.TLS_RSA_WITH_AES_128_CBC_SHA, | ||||||
|  | 			tls.TLS_RSA_WITH_AES_256_CBC_SHA, | ||||||
|  | 		}, | ||||||
|  | 		CompressionMethods: []byte{ | ||||||
|  | 			0x00, // compressionNone | ||||||
|  | 		}, | ||||||
|  | 		Extensions: []tls.TLSExtension{ | ||||||
|  | 			&tls.UtlsGREASEExtension{}, | ||||||
|  | 			&tls.SNIExtension{}, | ||||||
|  | 			&tls.ExtendedMasterSecretExtension{}, | ||||||
|  | 			&tls.RenegotiationInfoExtension{Renegotiation: tls.RenegotiateOnceAsClient}, | ||||||
|  | 			&tls.SupportedCurvesExtension{[]tls.CurveID{ | ||||||
|  | 				tls.GREASE_PLACEHOLDER, | ||||||
|  | 				tls.X25519, | ||||||
|  | 				tls.CurveP256, | ||||||
|  | 				tls.CurveP384, | ||||||
|  | 			}}, | ||||||
|  | 			&tls.SupportedPointsExtension{SupportedPoints: []byte{ | ||||||
|  | 				0x00, // pointFormatUncompressed | ||||||
|  | 			}}, | ||||||
|  | 			&tls.SessionTicketExtension{}, | ||||||
|  | 			&tls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}}, | ||||||
|  | 			&tls.StatusRequestExtension{}, | ||||||
|  | 			&tls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []tls.SignatureScheme{ | ||||||
|  | 				tls.ECDSAWithP256AndSHA256, | ||||||
|  | 				tls.PSSWithSHA256, | ||||||
|  | 				tls.PKCS1WithSHA256, | ||||||
|  | 				tls.ECDSAWithP384AndSHA384, | ||||||
|  | 				tls.PSSWithSHA384, | ||||||
|  | 				tls.PKCS1WithSHA384, | ||||||
|  | 				tls.PSSWithSHA512, | ||||||
|  | 				tls.PKCS1WithSHA512, | ||||||
|  | 			}}, | ||||||
|  | 			&tls.SCTExtension{}, | ||||||
|  | 			&tls.KeyShareExtension{[]tls.KeyShare{ | ||||||
|  | 				{Group: tls.CurveID(tls.GREASE_PLACEHOLDER), Data: []byte{0}}, | ||||||
|  | 				{Group: tls.X25519}, | ||||||
|  | 			}}, | ||||||
|  | 			&tls.PSKKeyExchangeModesExtension{[]uint8{ | ||||||
|  | 				tls.PskModeDHE, | ||||||
|  | 			}}, | ||||||
|  | 			&tls.SupportedVersionsExtension{[]uint16{ | ||||||
|  | 				tls.GREASE_PLACEHOLDER, | ||||||
|  | 				tls.VersionTLS13, | ||||||
|  | 				tls.VersionTLS12, | ||||||
|  | 			}}, | ||||||
|  | 			&tls.UtlsCompressCertExtension{[]tls.CertCompressionAlgo{ | ||||||
|  | 				tls.CertCompressionBrotli, | ||||||
|  | 			}}, | ||||||
|  | 			&tls.ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}}, | ||||||
|  | 			&tls.UtlsGREASEExtension{}, | ||||||
|  | 			&tls.UtlsPaddingExtension{GetPaddingLen: tls.BoringPaddingStyle}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	