Add L3 forwarding support
This commit is contained in:
+3
-3
@@ -3,7 +3,6 @@ package adapter
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@@ -27,9 +26,10 @@ type OutboundWithPreferredRoutes interface {
|
||||
PreferredAddress(address netip.Addr) bool
|
||||
}
|
||||
|
||||
type DirectRouteOutbound interface {
|
||||
type FlowOutbound interface {
|
||||
Outbound
|
||||
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
||||
tun.Port
|
||||
SupportsFlow(network string) bool
|
||||
}
|
||||
|
||||
type OutboundRegistry interface {
|
||||
|
||||
+70
-2
@@ -3,9 +3,11 @@ package adapter
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
@@ -15,7 +17,7 @@ import (
|
||||
type Router interface {
|
||||
Lifecycle
|
||||
ConnectionRouter
|
||||
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error)
|
||||
PreMatch(metadata InboundContext) PreMatchResult
|
||||
ConnectionRouterEx
|
||||
RuleSet(tag string) (RuleSet, bool)
|
||||
Rules() []Rule
|
||||
@@ -26,6 +28,72 @@ type Router interface {
|
||||
ResetNetwork()
|
||||
}
|
||||
|
||||
type PreMatchAction uint8
|
||||
|
||||
const (
|
||||
PreMatchContinue PreMatchAction = iota
|
||||
PreMatchFlow
|
||||
PreMatchReject
|
||||
PreMatchDrop
|
||||
PreMatchBypass
|
||||
)
|
||||
|
||||
type PreMatchResult struct {
|
||||
Action PreMatchAction
|
||||
Outbound Outbound
|
||||
Destination netip.AddrPort
|
||||
}
|
||||
|
||||
func JudgeFlow(router Router, inbound string, inboundType string, network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
|
||||
var networkName string
|
||||
switch network {
|
||||
case uint8(header.TCPProtocolNumber):
|
||||
networkName = N.NetworkTCP
|
||||
case uint8(header.UDPProtocolNumber):
|
||||
networkName = N.NetworkUDP
|
||||
case uint8(header.ICMPv4ProtocolNumber), uint8(header.ICMPv6ProtocolNumber):
|
||||
networkName = N.NetworkICMP
|
||||
default:
|
||||
return tun.FlowVerdict{Action: tun.ActionAccept}
|
||||
}
|
||||
metadata := InboundContext{
|
||||
Inbound: inbound,
|
||||
InboundType: inboundType,
|
||||
Network: networkName,
|
||||
Source: M.SocksaddrFromNetIP(source),
|
||||
Destination: M.SocksaddrFromNetIP(destination),
|
||||
}
|
||||
if networkName == N.NetworkICMP {
|
||||
metadata.Source.Port = 0
|
||||
metadata.Destination.Port = 0
|
||||
}
|
||||
result := router.PreMatch(metadata)
|
||||
switch result.Action {
|
||||
case PreMatchFlow:
|
||||
port, isPort := result.Outbound.(tun.Port)
|
||||
if !isPort {
|
||||
return tun.FlowVerdict{Action: tun.ActionAccept}
|
||||
}
|
||||
verdict := tun.FlowVerdict{Action: tun.ActionFlow, Port: port}
|
||||
if result.Destination.IsValid() {
|
||||
destinationPort := result.Destination.Port()
|
||||
if networkName == N.NetworkICMP {
|
||||
destinationPort = destination.Port()
|
||||
}
|
||||
verdict.Destination = netip.AddrPortFrom(result.Destination.Addr(), destinationPort)
|
||||
}
|
||||
return verdict
|
||||
case PreMatchReject:
|
||||
return tun.FlowVerdict{Action: tun.ActionReject}
|
||||
case PreMatchDrop:
|
||||
return tun.FlowVerdict{Action: tun.ActionDrop}
|
||||
case PreMatchBypass:
|
||||
return tun.FlowVerdict{Action: tun.ActionBypass}
|
||||
default:
|
||||
return tun.FlowVerdict{Action: tun.ActionAccept}
|
||||
}
|
||||
}
|
||||
|
||||
type ConnectionTracker interface {
|
||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn
|
||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn
|
||||
|
||||
+4
-3
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
R "github.com/sagernet/sing-box/route/rule"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
@@ -500,7 +499,7 @@ func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule,
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return exchangeWithRulesResult{
|
||||
rejectAction: action,
|
||||
err: tun.ErrDrop,
|
||||
err: R.ErrDrop,
|
||||
}
|
||||
}
|
||||
case *R.RuleActionPredefined:
|
||||
@@ -691,7 +690,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
Question: []mDNS.Question{message.Question[0]},
|
||||
}, nil
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return nil, tun.ErrDrop
|
||||
return nil, R.ErrDrop
|
||||
}
|
||||
case *R.RuleActionPredefined:
|
||||
err = nil
|
||||
@@ -767,6 +766,8 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
r.logger.DebugContext(ctx, "response rejected for ", domain)
|
||||
} else if R.IsRejected(err) {
|
||||
r.logger.DebugContext(ctx, "lookup rejected for ", domain)
|
||||
} else if errors.Is(err, ErrNotCached) {
|
||||
r.logger.DebugContext(ctx, "cache-only lookup missed for ", domain)
|
||||
} else {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ icon: material/new-box
|
||||
|
||||
# Pre-match
|
||||
|
||||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
:material-alert: [route](#route)
|
||||
|
||||
!!! quote "Changes in sing-box 1.13.0"
|
||||
|
||||
:material-plus: [bypass](#bypass)
|
||||
@@ -12,7 +16,7 @@ Pre-match is rule matching that runs before the connection is established.
|
||||
|
||||
### How it works
|
||||
|
||||
When TUN receives a connection request, the connection has not yet been established,
|
||||
When an L3 inbound (TUN, WireGuard, or Tailscale) receives a connection request, the connection has not yet been established,
|
||||
so no connection data can be read. In this phase, sing-box runs the routing rules in pre-match mode.
|
||||
|
||||
Since connection data is unavailable, only actions that do not require connection data can be executed.
|
||||
@@ -28,7 +32,24 @@ See [reject](/configuration/route/rule_action/#reject) for details.
|
||||
|
||||
#### route
|
||||
|
||||
Route ICMP connections to the specified outbound for direct reply.
|
||||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
Since sing-box 1.14.0, TCP and UDP connections can also be forwarded at L3;
|
||||
previously only ICMP connections were supported.
|
||||
|
||||
Forward connections directly at L3 to the specified outbound,
|
||||
without going through L3 to L4 translation.
|
||||
|
||||
Supported targets:
|
||||
|
||||
- ICMP connections: Direct outbounds and WireGuard / Tailscale endpoints.
|
||||
- TCP and UDP connections: WireGuard and Tailscale endpoints.
|
||||
|
||||
L3 forwarding also applies when no rule matches and the default outbound is a supported
|
||||
target; for outbound groups, the currently selected outbound is used.
|
||||
|
||||
FakeIP destinations require a `resolve` action performed in pre-match,
|
||||
otherwise connections will be rejected.
|
||||
|
||||
See [route](/configuration/route/rule_action/#route) for details.
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ icon: material/new-box
|
||||
|
||||
# 预匹配
|
||||
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-alert: [route](#route)
|
||||
|
||||
!!! quote "sing-box 1.13.0 中的更改"
|
||||
|
||||
:material-plus: [bypass](#bypass)
|
||||
@@ -12,7 +16,7 @@ icon: material/new-box
|
||||
|
||||
### 工作原理
|
||||
|
||||
当 TUN 收到连接请求时,连接尚未建立,因此无法读取连接数据。在此阶段,sing-box 在预匹配模式下运行路由规则。
|
||||
当 L3 入站(TUN、WireGuard 或 Tailscale)收到连接请求时,连接尚未建立,因此无法读取连接数据。在此阶段,sing-box 在预匹配模式下运行路由规则。
|
||||
|
||||
由于连接数据不可用,只有不需要连接数据的动作才能执行。当规则匹配到需要已建立连接的动作时,预匹配将在该规则处停止。
|
||||
|
||||
@@ -26,7 +30,20 @@ icon: material/new-box
|
||||
|
||||
#### route
|
||||
|
||||
将 ICMP 连接路由到指定出站以直接回复。
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
自 sing-box 1.14.0 起,TCP 和 UDP 连接也可以在 L3 转发;此前仅支持 ICMP 连接。
|
||||
|
||||
将连接直接在 L3 转发到指定出站,不经过 L3 到 L4 转换。
|
||||
|
||||
支持的目标:
|
||||
|
||||
- ICMP 连接:direct 出站和 WireGuard / Tailscale 端点。
|
||||
- TCP 和 UDP 连接:WireGuard 和 Tailscale 端点。
|
||||
|
||||
当没有规则匹配且默认出站为受支持的目标时,L3 转发同样生效;对于出站组,使用当前选中的出站。
|
||||
|
||||
FakeIP 目标需要在预匹配中先执行 `resolve` 动作,否则连接将被拒绝。
|
||||
|
||||
详情参阅 [route](/zh/configuration/route/rule_action/#route)。
|
||||
|
||||
|
||||
@@ -42,19 +42,19 @@ require (
|
||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
|
||||
github.com/sagernet/sing v0.8.12-0.20260702081104-2ded2af32d3d
|
||||
github.com/sagernet/sing-cloudflared v0.1.2
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3
|
||||
github.com/sagernet/sing-mux v0.3.5
|
||||
github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.2.1
|
||||
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814
|
||||
github.com/sagernet/sing-tun v0.8.12-0.20260629021427-b3c6babbd353
|
||||
github.com/sagernet/sing-tun v0.8.12-0.20260706130635-4b50856586ba
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260527101438-dc40932c32d9
|
||||
github.com/sagernet/wireguard-go v0.0.3
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260706062137-ae2dde1295a3
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260706130655-57baac9504a8
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
|
||||
@@ -260,8 +260,8 @@ github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgj
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
||||
github.com/sagernet/sing v0.8.12-0.20260702081104-2ded2af32d3d h1:BhsQU0Iug1tU4xR52cjm8Sc+LBo+KwdyLTRn3ie9moo=
|
||||
github.com/sagernet/sing v0.8.12-0.20260702081104-2ded2af32d3d/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA=
|
||||
github.com/sagernet/sing-cloudflared v0.1.2 h1:rEz98+q2nvbNlQXaDXJQlRKngK81FHK+rm7pNCSLXB4=
|
||||
github.com/sagernet/sing-cloudflared v0.1.2/go.mod h1:bH2NKX+NpDTY1Zkxfboxw6MXB/ZywaNLmrDJYgKMJ2Y=
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 h1:3y6++yIa8XlDhxPkpR4p+7RUHVY2KTP9CPIGnWmOlO8=
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg=
|
||||
github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI=
|
||||
github.com/sagernet/sing-mux v0.3.5/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
||||
github.com/sagernet/sing-quic v0.6.2-0.20260525051024-9467ede27fb7 h1:hFLPJ21uNZSbRnzhOKz4Zv0b4F93mpDorWyN93BeRcM=
|
||||
@@ -274,18 +274,18 @@ github.com/sagernet/sing-shadowtls v0.2.1 h1:ZiHZdnEnP+YS73NMsxiZmIFCwNd0M4k7PkG
|
||||
github.com/sagernet/sing-shadowtls v0.2.1/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814 h1:xfnkRpjVRVeJhVvDZA8PzTLlKGTb1o2kdI4uv1YymXo=
|
||||
github.com/sagernet/sing-snell v0.0.0-20260705044717-4e9e73be7814/go.mod h1:PcwzX/Xvqky0EP3kGt8OCjYb3R1pydenPHNQZcPZmXY=
|
||||
github.com/sagernet/sing-tun v0.8.12-0.20260629021427-b3c6babbd353 h1:HA0TGrBQSFfvcoVXL1DxzF+i8pmaGOGb33jdReB7L4s=
|
||||
github.com/sagernet/sing-tun v0.8.12-0.20260629021427-b3c6babbd353/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ=
|
||||
github.com/sagernet/sing-tun v0.8.12-0.20260706130635-4b50856586ba h1:/+QtKNDDJ6MEAIbmRWvnQSHNJHxXbtYTRX12xRY26Cg=
|
||||
github.com/sagernet/sing-tun v0.8.12-0.20260706130635-4b50856586ba/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ=
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb h1:KEMbfexD4DvrQGYWwx6r+AwH9Veh8z6cnBZmtCS2G+0=
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb/go.mod h1:D4CnJX3MNAAANhbQUxfIRgBdnvlTEaV7h6ojedcs+pw=
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260527101438-dc40932c32d9 h1:jOkKeYI0A0M+jVEu2omQLId4q5GVP7G8FSZh1eUArIk=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260527101438-dc40932c32d9/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc=
|
||||
github.com/sagernet/wireguard-go v0.0.3 h1:6ebmwj/SFQRnYv6/nRCnwUzf+KFepF8tIBd57IAq1jE=
|
||||
github.com/sagernet/wireguard-go v0.0.3/go.mod h1:hEqi4y5czEg6LYtX2Bpjg+lV0b/J1n+5rA885Z66Mx0=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260706062137-ae2dde1295a3 h1:eczvica8YiS5j3GfpHg6JG1Icur4Z2D6ffSrLZTfD1E=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260706062137-ae2dde1295a3/go.mod h1:p8Ms8FbGlwQJyHb862XmdShTS50fFJ8C71VdO6xvWyk=
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260706130655-57baac9504a8 h1:gfukXANr9v5kcrKGeoCV5c+IdessM9NRGzWE7Aot9sw=
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260706130655-57baac9504a8/go.mod h1:hEqi4y5czEg6LYtX2Bpjg+lV0b/J1n+5rA885Z66Mx0=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
|
||||
@@ -5,6 +5,7 @@ package cloudflare
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/route/rule"
|
||||
"github.com/sagernet/sing-cloudflared"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
@@ -137,32 +137,44 @@ type icmpRouterHandler struct {
|
||||
tag string
|
||||
}
|
||||
|
||||
func (h *icmpRouterHandler) RouteICMPConnection(ctx context.Context, session tun.DirectRouteSession, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
var ipVersion uint8
|
||||
if session.Destination.Is4() {
|
||||
ipVersion = 4
|
||||
} else {
|
||||
ipVersion = 6
|
||||
}
|
||||
destination := M.SocksaddrFrom(session.Destination, 0)
|
||||
routeDestination, err := h.router.PreMatch(adapter.InboundContext{
|
||||
Inbound: h.tag,
|
||||
InboundType: C.TypeCloudflared,
|
||||
IPVersion: ipVersion,
|
||||
Network: N.NetworkICMP,
|
||||
Source: M.SocksaddrFrom(session.Source, 0),
|
||||
Destination: destination,
|
||||
OriginDestination: destination,
|
||||
}, routeContext, timeout, false)
|
||||
if err != nil {
|
||||
switch {
|
||||
case rule.IsBypassed(err):
|
||||
err = nil
|
||||
case rule.IsRejected(err):
|
||||
h.logger.Trace("reject ICMP connection from ", session.Source, " to ", session.Destination)
|
||||
default:
|
||||
h.logger.Warn(E.Cause(err, "link ICMP connection from ", session.Source, " to ", session.Destination))
|
||||
func (h *icmpRouterHandler) RouteICMPFlow(source netip.Addr, destination netip.Addr) (tun.Port, error) {
|
||||
result := h.router.PreMatch(adapter.InboundContext{
|
||||
Inbound: h.tag,
|
||||
InboundType: C.TypeCloudflared,
|
||||
Network: N.NetworkICMP,
|
||||
Source: M.SocksaddrFrom(source, 0),
|
||||
Destination: M.SocksaddrFrom(destination, 0),
|
||||
})
|
||||
switch result.Action {
|
||||
case adapter.PreMatchFlow:
|
||||
flowOutbound, isFlowOutbound := result.Outbound.(adapter.FlowOutbound)
|
||||
if !isFlowOutbound {
|
||||
return nil, E.New("outbound is not a flow outbound")
|
||||
}
|
||||
if result.Destination.IsValid() && result.Destination.Addr() != destination {
|
||||
h.logger.Trace("drop ICMP flow from ", source, " to ", destination, ": destination override is not supported from cloudflared")
|
||||
return nil, E.New("destination override is not supported")
|
||||
}
|
||||
inet4Address, inet6Address := flowOutbound.PortAddresses()
|
||||
var portAddress netip.Addr
|
||||
if destination.Is4() {
|
||||
portAddress = inet4Address
|
||||
} else {
|
||||
portAddress = inet6Address
|
||||
}
|
||||
if !portAddress.IsValid() || !portAddress.IsUnspecified() {
|
||||
h.logger.Trace("drop ICMP flow from ", source, " to ", destination, ": forwarding ICMP to outbound/", result.Outbound.Type(), "[", result.Outbound.Tag(), "] is not supported from cloudflared")
|
||||
return nil, E.New("unsupported flow outbound")
|
||||
}
|
||||
h.logger.Debug("link ICMP flow from ", source, " to ", destination, " via outbound/", result.Outbound.Type(), "[", result.Outbound.Tag(), "]")
|
||||
return flowOutbound, nil
|
||||
case adapter.PreMatchReject:
|
||||
h.logger.Trace("reject ICMP flow from ", source, " to ", destination)
|
||||
return nil, E.New("rejected")
|
||||
case adapter.PreMatchDrop:
|
||||
return nil, E.New("dropped")
|
||||
default:
|
||||
h.logger.Trace("drop ICMP flow from ", source, " to ", destination, ": no direct route")
|
||||
return nil, E.New("no direct route")
|
||||
}
|
||||
return routeDestination, err
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/ping"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -31,7 +32,7 @@ var (
|
||||
_ N.ParallelDialer = (*Outbound)(nil)
|
||||
_ dialer.ParallelNetworkDialer = (*Outbound)(nil)
|
||||
_ dialer.DirectDialer = (*Outbound)(nil)
|
||||
_ adapter.DirectRouteOutbound = (*Outbound)(nil)
|
||||
_ adapter.FlowOutbound = (*Outbound)(nil)
|
||||
)
|
||||
|
||||
type Outbound struct {
|
||||
@@ -44,6 +45,7 @@ type Outbound struct {
|
||||
fallbackDelay time.Duration
|
||||
isEmpty bool
|
||||
myAddresses common.TypedValue[[]netip.Prefix]
|
||||
icmpPort *ping.Port
|
||||
}
|
||||
|
||||
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) {
|
||||
@@ -75,6 +77,11 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
if options.ProxyProtocol != 0 {
|
||||
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
|
||||
}
|
||||
if defaultDialer, isDefaultDialer := common.Cast[*dialer.DefaultDialer](outbound.dialer); isDefaultDialer {
|
||||
outbound.icmpPort = ping.NewPort(ctx, logger, func(destination netip.Addr) control.Func {
|
||||
return defaultDialer.DialerForICMPDestination(destination).Control
|
||||
}, 0)
|
||||
}
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
@@ -148,14 +155,35 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (h *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
ctx := log.ContextWithNewID(h.ctx)
|
||||
destination, err := ping.ConnectDestination(ctx, h.logger, common.MustCast[*dialer.DefaultDialer](h.dialer).DialerForICMPDestination(metadata.Destination.Addr).Control, metadata.Destination.Addr, routeContext, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (h *Outbound) SupportsFlow(network string) bool {
|
||||
return network == N.NetworkICMP && h.icmpPort != nil
|
||||
}
|
||||
|
||||
func (h *Outbound) PortAddresses() (netip.Addr, netip.Addr) {
|
||||
return h.icmpPort.PortAddresses()
|
||||
}
|
||||
|
||||
func (h *Outbound) PortMTU() uint32 {
|
||||
return h.icmpPort.PortMTU()
|
||||
}
|
||||
|
||||
func (h *Outbound) AttachReturn(returnPath tun.Return) error {
|
||||
return h.icmpPort.AttachReturn(returnPath)
|
||||
}
|
||||
|
||||
func (h *Outbound) DetachReturn(returnPath tun.Return) error {
|
||||
return h.icmpPort.DetachReturn(returnPath)
|
||||
}
|
||||
|
||||
func (h *Outbound) WritePackets(packets [][]byte) error {
|
||||
return h.icmpPort.WritePackets(packets)
|
||||
}
|
||||
|
||||
func (h *Outbound) Close() error {
|
||||
if h.icmpPort != nil {
|
||||
return h.icmpPort.Close()
|
||||
}
|
||||
h.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||
return destination, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package group
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
@@ -12,7 +11,6 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
@@ -182,14 +180,6 @@ func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Selector) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
selected := s.selected.Load()
|
||||
if !common.Contains(selected.Network(), metadata.Network) {
|
||||
return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag())
|
||||
}
|
||||
return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||
}
|
||||
|
||||
func RealTag(detour adapter.Outbound) string {
|
||||
if group, isGroup := detour.(adapter.OutboundGroup); isGroup {
|
||||
return group.Now()
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -169,21 +168,6 @@ func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, me
|
||||
s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (s *URLTest) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
s.group.Touch()
|
||||
selected := s.group.selectedOutboundTCP
|
||||
if selected == nil {
|
||||
selected, _ = s.group.Select(N.NetworkTCP)
|
||||
}
|
||||
if selected == nil {
|
||||
return nil, E.New("missing supported outbound")
|
||||
}
|
||||
if !common.Contains(selected.Network(), metadata.Network) {
|
||||
return nil, E.New(metadata.Network, " is not supported by outbound: ", selected.Tag())
|
||||
}
|
||||
return selected.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||
}
|
||||
|
||||
type URLTestGroup struct {
|
||||
ctx context.Context
|
||||
outbound adapter.OutboundManager
|
||||
|
||||
+14
-104
@@ -15,6 +15,7 @@ import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -34,7 +35,6 @@ import (
|
||||
"github.com/sagernet/sing-box/protocol/tailscale/tailssh"
|
||||
R "github.com/sagernet/sing-box/route/rule"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/ping"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -56,7 +56,6 @@ import (
|
||||
tsTUN "github.com/sagernet/tailscale/net/tstun"
|
||||
"github.com/sagernet/tailscale/tailcfg"
|
||||
"github.com/sagernet/tailscale/tsnet"
|
||||
"github.com/sagernet/tailscale/types/ipproto"
|
||||
"github.com/sagernet/tailscale/types/nettype"
|
||||
"github.com/sagernet/tailscale/version"
|
||||
"github.com/sagernet/tailscale/wgengine"
|
||||
@@ -70,8 +69,8 @@ import (
|
||||
|
||||
var (
|
||||
_ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
|
||||
_ adapter.DirectRouteOutbound = (*Endpoint)(nil)
|
||||
_ dialer.PacketDialerWithDestination = (*Endpoint)(nil)
|
||||
_ tun.Port = (*Endpoint)(nil)
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -95,6 +94,9 @@ type Endpoint struct {
|
||||
stack *stack.Stack
|
||||
icmpForwarder *tun.ICMPForwarder
|
||||
filter *atomic.Pointer[filter.Filter]
|
||||
returnAccess sync.Mutex
|
||||
returnPath tun.Return
|
||||
wgEngine wgengine.ExportedUserspaceEngine
|
||||
onReconfigHook wgengine.ReconfigListener
|
||||
sshReconfigHook wgengine.ReconfigListener
|
||||
|
||||
@@ -287,6 +289,7 @@ func (t *Endpoint) start() error {
|
||||
if mtu == 0 {
|
||||
mtu = uint32(tsTUN.DefaultTUNMTU())
|
||||
}
|
||||
t.systemInterfaceMTU = mtu
|
||||
tunName := t.systemInterfaceName
|
||||
if tunName == "" {
|
||||
tunName = tun.CalculateInterfaceName("tailscale")
|
||||
@@ -361,7 +364,9 @@ func (t *Endpoint) postStart() error {
|
||||
}, true
|
||||
})
|
||||
}
|
||||
t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)
|
||||
wgEngine := t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine)
|
||||
wgEngine.SetOnReconfigListener(t.onReconfig)
|
||||
t.wgEngine = wgEngine
|
||||
|
||||
ipStack := t.server.ExportNetstack().ExportIPStack()
|
||||
gErr := ipStack.SetSpoofing(tun.DefaultNIC, true)
|
||||
@@ -372,7 +377,7 @@ func (t *Endpoint) postStart() error {
|
||||
if gErr != nil {
|
||||
return gonet.TranslateNetstackError(gErr)
|
||||
}
|
||||
icmpForwarder := tun.NewICMPForwarder(t.ctx, ipStack, t.logger, t, t.icmpTimeout)
|
||||
icmpForwarder := tun.NewICMPForwarder(ipStack, t, t.logger)
|
||||
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
|
||||
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
|
||||
t.stack = ipStack
|
||||
@@ -622,6 +627,10 @@ func (t *Endpoint) Logout(ctx context.Context) error {
|
||||
func (t *Endpoint) Close() error {
|
||||
var err error
|
||||
t.started.Store(false)
|
||||
if t.icmpForwarder != nil {
|
||||
t.icmpForwarder.Close()
|
||||
t.icmpForwarder = nil
|
||||
}
|
||||
common.Close(common.PtrOrNil(t.sshServerInstance))
|
||||
t.sshServerInstance = nil
|
||||
if t.serverStarted {
|
||||
@@ -776,62 +785,6 @@ func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
||||
return packetConn, nil
|
||||
}
|
||||
|
||||
func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
if !t.started.Load() {
|
||||
return nil, E.New("Tailscale is not ready yet")
|
||||
}
|
||||
tsFilter := t.filter.Load()
|
||||
if tsFilter != nil {
|
||||
var ipProto ipproto.Proto
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
ipProto = ipproto.TCP
|
||||
case N.NetworkUDP:
|
||||
ipProto = ipproto.UDP
|
||||
case N.NetworkICMP:
|
||||
if !destination.IsIPv6() {
|
||||
ipProto = ipproto.ICMPv4
|
||||
} else {
|
||||
ipProto = ipproto.ICMPv6
|
||||
}
|
||||
}
|
||||
response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto)
|
||||
switch response {
|
||||
case filter.Drop:
|
||||
return nil, syscall.ECONNREFUSED
|
||||
case filter.DropSilently:
|
||||
return nil, tun.ErrDrop
|
||||
}
|
||||
}
|
||||
var ipVersion uint8
|
||||
if !destination.IsIPv6() {
|
||||
ipVersion = 4
|
||||
} else {
|
||||
ipVersion = 6
|
||||
}
|
||||
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
|
||||
Inbound: t.Tag(),
|
||||
InboundType: t.Type(),
|
||||
IPVersion: ipVersion,
|
||||
Network: network,
|
||||
Source: source,
|
||||
Destination: destination,
|
||||
}, routeContext, timeout, false)
|
||||
if err != nil {
|
||||
switch {
|
||||
case R.IsBypassed(err):
|
||||
err = nil
|
||||
case R.IsRejected(err):
|
||||
t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
|
||||
default:
|
||||
if network == N.NetworkICMP {
|
||||
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
return routeDestination, err
|
||||
}
|
||||
|
||||
func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Inbound = t.Tag()
|
||||
@@ -872,40 +825,6 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
||||
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (t *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
if !t.started.Load() {
|
||||
return nil, E.New("Tailscale is not ready yet")
|
||||
}
|
||||
ctx := log.ContextWithNewID(t.ctx)
|
||||
var destination tun.DirectRouteDestination
|
||||
var err error
|
||||
if t.systemDialer != nil {
|
||||
destination, err = ping.ConnectDestination(
|
||||
ctx, t.logger,
|
||||
t.systemDialer.DialerForICMPDestination(metadata.Destination.Addr).Control,
|
||||
metadata.Destination.Addr, routeContext, timeout,
|
||||
)
|
||||
} else {
|
||||
inet4Address, inet6Address := t.server.TailscaleIPs()
|
||||
if metadata.Destination.Addr.Is4() && !inet4Address.IsValid() || metadata.Destination.Addr.Is6() && !inet6Address.IsValid() {
|
||||
return nil, E.New("Tailscale is not ready yet")
|
||||
}
|
||||
destination, err = ping.ConnectGVisor(
|
||||
ctx, t.logger,
|
||||
metadata.Source.Addr, metadata.Destination.Addr,
|
||||
routeContext,
|
||||
t.stack,
|
||||
inet4Address, inet6Address,
|
||||
timeout,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||
return destination, nil
|
||||
}
|
||||
|
||||
func (t *Endpoint) PreferredDomain(domain string) bool {
|
||||
routeDomains := t.routeDomains.Load()
|
||||
if routeDomains == nil {
|
||||
@@ -933,15 +852,6 @@ func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCf
|
||||
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
|
||||
return
|
||||
}
|
||||
var inet4Address, inet6Address netip.Addr
|
||||
for _, address := range cfg.Addresses {
|
||||
if address.Addr().Is4() {
|
||||
inet4Address = address.Addr()
|
||||
} else if address.Addr().Is6() {
|
||||
inet6Address = address.Addr()
|
||||
}
|
||||
}
|
||||
t.icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
|
||||
t.cfg = cfg
|
||||
t.dnsCfg = dnsCfg
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
//go:build with_gvisor
|
||||
|
||||
package tailscale
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
tsTUN "github.com/sagernet/tailscale/net/tstun"
|
||||
"github.com/sagernet/tailscale/types/ipproto"
|
||||
"github.com/sagernet/tailscale/wgengine/filter"
|
||||
)
|
||||
|
||||
func (t *Endpoint) SupportsFlow(network string) bool {
|
||||
switch network {
|
||||
case N.NetworkTCP, N.NetworkUDP, N.NetworkICMP:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Endpoint) PortAddresses() (netip.Addr, netip.Addr) {
|
||||
if !t.started.Load() {
|
||||
return netip.Addr{}, netip.Addr{}
|
||||
}
|
||||
return t.server.TailscaleIPs()
|
||||
}
|
||||
|
||||
func (t *Endpoint) PortMTU() uint32 {
|
||||
if t.systemInterface {
|
||||
return t.systemInterfaceMTU
|
||||
}
|
||||
return uint32(tsTUN.DefaultTUNMTU())
|
||||
}
|
||||
|
||||
func (t *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
|
||||
inet4Address, inet6Address := t.PortAddresses()
|
||||
if destination.Addr() == inet4Address || destination.Addr() == inet6Address {
|
||||
return tun.FlowVerdict{Action: tun.ActionAccept}
|
||||
}
|
||||
if t.filter != nil {
|
||||
tsFilter := t.filter.Load()
|
||||
if tsFilter != nil {
|
||||
var (
|
||||
ipProto ipproto.Proto
|
||||
destinationPort uint16
|
||||
)
|
||||
switch network {
|
||||
case uint8(header.TCPProtocolNumber):
|
||||
ipProto = ipproto.TCP
|
||||
destinationPort = destination.Port()
|
||||
case uint8(header.UDPProtocolNumber):
|
||||
ipProto = ipproto.UDP
|
||||
destinationPort = destination.Port()
|
||||
case uint8(header.ICMPv4ProtocolNumber):
|
||||
ipProto = ipproto.ICMPv4
|
||||
case uint8(header.ICMPv6ProtocolNumber):
|
||||
ipProto = ipproto.ICMPv6
|
||||
}
|
||||
switch tsFilter.Check(source.Addr(), destination.Addr(), destinationPort, ipProto) {
|
||||
case filter.Drop:
|
||||
return tun.FlowVerdict{Action: tun.ActionReject}
|
||||
case filter.DropSilently:
|
||||
return tun.FlowVerdict{Action: tun.ActionDrop}
|
||||
}
|
||||
}
|
||||
}
|
||||
return adapter.JudgeFlow(t.router, t.Tag(), t.Type(), network, source, destination)
|
||||
}
|
||||
|
||||
func (t *Endpoint) AttachReturn(returnPath tun.Return) error {
|
||||
t.returnAccess.Lock()
|
||||
defer t.returnAccess.Unlock()
|
||||
if t.returnPath == returnPath {
|
||||
return nil
|
||||
}
|
||||
if t.returnPath != nil {
|
||||
return E.New("return path already attached")
|
||||
}
|
||||
err := t.wgEngine.SetReturnPath(returnPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.returnPath = returnPath
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Endpoint) DetachReturn(returnPath tun.Return) error {
|
||||
t.returnAccess.Lock()
|
||||
defer t.returnAccess.Unlock()
|
||||
if t.returnPath == returnPath {
|
||||
t.returnPath = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Endpoint) WritePackets(packets [][]byte) error {
|
||||
if !t.started.Load() {
|
||||
return E.New("Tailscale is not ready yet")
|
||||
}
|
||||
unmatched, err := t.wgEngine.InputPackets(packets)
|
||||
if err != nil || len(unmatched) == 0 {
|
||||
return err
|
||||
}
|
||||
t.returnAccess.Lock()
|
||||
returnPath := t.returnPath
|
||||
t.returnAccess.Unlock()
|
||||
if returnPath == nil {
|
||||
return nil
|
||||
}
|
||||
headroom := returnPath.ReturnHeadroom()
|
||||
inet4Address, inet6Address := t.PortAddresses()
|
||||
var replies [][]byte
|
||||
for _, packet := range unmatched {
|
||||
source := inet4Address
|
||||
if header.IPVersion(packet) == header.IPv6Version {
|
||||
source = inet6Address
|
||||
}
|
||||
reply, replyOk := tun.BuildUnreachable(packet, source, headroom)
|
||||
if replyOk {
|
||||
replies = append(replies, reply)
|
||||
}
|
||||
}
|
||||
if len(replies) > 0 {
|
||||
returnPath.ReturnPackets(replies)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+25
-59
@@ -16,7 +16,6 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/route/rule"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -99,7 +98,6 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
|
||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||
tunMTU := options.MTU
|
||||
enableGSO := C.IsLinux && options.Stack == "gvisor" && platformInterface == nil && tunMTU > 0 && tunMTU < 49152
|
||||
if tunMTU == 0 {
|
||||
if platformInterface != nil && platformInterface.UnderNetworkExtension() {
|
||||
// In Network Extension, when MTU exceeds 4064 (4096-UTUN_IF_HEADROOM_SIZE), the performance of tun will drop significantly, which may be a system bug.
|
||||
@@ -111,6 +109,10 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
tunMTU = 65535
|
||||
}
|
||||
}
|
||||
var enableGSO bool
|
||||
if C.IsLinux && platformInterface == nil {
|
||||
enableGSO = (options.Stack == "gvisor" && tunMTU < 49152)
|
||||
}
|
||||
var udpTimeout time.Duration
|
||||
if options.UDPTimeout != 0 {
|
||||
udpTimeout = time.Duration(options.UDPTimeout)
|
||||
@@ -178,7 +180,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
excludeMACAddress = append(excludeMACAddress, mac)
|
||||
}
|
||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && options.MTU <= 9000))
|
||||
multiPendingPackets := C.IsDarwin && ((options.Stack == "gvisor" && tunMTU < 32768) || (options.Stack != "gvisor" && tunMTU <= 9000))
|
||||
inbound := &Inbound{
|
||||
tag: tag,
|
||||
ctx: ctx,
|
||||
@@ -320,6 +322,22 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
|
||||
t.dnsHijackAddress = append(inet4DNSAddress, inet6DNSAddress...)
|
||||
}
|
||||
case adapter.StartStateStart:
|
||||
if t.platformInterface == nil &&
|
||||
((C.IsLinux && !t.tunOptions.GSO) || (C.IsDarwin && !t.tunOptions.EXP_MultiPendingPackets)) {
|
||||
endpointManager := service.FromContext[adapter.EndpointManager](t.ctx)
|
||||
if endpointManager != nil {
|
||||
for _, managedEndpoint := range endpointManager.Endpoints() {
|
||||
if _, isFlowOutbound := managedEndpoint.(adapter.FlowOutbound); isFlowOutbound {
|
||||
if C.IsLinux {
|
||||
t.tunOptions.GSO = true
|
||||
} else {
|
||||
t.tunOptions.EXP_MultiPendingPackets = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if C.IsAndroid && t.platformInterface == nil {
|
||||
t.tunOptions.BuildAndroidRules(t.networkManager.PackageManager())
|
||||
}
|
||||
@@ -460,34 +478,8 @@ func (t *Inbound) Close() error {
|
||||
)
|
||||
}
|
||||
|
||||
func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
var ipVersion uint8
|
||||
if !destination.IsIPv6() {
|
||||
ipVersion = 4
|
||||
} else {
|
||||
ipVersion = 6
|
||||
}
|
||||
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
|
||||
Inbound: t.tag,
|
||||
InboundType: C.TypeTun,
|
||||
IPVersion: ipVersion,
|
||||
Network: network,
|
||||
Source: source,
|
||||
Destination: destination,
|
||||
}, routeContext, timeout, false)
|
||||
if err != nil {
|
||||
switch {
|
||||
case rule.IsBypassed(err):
|
||||
err = nil
|
||||
case rule.IsRejected(err):
|
||||
t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
|
||||
default:
|
||||
if network == N.NetworkICMP {
|
||||
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
return routeDestination, err
|
||||
func (t *Inbound) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
|
||||
return adapter.JudgeFlow(t.router, t.tag, C.TypeTun, network, source, destination)
|
||||
}
|
||||
|
||||
func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
@@ -534,34 +526,8 @@ func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
||||
|
||||
type autoRedirectHandler Inbound
|
||||
|
||||
func (t *autoRedirectHandler) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
var ipVersion uint8
|
||||
if !destination.IsIPv6() {
|
||||
ipVersion = 4
|
||||
} else {
|
||||
ipVersion = 6
|
||||
}
|
||||
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
|
||||
Inbound: t.tag,
|
||||
InboundType: C.TypeTun,
|
||||
IPVersion: ipVersion,
|
||||
Network: network,
|
||||
Source: source,
|
||||
Destination: destination,
|
||||
}, routeContext, timeout, true)
|
||||
if err != nil {
|
||||
switch {
|
||||
case rule.IsBypassed(err):
|
||||
t.logger.Trace("bypass ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
|
||||
case rule.IsRejected(err):
|
||||
t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
|
||||
default:
|
||||
if network == N.NetworkICMP {
|
||||
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
return routeDestination, err
|
||||
func (t *autoRedirectHandler) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
|
||||
return (*Inbound)(t).JudgeFlow(network, source, destination)
|
||||
}
|
||||
|
||||
func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/route/rule"
|
||||
"github.com/sagernet/sing-box/transport/wireguard"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
@@ -137,37 +136,45 @@ func (w *Endpoint) Close() error {
|
||||
return w.endpoint.Close()
|
||||
}
|
||||
|
||||
func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
if !w.started.Load() {
|
||||
return nil, E.New("WireGuard is not ready yet")
|
||||
func (w *Endpoint) SupportsFlow(network string) bool {
|
||||
switch network {
|
||||
case N.NetworkTCP, N.NetworkUDP, N.NetworkICMP:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
var ipVersion uint8
|
||||
if !destination.IsIPv6() {
|
||||
ipVersion = 4
|
||||
} else {
|
||||
ipVersion = 6
|
||||
}
|
||||
routeDestination, err := w.router.PreMatch(adapter.InboundContext{
|
||||
Inbound: w.Tag(),
|
||||
InboundType: w.Type(),
|
||||
IPVersion: ipVersion,
|
||||
Network: network,
|
||||
Source: source,
|
||||
Destination: destination,
|
||||
}, routeContext, timeout, false)
|
||||
if err != nil {
|
||||
switch {
|
||||
case rule.IsBypassed(err):
|
||||
err = nil
|
||||
case rule.IsRejected(err):
|
||||
w.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString())
|
||||
default:
|
||||
if network == N.NetworkICMP {
|
||||
w.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Endpoint) PortAddresses() (netip.Addr, netip.Addr) {
|
||||
return w.endpoint.PortAddresses()
|
||||
}
|
||||
|
||||
func (w *Endpoint) PortMTU() uint32 {
|
||||
return w.endpoint.PortMTU()
|
||||
}
|
||||
|
||||
func (w *Endpoint) AttachReturn(returnPath tun.Return) error {
|
||||
return w.endpoint.AttachReturn(returnPath)
|
||||
}
|
||||
|
||||
func (w *Endpoint) DetachReturn(returnPath tun.Return) error {
|
||||
return w.endpoint.DetachReturn(returnPath)
|
||||
}
|
||||
|
||||
func (w *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort) tun.FlowVerdict {
|
||||
for _, localPrefix := range w.localAddresses {
|
||||
if localPrefix.Contains(destination.Addr()) {
|
||||
return tun.FlowVerdict{Action: tun.ActionAccept}
|
||||
}
|
||||
}
|
||||
return routeDestination, err
|
||||
return adapter.JudgeFlow(w.router, w.Tag(), w.Type(), network, source, destination)
|
||||
}
|
||||
|
||||
func (w *Endpoint) WritePackets(packets [][]byte) error {
|
||||
if !w.started.Load() {
|
||||
return E.New("WireGuard is not ready yet")
|
||||
}
|
||||
return w.endpoint.WritePackets(packets)
|
||||
}
|
||||
|
||||
func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
@@ -279,10 +286,3 @@ func (w *Endpoint) PreferredAddress(address netip.Addr) bool {
|
||||
}
|
||||
return w.endpoint.Lookup(address) != nil
|
||||
}
|
||||
|
||||
func (w *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
if !w.started.Load() {
|
||||
return nil, E.New("WireGuard is not ready yet")
|
||||
}
|
||||
return w.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||
}
|
||||
|
||||
+146
-138
@@ -9,12 +9,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/sniff"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
R "github.com/sagernet/sing-box/route/rule"
|
||||
"github.com/sagernet/sing-mux"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/ping"
|
||||
"github.com/sagernet/sing-vmess"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
@@ -95,7 +94,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
if deadline.NeedAdditionalReadDeadline(conn) {
|
||||
conn = deadline.NewConn(conn)
|
||||
}
|
||||
selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, false, false, conn, nil)
|
||||
selectedRule, _, buffers, _, err := r.matchRule(ctx, &metadata, conn, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -226,7 +225,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
if metadata.InboundType == C.TypeTun && metadata.Protocol == C.ProtocolDNS {
|
||||
return r.hijackDNSPacket(ctx, conn, nil, metadata, onClose)
|
||||
}
|
||||
selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, false, false, nil, conn)
|
||||
selectedRule, _, _, packetBuffers, err := r.matchRule(ctx, &metadata, nil, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -295,119 +294,157 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error) {
|
||||
selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, supportBypass, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (r *Router) PreMatch(metadata adapter.InboundContext) adapter.PreMatchResult {
|
||||
continueResult := adapter.PreMatchResult{Action: adapter.PreMatchContinue}
|
||||
packetDestination := metadata.Destination
|
||||
if metadata.Destination.Addr.IsValid() && r.dnsTransport.FakeIP() != nil && r.dnsTransport.FakeIP().Store().Contains(metadata.Destination.Addr) {
|
||||
domain, loaded := r.dnsTransport.FakeIP().Store().Lookup(metadata.Destination.Addr)
|
||||
if !loaded || domain == "" {
|
||||
return continueResult
|
||||
}
|
||||
metadata.OriginDestination = metadata.Destination
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Fqdn: domain,
|
||||
Port: metadata.Destination.Port,
|
||||
}
|
||||
metadata.FakeIP = true
|
||||
}
|
||||
var directRouteOutbound adapter.DirectRouteOutbound
|
||||
if selectedRule != nil {
|
||||
switch action := selectedRule.Action().(type) {
|
||||
case *R.RuleActionReject:
|
||||
switch metadata.Network {
|
||||
case N.NetworkTCP:
|
||||
if action.Method == C.RuleActionRejectMethodReply {
|
||||
return nil, E.New("reject method `reply` is not supported for TCP connections")
|
||||
}
|
||||
case N.NetworkUDP:
|
||||
if action.Method == C.RuleActionRejectMethodReply {
|
||||
return nil, E.New("reject method `reply` is not supported for UDP connections")
|
||||
}
|
||||
if metadata.Destination.IsIPv4() {
|
||||
metadata.IPVersion = 4
|
||||
} else if metadata.Destination.IsIPv6() {
|
||||
metadata.IPVersion = 6
|
||||
}
|
||||
for currentRuleIndex, currentRule := range r.rules {
|
||||
metadata.ResetRuleCache()
|
||||
if !currentRule.Match(&metadata) {
|
||||
continue
|
||||
}
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *R.RuleActionSniff:
|
||||
if metadata.Network == N.NetworkICMP {
|
||||
continue
|
||||
}
|
||||
return nil, action.Error(context.Background())
|
||||
case *R.RuleActionBypass:
|
||||
if supportBypass {
|
||||
return nil, &R.BypassedError{Cause: tun.ErrBypass}
|
||||
}
|
||||
if routeContext == nil {
|
||||
return nil, nil
|
||||
}
|
||||
outbound, loaded := r.outbound.Outbound(action.Outbound)
|
||||
if !loaded {
|
||||
return nil, E.New("outbound not found: ", action.Outbound)
|
||||
}
|
||||
if !common.Contains(outbound.Network(), metadata.Network) {
|
||||
return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound)
|
||||
}
|
||||
directRouteOutbound = outbound.(adapter.DirectRouteOutbound)
|
||||
return continueResult
|
||||
case *R.RuleActionRouteOptions:
|
||||
applyRouteOptionsOverride(&metadata, action)
|
||||
case *R.RuleActionRoute:
|
||||
if routeContext == nil {
|
||||
return nil, nil
|
||||
applyRouteOptionsOverride(&metadata, &action.RuleActionRouteOptions)
|
||||
r.logger.Debug("pre-match[", currentRuleIndex, "] ", currentRule, " => ", action)
|
||||
return r.preMatchFlow(&metadata, packetDestination, action.Outbound)
|
||||
case *R.RuleActionBypass:
|
||||
applyRouteOptionsOverride(&metadata, &action.RuleActionRouteOptions)
|
||||
r.logger.Debug("pre-match[", currentRuleIndex, "] ", currentRule, " => ", action)
|
||||
if action.Outbound == "" {
|
||||
if metadata.Destination.IsDomain() || metadata.Destination != packetDestination {
|
||||
return continueResult
|
||||
}
|
||||
return adapter.PreMatchResult{Action: adapter.PreMatchBypass}
|
||||
}
|
||||
outbound, loaded := r.outbound.Outbound(action.Outbound)
|
||||
if !loaded {
|
||||
return nil, E.New("outbound not found: ", action.Outbound)
|
||||
return r.preMatchFlow(&metadata, packetDestination, action.Outbound)
|
||||
case *R.RuleActionReject:
|
||||
r.logger.Debug("pre-match[", currentRuleIndex, "] ", currentRule, " => ", action)
|
||||
rejectErr := action.Error(r.ctx)
|
||||
if errors.Is(rejectErr, R.ErrDrop) {
|
||||
return adapter.PreMatchResult{Action: adapter.PreMatchDrop}
|
||||
}
|
||||
if !common.Contains(outbound.Network(), metadata.Network) {
|
||||
return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound)
|
||||
return adapter.PreMatchResult{Action: adapter.PreMatchReject}
|
||||
case *R.RuleActionResolve:
|
||||
resolveErr := r.actionResolve(adapter.WithContext(r.ctx, &metadata), &metadata, action)
|
||||
if resolveErr != nil {
|
||||
r.logger.Debug("pre-match[", currentRuleIndex, "] ", currentRule, " => ", action, ": ", resolveErr)
|
||||
return adapter.PreMatchResult{Action: adapter.PreMatchReject}
|
||||
}
|
||||
directRouteOutbound = outbound.(adapter.DirectRouteOutbound)
|
||||
default:
|
||||
return continueResult
|
||||
}
|
||||
}
|
||||
if directRouteOutbound == nil {
|
||||
if selectedRule != nil || metadata.Network != N.NetworkICMP {
|
||||
return nil, nil
|
||||
return r.preMatchFlow(&metadata, packetDestination, "")
|
||||
}
|
||||
|
||||
func applyRouteOptionsOverride(metadata *adapter.InboundContext, routeOptions *R.RuleActionRouteOptions) {
|
||||
if routeOptions.OverrideAddress.IsValid() {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Addr: routeOptions.OverrideAddress.Addr,
|
||||
Port: metadata.Destination.Port,
|
||||
Fqdn: routeOptions.OverrideAddress.Fqdn,
|
||||
}
|
||||
defaultOutbound := r.outbound.Default()
|
||||
if !common.Contains(defaultOutbound.Network(), metadata.Network) {
|
||||
return nil, E.New(metadata.Network, " is not supported by default outbound: ", defaultOutbound.Tag())
|
||||
}
|
||||
directRouteOutbound = defaultOutbound.(adapter.DirectRouteOutbound)
|
||||
}
|
||||
if routeOptions.OverridePort > 0 {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Addr: metadata.Destination.Addr,
|
||||
Port: routeOptions.OverridePort,
|
||||
Fqdn: metadata.Destination.Fqdn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) preMatchFlow(metadata *adapter.InboundContext, packetDestination M.Socksaddr, outboundTag string) adapter.PreMatchResult {
|
||||
continueResult := adapter.PreMatchResult{Action: adapter.PreMatchContinue}
|
||||
var outbound adapter.Outbound
|
||||
if outboundTag == "" {
|
||||
outbound = r.outbound.Default()
|
||||
} else {
|
||||
var loaded bool
|
||||
outbound, loaded = r.outbound.Outbound(outboundTag)
|
||||
if !loaded {
|
||||
return continueResult
|
||||
}
|
||||
}
|
||||
for range 8 {
|
||||
group, isGroup := outbound.(adapter.OutboundGroup)
|
||||
if !isGroup {
|
||||
break
|
||||
}
|
||||
selectedOutbound, selectedLoaded := r.outbound.Outbound(group.Now())
|
||||
if !selectedLoaded {
|
||||
return continueResult
|
||||
}
|
||||
outbound = selectedOutbound
|
||||
}
|
||||
if !common.Contains(outbound.Network(), metadata.Network) {
|
||||
return continueResult
|
||||
}
|
||||
flowOutbound, isFlowOutbound := outbound.(adapter.FlowOutbound)
|
||||
if !isFlowOutbound || !flowOutbound.SupportsFlow(metadata.Network) {
|
||||
if outbound.Type() == C.TypeDirect {
|
||||
directDialer, isDirectDialer := outbound.(dialer.DirectDialer)
|
||||
if isDirectDialer && directDialer.IsEmpty() && !metadata.Destination.IsDomain() && metadata.Destination == packetDestination {
|
||||
r.logger.Debug("pre-match bypass ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||
return adapter.PreMatchResult{Action: adapter.PreMatchBypass, Outbound: outbound}
|
||||
}
|
||||
}
|
||||
return continueResult
|
||||
}
|
||||
result := adapter.PreMatchResult{Action: adapter.PreMatchFlow, Outbound: outbound}
|
||||
if metadata.Destination.IsDomain() {
|
||||
if len(metadata.DestinationAddresses) == 0 {
|
||||
var strategy C.DomainStrategy
|
||||
if metadata.Source.IsIPv4() {
|
||||
strategy = C.DomainStrategyIPv4Only
|
||||
} else {
|
||||
strategy = C.DomainStrategyIPv6Only
|
||||
}
|
||||
err = r.actionResolve(r.ctx, &metadata, &R.RuleActionResolve{
|
||||
Strategy: strategy,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !metadata.FakeIP {
|
||||
return continueResult
|
||||
}
|
||||
var newDestination netip.Addr
|
||||
if metadata.Source.IsIPv4() {
|
||||
for _, address := range metadata.DestinationAddresses {
|
||||
if address.Is4() {
|
||||
newDestination = address
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, address := range metadata.DestinationAddresses {
|
||||
if address.Is6() {
|
||||
newDestination = address
|
||||
break
|
||||
}
|
||||
for _, address := range metadata.DestinationAddresses {
|
||||
if address.Is4() == packetDestination.IsIPv4() {
|
||||
newDestination = address
|
||||
break
|
||||
}
|
||||
}
|
||||
if !newDestination.IsValid() {
|
||||
if metadata.Source.IsIPv4() {
|
||||
return nil, E.New("no IPv4 address found for domain: ", metadata.Destination.Fqdn)
|
||||
if len(metadata.DestinationAddresses) == 0 {
|
||||
r.logger.Warn("pre-match: reject ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to fake destination ", metadata.Destination.Fqdn, ": a resolve action is required before routing to outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
} else {
|
||||
return nil, E.New("no IPv6 address found for domain: ", metadata.Destination.Fqdn)
|
||||
r.logger.Debug("pre-match: reject ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to fake destination ", metadata.Destination.Fqdn, ": no resolved address for this address family")
|
||||
}
|
||||
return adapter.PreMatchResult{Action: adapter.PreMatchReject}
|
||||
}
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Addr: newDestination,
|
||||
}
|
||||
routeContext = ping.NewContextDestinationWriter(routeContext, metadata.OriginDestination.Addr)
|
||||
var routeDestination tun.DirectRouteDestination
|
||||
routeDestination, err = directRouteOutbound.NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ping.NewDestinationWriter(routeDestination, newDestination), nil
|
||||
result.Destination = netip.AddrPortFrom(newDestination, metadata.Destination.Port)
|
||||
} else if metadata.Destination != packetDestination {
|
||||
result.Destination = metadata.Destination.AddrPort()
|
||||
}
|
||||
return directRouteOutbound.NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||
r.logger.Debug("pre-match forward ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString(), " via outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *Router) matchRule(
|
||||
ctx context.Context, metadata *adapter.InboundContext, preMatch bool, supportBypass bool,
|
||||
ctx context.Context, metadata *adapter.InboundContext,
|
||||
inputConn net.Conn, inputPacketConn N.PacketConn,
|
||||
) (
|
||||
selectedRule adapter.Rule, selectedRuleIndex int,
|
||||
@@ -465,23 +502,11 @@ match:
|
||||
if !currentRule.Match(metadata) {
|
||||
continue
|
||||
}
|
||||
if !preMatch {
|
||||
ruleDescription := currentRule.String()
|
||||
if ruleDescription != "" {
|
||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||
} else {
|
||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action())
|
||||
}
|
||||
ruleDescription := currentRule.String()
|
||||
if ruleDescription != "" {
|
||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||
} else {
|
||||
switch currentRule.Action().Type() {
|
||||
case C.RuleActionTypeReject:
|
||||
ruleDescription := currentRule.String()
|
||||
if ruleDescription != "" {
|
||||
r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||
} else {
|
||||
r.logger.DebugContext(ctx, "pre-match[", currentRuleIndex, "] => ", currentRule.Action())
|
||||
}
|
||||
}
|
||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action())
|
||||
}
|
||||
var routeOptions *R.RuleActionRouteOptions
|
||||
switch action := currentRule.Action().(type) {
|
||||
@@ -500,20 +525,9 @@ match:
|
||||
metadata.RouteOriginalDestination = metadata.Destination
|
||||
}
|
||||
if routeOptions.OverrideAddress.IsValid() {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Addr: routeOptions.OverrideAddress.Addr,
|
||||
Port: metadata.Destination.Port,
|
||||
Fqdn: routeOptions.OverrideAddress.Fqdn,
|
||||
}
|
||||
metadata.DestinationAddresses = nil
|
||||
}
|
||||
if routeOptions.OverridePort > 0 {
|
||||
metadata.Destination = M.Socksaddr{
|
||||
Addr: metadata.Destination.Addr,
|
||||
Port: routeOptions.OverridePort,
|
||||
Fqdn: metadata.Destination.Fqdn,
|
||||
}
|
||||
}
|
||||
applyRouteOptionsOverride(metadata, routeOptions)
|
||||
if routeOptions.NetworkStrategy != nil {
|
||||
metadata.NetworkStrategy = routeOptions.NetworkStrategy
|
||||
}
|
||||
@@ -549,21 +563,15 @@ match:
|
||||
}
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *R.RuleActionSniff:
|
||||
if !preMatch {
|
||||
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers, packetBuffers)
|
||||
if newBuffer != nil {
|
||||
buffers = append(buffers, newBuffer)
|
||||
} else if len(newPacketBuffers) > 0 {
|
||||
packetBuffers = append(packetBuffers, newPacketBuffers...)
|
||||
}
|
||||
if newErr != nil {
|
||||
fatalErr = newErr
|
||||
return
|
||||
}
|
||||
} else if metadata.Network != N.NetworkICMP {
|
||||
selectedRule = currentRule
|
||||
selectedRuleIndex = currentRuleIndex
|
||||
break match
|
||||
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers, packetBuffers)
|
||||
if newBuffer != nil {
|
||||
buffers = append(buffers, newBuffer)
|
||||
} else if len(newPacketBuffers) > 0 {
|
||||
packetBuffers = append(packetBuffers, newPacketBuffers...)
|
||||
}
|
||||
if newErr != nil {
|
||||
fatalErr = newErr
|
||||
return
|
||||
}
|
||||
case *R.RuleActionResolve:
|
||||
fatalErr = r.actionResolve(ctx, metadata, action)
|
||||
@@ -581,7 +589,7 @@ match:
|
||||
}
|
||||
if actionType == C.RuleActionTypeBypass {
|
||||
bypassAction := currentRule.Action().(*R.RuleActionBypass)
|
||||
if !supportBypass && bypassAction.Outbound == "" {
|
||||
if bypassAction.Outbound == "" {
|
||||
continue match
|
||||
}
|
||||
selectedRule = currentRule
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/sagernet/sing-box/common/tlsspoof"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
@@ -388,6 +387,11 @@ func (r *RuleActionDirect) String() string {
|
||||
return "direct" + r.description
|
||||
}
|
||||
|
||||
var (
|
||||
ErrReset = E.New("connection reset")
|
||||
ErrDrop = E.New("packet dropped")
|
||||
)
|
||||
|
||||
type RejectedError struct {
|
||||
Cause error
|
||||
}
|
||||
@@ -445,9 +449,9 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
|
||||
var returnErr error
|
||||
switch r.Method {
|
||||
case C.RuleActionRejectMethodDefault:
|
||||
returnErr = &RejectedError{tun.ErrReset}
|
||||
returnErr = &RejectedError{ErrReset}
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return &RejectedError{tun.ErrDrop}
|
||||
return &RejectedError{ErrDrop}
|
||||
case C.RuleActionRejectMethodReply:
|
||||
return nil
|
||||
default:
|
||||
@@ -467,7 +471,7 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
|
||||
if ctx != nil {
|
||||
r.logger.DebugContext(ctx, "dropped due to flooding")
|
||||
}
|
||||
return &RejectedError{tun.ErrDrop}
|
||||
return &RejectedError{ErrDrop}
|
||||
}
|
||||
return returnErr
|
||||
}
|
||||
|
||||
@@ -5,10 +5,8 @@ import (
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-tun"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
@@ -22,8 +20,8 @@ type ruleSetItemTestRouter struct {
|
||||
|
||||
func (r *ruleSetItemTestRouter) Start(adapter.StartStage) error { return nil }
|
||||
func (r *ruleSetItemTestRouter) Close() error { return nil }
|
||||
func (r *ruleSetItemTestRouter) PreMatch(adapter.InboundContext, tun.DirectRouteContext, time.Duration, bool) (tun.DirectRouteDestination, error) {
|
||||
return nil, nil
|
||||
func (r *ruleSetItemTestRouter) PreMatch(adapter.InboundContext) adapter.PreMatchResult {
|
||||
return adapter.PreMatchResult{}
|
||||
}
|
||||
|
||||
func (r *ruleSetItemTestRouter) RouteConnection(context.Context, net.Conn, adapter.InboundContext) error {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -45,8 +44,3 @@ func NewDevice(options DeviceOptions) (Device, error) {
|
||||
return newSystemStackDevice(options)
|
||||
}
|
||||
}
|
||||
|
||||
type NatDevice interface {
|
||||
Device
|
||||
CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
||||
}
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/ping"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
var _ Device = (*natDeviceWrapper)(nil)
|
||||
|
||||
type natDeviceWrapper struct {
|
||||
Device
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
packetOutbound chan *buf.Buffer
|
||||
rewriter *ping.SourceRewriter
|
||||
buffer [][]byte
|
||||
}
|
||||
|
||||
func NewNATDevice(ctx context.Context, logger logger.ContextLogger, upstream Device) NatDevice {
|
||||
wrapper := &natDeviceWrapper{
|
||||
Device: upstream,
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
packetOutbound: make(chan *buf.Buffer, 256),
|
||||
rewriter: ping.NewSourceRewriter(ctx, logger, upstream.Inet4Address(), upstream.Inet6Address()),
|
||||
}
|
||||
return wrapper
|
||||
}
|
||||
|
||||
func (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
|
||||
select {
|
||||
case packet := <-d.packetOutbound:
|
||||
defer packet.Release()
|
||||
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
|
||||
return 1, nil
|
||||
default:
|
||||
}
|
||||
return d.Device.Read(bufs, sizes, offset)
|
||||
}
|
||||
|
||||
func (d *natDeviceWrapper) Write(bufs [][]byte, offset int) (int, error) {
|
||||
for _, buffer := range bufs {
|
||||
handled, err := d.rewriter.WriteBack(buffer[offset:])
|
||||
if handled {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
} else {
|
||||
d.buffer = append(d.buffer, buffer)
|
||||
}
|
||||
}
|
||||
if len(d.buffer) > 0 {
|
||||
_, err := d.Device.Write(d.buffer, offset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d.buffer = d.buffer[:0]
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *natDeviceWrapper) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
ctx := log.ContextWithNewID(d.ctx)
|
||||
session := tun.DirectRouteSession{
|
||||
Source: metadata.Source.Addr,
|
||||
Destination: metadata.Destination.Addr,
|
||||
}
|
||||
d.rewriter.CreateSession(session, routeContext)
|
||||
d.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||
return &natDestination{device: d, session: session}, nil
|
||||
}
|
||||
|
||||
var _ tun.DirectRouteDestination = (*natDestination)(nil)
|
||||
|
||||
type natDestination struct {
|
||||
device *natDeviceWrapper
|
||||
session tun.DirectRouteSession
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
func (d *natDestination) WritePacket(buffer *buf.Buffer) error {
|
||||
d.device.rewriter.RewritePacket(buffer.Bytes())
|
||||
d.device.packetOutbound <- buffer
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *natDestination) Close() error {
|
||||
d.closed.Store(true)
|
||||
d.device.rewriter.DeleteSession(d.session)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *natDestination) IsClosed() bool {
|
||||
return d.closed.Load()
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
@@ -20,10 +19,7 @@ import (
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/ping"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
@@ -32,11 +28,9 @@ import (
|
||||
wgTun "github.com/sagernet/wireguard-go/tun"
|
||||
)
|
||||
|
||||
var _ NatDevice = (*stackDevice)(nil)
|
||||
var _ Device = (*stackDevice)(nil)
|
||||
|
||||
type stackDevice struct {
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
stack *stack.Stack
|
||||
mtu uint32
|
||||
events chan wgTun.Event
|
||||
@@ -47,12 +41,11 @@ type stackDevice struct {
|
||||
dispatcher stack.NetworkDispatcher
|
||||
inet4Address netip.Addr
|
||||
inet6Address netip.Addr
|
||||
icmpForwarder *tun.ICMPForwarder
|
||||
}
|
||||
|
||||
func newStackDevice(options DeviceOptions) (*stackDevice, error) {
|
||||
tunDevice := &stackDevice{
|
||||
ctx: options.Context,
|
||||
logger: options.Logger,
|
||||
mtu: options.MTU,
|
||||
events: make(chan wgTun.Event, 1),
|
||||
outbound: make(chan *stack.PacketBuffer, 256),
|
||||
@@ -63,10 +56,6 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
inet4Address netip.Addr
|
||||
inet6Address netip.Addr
|
||||
)
|
||||
for _, prefix := range options.Address {
|
||||
addr := tun.AddressFromAddr(prefix.Addr())
|
||||
protoAddr := tcpip.ProtocolAddress{
|
||||
@@ -76,12 +65,10 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
|
||||
},
|
||||
}
|
||||
if prefix.Addr().Is4() {
|
||||
inet4Address = prefix.Addr()
|
||||
tunDevice.inet4Address = inet4Address
|
||||
tunDevice.inet4Address = prefix.Addr()
|
||||
protoAddr.Protocol = ipv4.ProtocolNumber
|
||||
} else {
|
||||
inet6Address = prefix.Addr()
|
||||
tunDevice.inet6Address = inet6Address
|
||||
tunDevice.inet6Address = prefix.Addr()
|
||||
protoAddr.Protocol = ipv6.ProtocolNumber
|
||||
}
|
||||
gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})
|
||||
@@ -93,10 +80,10 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
|
||||
if options.Handler != nil {
|
||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
|
||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
|
||||
icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Logger, options.Handler, options.ICMPTimeout)
|
||||
icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
|
||||
icmpForwarder := tun.NewICMPForwarder(ipStack, options.Handler, options.Logger)
|
||||
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
|
||||
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
|
||||
tunDevice.icmpForwarder = icmpForwarder
|
||||
}
|
||||
return tunDevice, nil
|
||||
}
|
||||
@@ -255,6 +242,9 @@ func (w *stackDevice) Close() error {
|
||||
w.closeOnce.Do(func() {
|
||||
close(w.done)
|
||||
close(w.events)
|
||||
if w.icmpForwarder != nil {
|
||||
w.icmpForwarder.Close()
|
||||
}
|
||||
w.stack.Close()
|
||||
for _, endpoint := range w.stack.CleanupEndpoints() {
|
||||
endpoint.Abort()
|
||||
@@ -268,23 +258,6 @@ func (w *stackDevice) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (w *stackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
ctx := log.ContextWithNewID(w.ctx)
|
||||
destination, err := ping.ConnectGVisor(
|
||||
ctx, w.logger,
|
||||
metadata.Source.Addr, metadata.Destination.Addr,
|
||||
routeContext,
|
||||
w.stack,
|
||||
w.inet4Address, w.inet6Address,
|
||||
timeout,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||
return destination, nil
|
||||
}
|
||||
|
||||
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
|
||||
|
||||
type wireEndpoint stackDevice
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
@@ -17,12 +15,8 @@ import (
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/ping"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/wireguard-go/device"
|
||||
)
|
||||
|
||||
@@ -30,12 +24,11 @@ var _ Device = (*systemStackDevice)(nil)
|
||||
|
||||
type systemStackDevice struct {
|
||||
*systemDevice
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
stack *stack.Stack
|
||||
endpoint *deviceEndpoint
|
||||
writeBufs [][]byte
|
||||
closeOnce sync.Once
|
||||
stack *stack.Stack
|
||||
endpoint *deviceEndpoint
|
||||
icmpForwarder *tun.ICMPForwarder
|
||||
writeBufs [][]byte
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
|
||||
@@ -51,10 +44,6 @@ func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
inet4Address netip.Addr
|
||||
inet6Address netip.Addr
|
||||
)
|
||||
for _, prefix := range options.Address {
|
||||
addr := tun.AddressFromAddr(prefix.Addr())
|
||||
protoAddr := tcpip.ProtocolAddress{
|
||||
@@ -64,10 +53,8 @@ func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
|
||||
},
|
||||
}
|
||||
if prefix.Addr().Is4() {
|
||||
inet4Address = prefix.Addr()
|
||||
protoAddr.Protocol = ipv4.ProtocolNumber
|
||||
} else {
|
||||
inet6Address = prefix.Addr()
|
||||
protoAddr.Protocol = ipv6.ProtocolNumber
|
||||
}
|
||||
gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})
|
||||
@@ -75,21 +62,20 @@ func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
|
||||
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String())
|
||||
}
|
||||
}
|
||||
if options.Handler != nil {
|
||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
|
||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
|
||||
icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Logger, options.Handler, options.ICMPTimeout)
|
||||
icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
|
||||
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
|
||||
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
|
||||
}
|
||||
return &systemStackDevice{
|
||||
ctx: options.Context,
|
||||
logger: options.Logger,
|
||||
stackDevice := &systemStackDevice{
|
||||
systemDevice: system,
|
||||
stack: ipStack,
|
||||
endpoint: endpoint,
|
||||
}, nil
|
||||
}
|
||||
if options.Handler != nil {
|
||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
|
||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
|
||||
icmpForwarder := tun.NewICMPForwarder(ipStack, options.Handler, options.Logger)
|
||||
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
|
||||
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
|
||||
stackDevice.icmpForwarder = icmpForwarder
|
||||
}
|
||||
return stackDevice, nil
|
||||
}
|
||||
|
||||
func (w *systemStackDevice) SetDevice(device *device.Device) {
|
||||
@@ -129,6 +115,9 @@ func (w *systemStackDevice) Close() error {
|
||||
var err error
|
||||
w.closeOnce.Do(func() {
|
||||
close(w.endpoint.done)
|
||||
if w.icmpForwarder != nil {
|
||||
w.icmpForwarder.Close()
|
||||
}
|
||||
w.stack.Close()
|
||||
for _, endpoint := range w.stack.CleanupEndpoints() {
|
||||
endpoint.Abort()
|
||||
@@ -165,23 +154,6 @@ func (w *systemStackDevice) writeStack(packet []byte) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *systemStackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
ctx := log.ContextWithNewID(w.ctx)
|
||||
destination, err := ping.ConnectGVisor(
|
||||
ctx, w.logger,
|
||||
metadata.Source.Addr, metadata.Destination.Addr,
|
||||
routeContext,
|
||||
w.stack,
|
||||
w.inet4Address, w.inet6Address,
|
||||
timeout,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||
return destination, nil
|
||||
}
|
||||
|
||||
type deviceEndpoint struct {
|
||||
mtu uint32
|
||||
done chan struct{}
|
||||
|
||||
@@ -10,12 +10,9 @@ import (
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
@@ -35,7 +32,7 @@ type Endpoint struct {
|
||||
ipcConf string
|
||||
allowedAddress []netip.Prefix
|
||||
tunDevice Device
|
||||
natDevice NatDevice
|
||||
returnDevice *returnDeviceWrapper
|
||||
device *device.Device
|
||||
allowedIPs *device.AllowedIPs
|
||||
pause pause.Manager
|
||||
@@ -120,17 +117,13 @@ func NewEndpoint(options EndpointOptions) (*Endpoint, error) {
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create WireGuard device")
|
||||
}
|
||||
natDevice, isNatDevice := tunDevice.(NatDevice)
|
||||
if !isNatDevice {
|
||||
natDevice = NewNATDevice(options.Context, options.Logger, tunDevice)
|
||||
}
|
||||
return &Endpoint{
|
||||
options: options,
|
||||
peers: peers,
|
||||
ipcConf: ipcConf,
|
||||
allowedAddress: allowedAddresses,
|
||||
tunDevice: tunDevice,
|
||||
natDevice: natDevice,
|
||||
returnDevice: &returnDeviceWrapper{Device: tunDevice},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -157,7 +150,11 @@ func (e *Endpoint) Start(resolve bool) error {
|
||||
var bind conn.Bind
|
||||
wgListener, isWgListener := common.Cast[dialer.WireGuardListener](e.options.Dialer)
|
||||
if isWgListener {
|
||||
bind = conn.NewStdNetBind(wgListener.WireGuardControl())
|
||||
stdBind := conn.NewStdNetBind(wgListener.WireGuardControl())
|
||||
if e.options.ListenPort == 0 && len(e.peers) == 1 && e.peers[0].endpoint.IsValid() {
|
||||
stdBind.(*conn.StdNetBind).SetSinglePeerMode()
|
||||
}
|
||||
bind = stdBind
|
||||
} else {
|
||||
var (
|
||||
isConnect bool
|
||||
@@ -190,13 +187,7 @@ func (e *Endpoint) Start(resolve bool) error {
|
||||
e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
|
||||
},
|
||||
}
|
||||
var deviceInput Device
|
||||
if e.natDevice != nil {
|
||||
deviceInput = e.natDevice
|
||||
} else {
|
||||
deviceInput = e.tunDevice
|
||||
}
|
||||
wgDevice := device.NewDevice(e.options.Context, deviceInput, bind, logger, e.options.Workers)
|
||||
wgDevice := device.NewDevice(e.options.Context, e.returnDevice, bind, logger, e.options.Workers)
|
||||
e.tunDevice.SetDevice(wgDevice)
|
||||
var ipcConf strings.Builder
|
||||
ipcConf.WriteString(e.ipcConf)
|
||||
@@ -251,13 +242,6 @@ func (e *Endpoint) Lookup(address netip.Addr) *device.Peer {
|
||||
return e.allowedIPs.Lookup(address.AsSlice())
|
||||
}
|
||||
|
||||
func (e *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||
if e.natDevice == nil {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
return e.natDevice.CreateDestination(metadata, routeContext, timeout)
|
||||
}
|
||||
|
||||
func (e *Endpoint) onPauseUpdated(event int) {
|
||||
switch event {
|
||||
case pause.EventDevicePaused, pause.EventNetworkPause:
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/wireguard-go/device"
|
||||
)
|
||||
|
||||
func (e *Endpoint) PortAddresses() (netip.Addr, netip.Addr) {
|
||||
return e.tunDevice.Inet4Address(), e.tunDevice.Inet6Address()
|
||||
}
|
||||
|
||||
func (e *Endpoint) PortMTU() uint32 {
|
||||
return e.options.MTU
|
||||
}
|
||||
|
||||
func (e *Endpoint) WritePackets(packets [][]byte) error {
|
||||
wgDevice := e.device
|
||||
if wgDevice == nil {
|
||||
return E.New("WireGuard device is not ready")
|
||||
}
|
||||
packetRefs := make([]*device.InputPacketRef, 0, len(packets))
|
||||
refs := make([]device.InputPacketRef, len(packets))
|
||||
packetSlices := make([][]byte, len(packets))
|
||||
for i, packet := range packets {
|
||||
if len(packet) == 0 {
|
||||
continue
|
||||
}
|
||||
var destination []byte
|
||||
switch header.IPVersion(packet) {
|
||||
case header.IPv4Version:
|
||||
if len(packet) < header.IPv4MinimumSize {
|
||||
continue
|
||||
}
|
||||
destination = header.IPv4(packet).DestinationAddressSlice()
|
||||
case header.IPv6Version:
|
||||
if len(packet) < header.IPv6MinimumSize {
|
||||
continue
|
||||
}
|
||||
destination = header.IPv6(packet).DestinationAddressSlice()
|
||||
default:
|
||||
continue
|
||||
}
|
||||
packetSlices[i] = packet
|
||||
refs[i] = device.InputPacketRef{
|
||||
Destination: destination,
|
||||
PacketSlices: packetSlices[i : i+1],
|
||||
}
|
||||
packetRefs = append(packetRefs, &refs[i])
|
||||
}
|
||||
if len(packetRefs) == 0 {
|
||||
return nil
|
||||
}
|
||||
unmatchedRefs := wgDevice.InputPackets(packetRefs)
|
||||
if len(unmatchedRefs) == 0 {
|
||||
return nil
|
||||
}
|
||||
state := e.returnDevice.state.Load()
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
var replies [][]byte
|
||||
for _, packetRef := range unmatchedRefs {
|
||||
packet := packetRef.PacketSlices[0]
|
||||
var source netip.Addr
|
||||
if header.IPVersion(packet) == header.IPv4Version {
|
||||
source = e.tunDevice.Inet4Address()
|
||||
} else {
|
||||
source = e.tunDevice.Inet6Address()
|
||||
}
|
||||
reply, replyOk := tun.BuildUnreachable(packet, source, state.headroom)
|
||||
if replyOk {
|
||||
replies = append(replies, reply)
|
||||
}
|
||||
}
|
||||
if len(replies) > 0 {
|
||||
state.returnPath.ReturnPackets(replies)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Endpoint) AttachReturn(returnPath tun.Return) error {
|
||||
headroom := returnPath.ReturnHeadroom()
|
||||
if headroom > device.MessageTransportOffsetContent {
|
||||
return E.New("return path headroom ", headroom, " exceeds available ", device.MessageTransportOffsetContent)
|
||||
}
|
||||
newState := &returnPathState{
|
||||
returnPath: returnPath,
|
||||
headroom: headroom,
|
||||
}
|
||||
for {
|
||||
currentState := e.returnDevice.state.Load()
|
||||
if currentState != nil {
|
||||
if currentState.returnPath == returnPath {
|
||||
return nil
|
||||
}
|
||||
return E.New("return path already attached")
|
||||
}
|
||||
if e.returnDevice.state.CompareAndSwap(nil, newState) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Endpoint) DetachReturn(returnPath tun.Return) error {
|
||||
currentState := e.returnDevice.state.Load()
|
||||
if currentState != nil && currentState.returnPath == returnPath {
|
||||
e.returnDevice.state.CompareAndSwap(currentState, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type returnPathState struct {
|
||||
returnPath tun.Return
|
||||
headroom int
|
||||
}
|
||||
|
||||
type returnDeviceWrapper struct {
|
||||
Device
|
||||
state atomic.Pointer[returnPathState]
|
||||
}
|
||||
|
||||
func (d *returnDeviceWrapper) Write(bufs [][]byte, offset int) (int, error) {
|
||||
state := d.state.Load()
|
||||
if state == nil || len(bufs) == 0 {
|
||||
return d.Device.Write(bufs, offset)
|
||||
}
|
||||
packets := make([][]byte, len(bufs))
|
||||
for i, packet := range bufs {
|
||||
// wireguard-go leaves device.MessageTransportOffsetContent writable bytes in front of the decrypted packet.
|
||||
packets[i] = packet[offset-state.headroom:]
|
||||
}
|
||||
unconsumed := state.returnPath.ReturnPackets(packets)
|
||||
if len(unconsumed) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if len(unconsumed) == len(bufs) {
|
||||
return d.Device.Write(bufs, offset)
|
||||
}
|
||||
remaining := make([][]byte, 0, len(unconsumed))
|
||||
searchIndex := 0
|
||||
for _, packet := range unconsumed {
|
||||
for searchIndex < len(bufs) && &packet[0] != &bufs[searchIndex][offset-state.headroom] {
|
||||
searchIndex++
|
||||
}
|
||||
if searchIndex == len(bufs) {
|
||||
break
|
||||
}
|
||||
remaining = append(remaining, bufs[searchIndex])
|
||||
searchIndex++
|
||||
}
|
||||
return d.Device.Write(remaining, offset)
|
||||
}
|
||||
Reference in New Issue
Block a user