From b893a27dfca5a3f27161ddd51ce97f63c1b269c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 5 May 2025 08:50:22 +0800 Subject: [PATCH] Add control options for listeners --- common/listener/listener_tcp.go | 11 +++++ common/listener/listener_udp.go | 35 ++++++++++++-- docs/configuration/shared/dial.md | 26 +++++----- docs/configuration/shared/dial.zh.md | 24 ++++----- docs/configuration/shared/listen.md | 66 +++++++++++++++++-------- docs/configuration/shared/listen.zh.md | 67 +++++++++++++++++--------- option/inbound.go | 5 +- option/outbound.go | 2 +- 8 files changed, 166 insertions(+), 70 deletions(-) diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go index c5995fad..b8ecc1db 100644 --- a/common/listener/listener_tcp.go +++ b/common/listener/listener_tcp.go @@ -8,9 +8,11 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" "github.com/metacubex/tfo-go" ) @@ -23,6 +25,15 @@ func (l *Listener) ListenTCP() (net.Listener, error) { var err error bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) var listenConfig net.ListenConfig + if l.listenOptions.BindInterface != "" { + listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) + } if l.listenOptions.TCPKeepAlive >= 0 { keepIdle := time.Duration(l.listenOptions.TCPKeepAlive) if keepIdle == 0 { diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go index eb5c7b50..d189bf13 100644 --- a/common/listener/listener_udp.go +++ b/common/listener/listener_udp.go @@ -6,16 +6,27 @@ import ( "net/netip" "os" + "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/service" ) func (l *Listener) ListenUDP() (net.PacketConn, error) { bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort) - var lc net.ListenConfig + var listenConfig net.ListenConfig + if l.listenOptions.BindInterface != "" { + listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) + } var udpFragment bool if l.listenOptions.UDPFragment != nil { udpFragment = *l.listenOptions.UDPFragment @@ -23,10 +34,10 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) { udpFragment = l.listenOptions.UDPFragmentDefault } if !udpFragment { - lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) + listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment()) } udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { - return lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) + return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) }) if err != nil { return nil, err @@ -39,12 +50,30 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) { func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) { return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) { + if l.listenOptions.BindInterface != "" { + dialer.Control = control.Append(dialer.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + dialer.Control = control.Append(dialer.Control, control.ReuseAddr()) + } return dialer.DialContext(ctx, network, address) }) } func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) { return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) { + if l.listenOptions.BindInterface != "" { + listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1)) + } + if l.listenOptions.RoutingMark != 0 { + listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark))) + } + if l.listenOptions.ReuseAddr { + listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr()) + } return listenConfig.ListenPacket(ctx, network, address) }) } diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index 97fbfce3..f48f355d 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -25,11 +25,12 @@ icon: material/new-box "inet6_bind_address": "", "routing_mark": 0, "reuse_addr": false, + "netns": "", "connect_timeout": "", "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, - "netns": "", + "domain_resolver": "", // or {} "network_strategy": "", "network_type": [], @@ -37,6 +38,7 @@ icon: material/new-box "fallback_delay": "", // Deprecated + "domain_strategy": "" } ``` @@ -73,10 +75,22 @@ The IPv6 address to bind to. Set netfilter routing mark. +Integers (e.g. `1234`) and string hexadecimals (e.g. `"0x1234"`) are supported. + #### reuse_addr Reuse listener address. +#### netns + +!!! question "Since sing-box 1.12.0" + +!!! quote "" + + Only supported on Linux. + +Set network namespace, name or path. + #### connect_timeout Connect timeout, in golang's Duration format. @@ -102,16 +116,6 @@ Enable TCP Multi Path. Enable UDP fragmentation. -#### netns - -!!! question "Since sing-box 1.12.0" - -!!! quote "" - - Only supported on Linux. - -Set network namespace, name or path. - #### domain_resolver !!! warning "" diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index 292cdc7c..babb43e9 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -25,11 +25,11 @@ icon: material/new-box "inet6_bind_address": "", "routing_mark": 0, "reuse_addr": false, + "netns": "", "connect_timeout": "", "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, - "netns": "", "domain_resolver": "", // 或 {} "network_strategy": "", "network_type": [], @@ -74,10 +74,22 @@ icon: material/new-box 设置 netfilter 路由标记。 +支持数字 (如 `1234`) 和十六进制字符串 (如 `"0x1234"`)。 + #### reuse_addr 重用监听地址。 +#### netns + +!!! question "自 sing-box 1.12.0 起" + +!!! quote "" + + 仅支持 Linux。 + +设置网络命名空间,名称或路径。 + #### connect_timeout 连接超时,采用 golang 的 Duration 格式。 @@ -101,16 +113,6 @@ icon: material/new-box 启用 UDP 分段。 -#### netns - -!!! question "自 sing-box 1.12.0 起" - -!!! quote "" - - 仅支持 Linux。 - -设置网络命名空间,名称或路径。 - #### domain_resolver !!! warning "" diff --git a/docs/configuration/shared/listen.md b/docs/configuration/shared/listen.md index ab6d07ce..4040e42f 100644 --- a/docs/configuration/shared/listen.md +++ b/docs/configuration/shared/listen.md @@ -4,7 +4,10 @@ icon: material/new-box !!! quote "Changes in sing-box 1.12.0" - :material-plus: [netns](#netns) + :material-plus: [netns](#netns) + :material-plus: [bind_interface](#bind_interface) + :material-plus: [routing_mark](#routing_mark) + :material-plus: [reuse_addr](#reuse_addr) !!! quote "Changes in sing-box 1.11.0" @@ -20,12 +23,18 @@ icon: material/new-box { "listen": "", "listen_port": 0, + "bind_interface": "", + "routing_mark": 0, + "reuse_addr": false, + "netns": "", "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, "udp_timeout": "", - "netns": "", "detour": "", + + // Deprecated + "sniff": false, "sniff_override_destination": false, "sniff_timeout": "", @@ -36,15 +45,6 @@ icon: material/new-box ### Fields -| Field | Available Context | -|--------------------------------|---------------------------------------------------------| -| `listen` | Needs to listen on TCP or UDP. | -| `listen_port` | Needs to listen on TCP or UDP. | -| `tcp_fast_open` | Needs to listen on TCP. | -| `tcp_multi_path` | Needs to listen on TCP. | -| `udp_timeout` | Needs to assemble UDP connections. | -| `udp_disable_domain_unmapping` | Needs to listen on UDP and accept domain UDP addresses. | - #### listen ==Required== @@ -55,6 +55,40 @@ Listen address. Listen port. +#### bind_interface + +!!! question "Since sing-box 1.12.0" + +The network interface to bind to. + +#### routing_mark + +!!! question "Since sing-box 1.12.0" + +!!! quote "" + + Only supported on Linux. + +Set netfilter routing mark. + +Integers (e.g. `1234`) and string hexadecimals (e.g. `"0x1234"`) are supported. + +#### reuse_addr + +!!! question "Since sing-box 1.12.0" + +Reuse listener address. + +#### netns + +!!! question "Since sing-box 1.12.0" + +!!! quote "" + + Only supported on Linux. + +Set network namespace, name or path. + #### tcp_fast_open Enable TCP Fast Open. @@ -77,16 +111,6 @@ UDP NAT expiration time. `5m` will be used by default. -#### netns - -!!! question "Since sing-box 1.12.0" - -!!! quote "" - - Only supported on Linux. - -Set network namespace, name or path. - #### detour If set, connections will be forwarded to the specified inbound. diff --git a/docs/configuration/shared/listen.zh.md b/docs/configuration/shared/listen.zh.md index bc67ac98..cd12036c 100644 --- a/docs/configuration/shared/listen.zh.md +++ b/docs/configuration/shared/listen.zh.md @@ -4,7 +4,10 @@ icon: material/new-box !!! quote "Changes in sing-box 1.12.0" - :material-plus: [netns](#netns) + :material-plus: [netns](#netns) + :material-plus: [bind_interface](#bind_interface) + :material-plus: [routing_mark](#routing_mark) + :material-plus: [reuse_addr](#reuse_addr) !!! quote "sing-box 1.11.0 中的更改" @@ -20,12 +23,18 @@ icon: material/new-box { "listen": "", "listen_port": 0, + "bind_interface": "", + "routing_mark": 0, + "reuse_addr": false, + "netns": "", "tcp_fast_open": false, "tcp_multi_path": false, "udp_fragment": false, "udp_timeout": "", - "netns": "", "detour": "", + + // 废弃的 + "sniff": false, "sniff_override_destination": false, "sniff_timeout": "", @@ -34,16 +43,6 @@ icon: material/new-box } ``` - -| 字段 | 可用上下文 | -|------------------|-----------------| -| `listen` | 需要监听 TCP 或 UDP。 | -| `listen_port` | 需要监听 TCP 或 UDP。 | -| `tcp_fast_open` | 需要监听 TCP。 | -| `tcp_multi_path` | 需要监听 TCP。 | -| `udp_timeout` | 需要组装 UDP 连接。 | -| - ### 字段 #### listen @@ -56,6 +55,40 @@ icon: material/new-box 监听端口。 +#### bind_interface + +!!! question "自 sing-box 1.12.0 起" + +要绑定到的网络接口。 + +#### routing_mark + +!!! question "自 sing-box 1.12.0 起" + +!!! quote "" + + 仅支持 Linux。 + +设置 netfilter 路由标记。 + +支持数字 (如 `1234`) 和十六进制字符串 (如 `"0x1234"`)。 + +#### reuse_addr + +!!! question "自 sing-box 1.12.0 起" + +重用监听地址。 + +#### netns + +!!! question "自 sing-box 1.12.0 起" + +!!! quote "" + + 仅支持 Linux。 + +设置网络命名空间,名称或路径。 + #### tcp_fast_open 启用 TCP Fast Open。 @@ -78,16 +111,6 @@ UDP NAT 过期时间。 默认使用 `5m`。 -#### netns - -!!! question "自 sing-box 1.12.0 起" - -!!! quote "" - - 仅支持 Linux。 - -设置网络命名空间,名称或路径。 - #### detour 如果设置,连接将被转发到指定的入站。 diff --git a/option/inbound.go b/option/inbound.go index b704c7e3..99c5bc91 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -61,6 +61,10 @@ type InboundOptions struct { type ListenOptions struct { Listen *badoption.Addr `json:"listen,omitempty"` ListenPort uint16 `json:"listen_port,omitempty"` + BindInterface string `json:"bind_interface,omitempty"` + RoutingMark FwMark `json:"routing_mark,omitempty"` + ReuseAddr bool `json:"reuse_addr,omitempty"` + NetNs string `json:"netns,omitempty"` TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"` TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"` TCPFastOpen bool `json:"tcp_fast_open,omitempty"` @@ -68,7 +72,6 @@ type ListenOptions struct { UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragmentDefault bool `json:"-"` UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` - NetNs string `json:"netns,omitempty"` // Deprecated: removed ProxyProtocol bool `json:"proxy_protocol,omitempty"` diff --git a/option/outbound.go b/option/outbound.go index 9b04de93..2520d000 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -72,12 +72,12 @@ type DialerOptions struct { ProtectPath string `json:"protect_path,omitempty"` RoutingMark FwMark `json:"routing_mark,omitempty"` ReuseAddr bool `json:"reuse_addr,omitempty"` + NetNs string `json:"netns,omitempty"` ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` TCPFastOpen bool `json:"tcp_fast_open,omitempty"` TCPMultiPath bool `json:"tcp_multi_path,omitempty"` UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragmentDefault bool `json:"-"` - NetNs string `json:"netns,omitempty"` DomainResolver *DomainResolveOptions `json:"domain_resolver,omitempty"` NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"` NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`