From 97c981764da6fe954093856db83b2a434839397f Mon Sep 17 00:00:00 2001 From: Shtorm <108103062+shtorm-7@users.noreply.github.com> Date: Sat, 8 Aug 2026 10:33:31 +0300 Subject: [PATCH] Add DefaultGateway --- .../client1.json | 53 ++++++++++++++++ .../client2.json | 40 ++++++++++++ .../server.json | 61 +++++++++++++++++++ .../vpn/client1-server-client2/client1.json | 9 +-- .../server.json | 4 +- examples/vpn/server-client/server.json | 9 +-- protocol/vpn/client.go | 37 ++++++----- protocol/vpn/server.go | 28 ++++++--- 8 files changed, 202 insertions(+), 39 deletions(-) create mode 100644 examples/vpn/client1-server-client2-override-gateway/client1.json create mode 100644 examples/vpn/client1-server-client2-override-gateway/client2.json create mode 100644 examples/vpn/client1-server-client2-override-gateway/server.json diff --git a/examples/vpn/client1-server-client2-override-gateway/client1.json b/examples/vpn/client1-server-client2-override-gateway/client1.json new file mode 100644 index 00000000..b5f4054c --- /dev/null +++ b/examples/vpn/client1-server-client2-override-gateway/client1.json @@ -0,0 +1,53 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "type": "local", + "tag": "default" + } + ] + }, + "endpoints": [ + { + "type": "vpn-client", + "tag": "vpn", + "address": "10.0.0.2", + "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe", + "outbound": { + "type": "vless", + "tag": "vless-out", + "server": "0.0.0.0", + "server_port": 8000, + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "network": "tcp" + } + } + ], + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + "listen_port": 7897 + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + } + ], + "route": { + "rules": [ + { + "domain_suffix": ".internal", + "outbound": "vpn", + "override_gateway": "10.0.0.3" + } + ], + "final": "direct-out", + "auto_detect_interface": true + } +} diff --git a/examples/vpn/client1-server-client2-override-gateway/client2.json b/examples/vpn/client1-server-client2-override-gateway/client2.json new file mode 100644 index 00000000..907f638e --- /dev/null +++ b/examples/vpn/client1-server-client2-override-gateway/client2.json @@ -0,0 +1,40 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "type": "local", + "tag": "default" + } + ] + }, + "endpoints": [ + { + "type": "vpn-client", + "tag": "vpn", + "address": "10.0.0.3", + "key": "3d74d616-2502-4c17-9cc3-92c366550f4f", + "outbound": { + "type": "vless", + "tag": "vless-out", + "server": "0.0.0.0", + "server_port": 8000, + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937", + "network": "tcp" + } + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + } + ], + "route": { + "final": "direct-out", + "default_domain_resolver": "default", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/examples/vpn/client1-server-client2-override-gateway/server.json b/examples/vpn/client1-server-client2-override-gateway/server.json new file mode 100644 index 00000000..bc6aafb5 --- /dev/null +++ b/examples/vpn/client1-server-client2-override-gateway/server.json @@ -0,0 +1,61 @@ +{ + "log": { + "level": "error" + }, + "dns": { + "servers": [ + { + "type": "local", + "tag": "default" + } + ] + }, + "endpoints": [ + { + "type": "vpn-server", + "tag": "vpn", + "address": "10.0.0.1", + "users": [ + { + "address": "10.0.0.2", + "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe" + }, + { + "address": "10.0.0.3", + "key": "3d74d616-2502-4c17-9cc3-92c366550f4f" + } + ], + "inbounds": [ + { + "type": "vless", + "tag": "vless-in", + "listen": "0.0.0.0", + "listen_port": 8000, + "users": [ + { + "name": "vless", + "uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937" + } + ] + } + ] + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct-out" + } + ], + "route": { + "rules": [ + { + "source_ip_cidr": "10.0.0.0/24", + "outbound": "vpn" + } + ], + "final": "direct-out", + "default_domain_resolver": "default", + "auto_detect_interface": true + } +} \ No newline at end of file diff --git a/examples/vpn/client1-server-client2/client1.json b/examples/vpn/client1-server-client2/client1.json index 70600225..a51bc855 100644 --- a/examples/vpn/client1-server-client2/client1.json +++ b/examples/vpn/client1-server-client2/client1.json @@ -16,6 +16,7 @@ "tag": "vpn", "address": "10.0.0.2", "key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe", + "default_gateway": "10.0.0.3", "outbound": { "type": "vless", "tag": "vless-out", @@ -40,13 +41,7 @@ } ], "route": { - "rules": [ - { - "outbound": "vpn", - "override_gateway": "10.0.0.3" - } - ], - "final": "direct-out", + "final": "vpn", "auto_detect_interface": true } } \ No newline at end of file diff --git a/examples/vpn/proxy_client-server-vpn_client/server.json b/examples/vpn/proxy_client-server-vpn_client/server.json index b5b62f8e..dc5c78b9 100644 --- a/examples/vpn/proxy_client-server-vpn_client/server.json +++ b/examples/vpn/proxy_client-server-vpn_client/server.json @@ -15,6 +15,7 @@ "type": "vpn-server", "tag": "vpn", "address": "10.0.0.1", + "default_gateway": "10.0.0.2", "users": [ { "address": "10.0.0.2", @@ -47,8 +48,7 @@ "rules": [ { "inbound": "vless-in", - "outbound": "vpn", - "override_gateway": "10.0.0.2" + "outbound": "vpn" } ], "final": "direct-out", diff --git a/examples/vpn/server-client/server.json b/examples/vpn/server-client/server.json index 6900cd1c..0cb0b224 100644 --- a/examples/vpn/server-client/server.json +++ b/examples/vpn/server-client/server.json @@ -15,6 +15,7 @@ "type": "vpn-server", "tag": "vpn", "address": "10.0.0.1", + "default_gateway": "10.0.0.2", "users": [ { "address": "10.0.0.2", @@ -51,13 +52,7 @@ } ], "route": { - "rules": [ - { - "outbound": "vpn", - "override_gateway": "10.0.0.2" - } - ], - "final": "direct-out", + "final": "vpn", "default_domain_resolver": "default", "auto_detect_interface": true } diff --git a/protocol/vpn/client.go b/protocol/vpn/client.go index 15007081..bee1187e 100644 --- a/protocol/vpn/client.go +++ b/protocol/vpn/client.go @@ -29,13 +29,14 @@ func RegisterClientEndpoint(registry *endpoint.Registry) { type ClientEndpoint struct { outbound.Adapter - ctx context.Context - outbound adapter.Outbound - router adapter.ConnectionRouterEx - logger logger.ContextLogger - address IPv4 - key uuid.UUID - uotClient *uot.Client + ctx context.Context + outbound adapter.Outbound + router adapter.ConnectionRouterEx + logger logger.ContextLogger + address IPv4 + key uuid.UUID + defaultGateway IPv4 + uotClient *uot.Client } func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VPNClientEndpointOptions) (adapter.Endpoint, error) { @@ -47,13 +48,21 @@ func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.Co if err != nil { return nil, err } + defaultGateway := Loopback.As4() + if options.DefaultGateway.IsValid() { + if !options.DefaultGateway.Is4() { + return nil, E.New("invalid default_gateway: ", options.DefaultGateway) + } + defaultGateway = options.DefaultGateway.As4() + } client := &ClientEndpoint{ - Adapter: outbound.NewAdapter(C.TypeVPNClient, tag, []string{N.NetworkTCP, N.NetworkUDP}, []string{}), - ctx: ctx, - router: sbUot.NewRouter(router, logger), - logger: logger, - address: address.As4(), - key: key, + Adapter: outbound.NewAdapter(C.TypeVPNClient, tag, []string{N.NetworkTCP, N.NetworkUDP}, []string{}), + ctx: ctx, + router: sbUot.NewRouter(router, logger), + logger: logger, + address: address.As4(), + key: key, + defaultGateway: defaultGateway, } outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) outbound, err := outboundRegistry.CreateOutbound(ctx, router, logger, options.Outbound.Tag, options.Outbound.Type, options.Outbound.Options) @@ -102,7 +111,7 @@ func (c *ClientEndpoint) DialContext(ctx context.Context, network string, destin if err != nil { return nil, err } - gateway := Loopback.As4() + gateway := c.defaultGateway if metadata := adapter.ContextFrom(ctx); metadata != nil { if metadata.Gateway != nil { gateway = metadata.Gateway.As4() diff --git a/protocol/vpn/server.go b/protocol/vpn/server.go index 872dd83e..966ab471 100644 --- a/protocol/vpn/server.go +++ b/protocol/vpn/server.go @@ -29,15 +29,16 @@ func RegisterServerEndpoint(registry *endpoint.Registry) { type ServerEndpoint struct { outbound.Adapter - logger logger.ContextLogger - inbounds []adapter.Inbound - router adapter.ConnectionRouterEx - address IPv4 - addresses map[uuid.UUID]IPv4 - keys map[IPv4]uuid.UUID - conns map[IPv4]chan net.Conn - timeout time.Duration - uotClient *uot.Client + logger logger.ContextLogger + inbounds []adapter.Inbound + router adapter.ConnectionRouterEx + address IPv4 + addresses map[uuid.UUID]IPv4 + keys map[IPv4]uuid.UUID + conns map[IPv4]chan net.Conn + timeout time.Duration + defaultGateway *netip.Addr + uotClient *uot.Client } func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VPNServerEndpointOptions) (adapter.Endpoint, error) { @@ -51,6 +52,13 @@ func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.Co router: sbUot.NewRouter(router, logger), address: address.As4(), } + if options.DefaultGateway.IsValid() { + if !options.DefaultGateway.Is4() { + return nil, E.New("invalid default_gateway: ", options.DefaultGateway) + } + defaultGateway := options.DefaultGateway + server.defaultGateway = &defaultGateway + } router = NewRouter(router, logger, server.connHandler) inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) inbounds := make([]adapter.Inbound, len(options.Inbounds)) @@ -124,6 +132,8 @@ func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destin Addr: Loopback, Port: destination.Port, } + } else if s.defaultGateway != nil { + gateway = s.defaultGateway } else { return nil, E.New("missing gateway") }