Add netns support

This commit is contained in:
世界 2025-03-18 14:21:08 +08:00
parent 786d17cb6a
commit 7669763137
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
9 changed files with 204 additions and 43 deletions

View file

@ -5,6 +5,8 @@ import (
"errors"
"net"
"net/netip"
"runtime"
"strings"
"syscall"
"time"
@ -20,6 +22,8 @@ import (
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"github.com/vishvananda/netns"
)
var (
@ -35,6 +39,7 @@ type DefaultDialer struct {
udpListener net.ListenConfig
udpAddr4 string
udpAddr6 string
netns string
networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool
@ -198,6 +203,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpListener: listener,
udpAddr4: udpAddr4,
udpAddr6: udpAddr6,
netns: options.NetNs,
networkManager: networkManager,
networkStrategy: networkStrategy,
defaultNetworkStrategy: defaultNetworkStrategy,
@ -214,6 +220,29 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
return nil, E.New("domain not resolved")
}
if d.networkStrategy == nil {
if d.netns != "" {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
currentNs, err := netns.Get()
if err != nil {
return nil, E.Cause(err, "get current netns")
}
defer netns.Set(currentNs)
var targetNs netns.NsHandle
if strings.HasPrefix(d.netns, "/") {
targetNs, err = netns.GetFromPath(d.netns)
} else {
targetNs, err = netns.GetFromName(d.netns)
}
if err != nil {
return nil, E.Cause(err, "get netns ", d.netns)
}
defer targetNs.Close()
err = netns.Set(targetNs)
if err != nil {
return nil, E.Cause(err, "set netns to ", d.netns)
}
}
switch N.NetworkName(network) {
case N.NetworkUDP:
if !address.IsIPv6() {
@ -282,6 +311,29 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if d.networkStrategy == nil {
if d.netns != "" {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
currentNs, err := netns.Get()
if err != nil {
return nil, E.Cause(err, "get current netns")
}
defer netns.Set(currentNs)
var targetNs netns.NsHandle
if strings.HasPrefix(d.netns, "/") {
targetNs, err = netns.GetFromPath(d.netns)
} else {
targetNs, err = netns.GetFromName(d.netns)
}
if err != nil {
return nil, E.Cause(err, "get netns ", d.netns)
}
defer targetNs.Close()
err = netns.Set(targetNs)
if err != nil {
return nil, E.Cause(err, "set netns to ", d.netns)
}
}
if destination.IsIPv6() {
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {

View file

@ -3,6 +3,8 @@ package listener
import (
"net"
"net/netip"
"runtime"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
@ -13,6 +15,7 @@ import (
N "github.com/sagernet/sing/common/network"
"github.com/metacubex/tfo-go"
"github.com/vishvananda/netns"
)
func (l *Listener) ListenTCP() (net.Listener, error) {
@ -37,6 +40,29 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
}
setMultiPathTCP(&listenConfig)
}
if l.listenOptions.NetNs != "" {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
currentNs, err := netns.Get()
if err != nil {
return nil, E.Cause(err, "get current netns")
}
defer netns.Set(currentNs)
var targetNs netns.NsHandle
if strings.HasPrefix(l.listenOptions.NetNs, "/") {
targetNs, err = netns.GetFromPath(l.listenOptions.NetNs)
} else {
targetNs, err = netns.GetFromName(l.listenOptions.NetNs)
}
if err != nil {
return nil, E.Cause(err, "get netns ", l.listenOptions.NetNs)
}
defer targetNs.Close()
err = netns.Set(targetNs)
if err != nil {
return nil, E.Cause(err, "set netns to ", l.listenOptions.NetNs)
}
}
if l.listenOptions.TCPFastOpen {
var tfoConfig tfo.ListenConfig
tfoConfig.ListenConfig = listenConfig

View file

@ -4,12 +4,16 @@ import (
"net"
"net/netip"
"os"
"runtime"
"strings"
"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/vishvananda/netns"
)
func (l *Listener) ListenUDP() (net.PacketConn, error) {
@ -24,6 +28,29 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
if !udpFragment {
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
}
if l.listenOptions.NetNs != "" {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
currentNs, err := netns.Get()
if err != nil {
return nil, E.Cause(err, "get current netns")
}
defer netns.Set(currentNs)
var targetNs netns.NsHandle
if strings.HasPrefix(l.listenOptions.NetNs, "/") {
targetNs, err = netns.GetFromPath(l.listenOptions.NetNs)
} else {
targetNs, err = netns.GetFromName(l.listenOptions.NetNs)
}
if err != nil {
return nil, E.Cause(err, "get netns ", l.listenOptions.NetNs)
}
defer targetNs.Close()
err = netns.Set(targetNs)
if err != nil {
return nil, E.Cause(err, "set netns to ", l.listenOptions.NetNs)
}
}
udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
if err != nil {
return nil, err

View file

@ -5,7 +5,8 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [domain_resolver](#domain_resolver)
:material-delete-clock: [domain_strategy](#domain_strategy)
:material-delete-clock: [domain_strategy](#domain_strategy)
:material-plus: [netns](#netns)
!!! quote "Changes in sing-box 1.11.0"
@ -18,24 +19,25 @@ icon: material/new-box
```json
{
"detour": "upstream-out",
"bind_interface": "en0",
"inet4_bind_address": "0.0.0.0",
"inet6_bind_address": "::",
"routing_mark": 1234,
"detour": "",
"bind_interface": "",
"inet4_bind_address": "",
"inet6_bind_address": "",
"routing_mark": 0,
"reuse_addr": false,
"connect_timeout": "5s",
"connect_timeout": "",
"tcp_fast_open": false,
"tcp_multi_path": false,
"udp_fragment": false,
"netns": "",
"domain_resolver": "", // or {}
"network_strategy": "default",
"network_strategy": "",
"network_type": [],
"fallback_network_type": [],
"fallback_delay": "300ms",
"fallback_delay": "",
// Deprecated
"domain_strategy": "prefer_ipv6"
"domain_strategy": ""
}
```
@ -75,6 +77,15 @@ Set netfilter routing mark.
Reuse listener address.
#### connect_timeout
Connect timeout, in golang's Duration format.
A duration string is a possibly signed sequence of
decimal numbers, each with optional fraction and a unit suffix,
such as "300ms", "-1.5h" or "2h45m".
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
#### tcp_fast_open
Enable TCP Fast Open.
@ -91,14 +102,15 @@ Enable TCP Multi Path.
Enable UDP fragmentation.
#### connect_timeout
#### netns
Connect timeout, in golang's Duration format.
!!! question "Since sing-box 1.12.0"
A duration string is a possibly signed sequence of
decimal numbers, each with optional fraction and a unit suffix,
such as "300ms", "-1.5h" or "2h45m".
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
!!! quote ""
Only supported on Linux.
Set network namespace, name or path.
#### domain_resolver

View file

@ -5,7 +5,8 @@ icon: material/new-box
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [domain_resolver](#domain_resolver)
:material-delete-clock: [domain_strategy](#domain_strategy)
:material-delete-clock: [domain_strategy](#domain_strategy)
:material-plus: [netns](#netns)
!!! quote "sing-box 1.11.0 中的更改"
@ -18,25 +19,26 @@ icon: material/new-box
```json
{
"detour": "upstream-out",
"bind_interface": "en0",
"inet4_bind_address": "0.0.0.0",
"inet6_bind_address": "::",
"routing_mark": 1234,
"detour": "",
"bind_interface": "",
"inet4_bind_address": "",
"inet6_bind_address": "",
"routing_mark": 0,
"reuse_addr": false,
"connect_timeout": "5s",
"connect_timeout": "",
"tcp_fast_open": false,
"tcp_multi_path": false,
"udp_fragment": false,
"netns": "",
"domain_resolver": "", // 或 {}
"network_strategy": "",
"network_type": [],
"fallback_network_type": [],
"fallback_delay": "300ms",
"fallback_delay": "",
// 废弃的
"domain_strategy": "prefer_ipv6"
"domain_strategy": ""
}
```
@ -76,6 +78,13 @@ icon: material/new-box
重用监听地址。
#### connect_timeout
连接超时,采用 golang 的 Duration 格式。
持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。
有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。
#### tcp_fast_open
启用 TCP Fast Open。
@ -92,12 +101,15 @@ icon: material/new-box
启用 UDP 分段。
#### connect_timeout
#### netns
连接超时,采用 golang 的 Duration 格式。
!!! question "自 sing-box 1.12.0 起"
持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。
有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。
!!! quote ""
仅支持 Linux。
设置网络命名空间,名称或路径。
#### domain_resolver

View file

@ -1,7 +1,11 @@
---
icon: material/delete-clock
icon: material/new-box
---
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [netns](#netns)
!!! quote "Changes in sing-box 1.11.0"
:material-delete-clock: [sniff](#sniff)
@ -14,17 +18,18 @@ icon: material/delete-clock
```json
{
"listen": "::",
"listen_port": 5353,
"listen": "",
"listen_port": 0,
"tcp_fast_open": false,
"tcp_multi_path": false,
"udp_fragment": false,
"udp_timeout": "5m",
"detour": "another-in",
"udp_timeout": "",
"netns": "",
"detour": "",
"sniff": false,
"sniff_override_destination": false,
"sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6",
"sniff_timeout": "",
"domain_strategy": "",
"udp_disable_domain_unmapping": false
}
```
@ -72,6 +77,16 @@ 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.

View file

@ -1,7 +1,11 @@
---
icon: material/delete-clock
icon: material/new-box
---
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [netns](#netns)
!!! quote "sing-box 1.11.0 中的更改"
:material-delete-clock: [sniff](#sniff)
@ -14,17 +18,18 @@ icon: material/delete-clock
```json
{
"listen": "::",
"listen_port": 5353,
"listen": "",
"listen_port": 0,
"tcp_fast_open": false,
"tcp_multi_path": false,
"udp_fragment": false,
"udp_timeout": "5m",
"detour": "another-in",
"udp_timeout": "",
"netns": "",
"detour": "",
"sniff": false,
"sniff_override_destination": false,
"sniff_timeout": "300ms",
"domain_strategy": "prefer_ipv6",
"sniff_timeout": "",
"domain_strategy": "",
"udp_disable_domain_unmapping": false
}
```
@ -73,6 +78,16 @@ UDP NAT 过期时间。
默认使用 `5m`
#### netns
!!! question "自 sing-box 1.12.0 起"
!!! quote ""
仅支持 Linux。
设置网络命名空间,名称或路径。
#### detour
如果设置,连接将被转发到指定的入站。

View file

@ -68,6 +68,7 @@ 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"`

View file

@ -77,6 +77,7 @@ type DialerOptions struct {
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"`