From 56bedd2f05585c4d0782cdee8c88871195523c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 21 Mar 2023 15:31:43 +0800 Subject: [PATCH] Add route support --- go.mod | 4 +- go.sum | 8 +- gvisor.go | 91 ++++++++++++++++++++- network_name.go | 41 ++++++++++ route.go | 89 +++++++++++++++++++++ route_gvisor.go | 16 ++++ route_mapping.go | 32 ++++++++ route_nat.go | 119 +++++++++++++++++++++++++++ route_nat_gvisor.go | 41 ++++++++++ route_non_gvisor.go | 11 +++ stack.go | 1 + system.go | 191 ++++++++++++++++++++++++++++++++++++++++++-- tun_linux.go | 5 +- 13 files changed, 631 insertions(+), 18 deletions(-) create mode 100644 network_name.go create mode 100644 route.go create mode 100644 route_gvisor.go create mode 100644 route_mapping.go create mode 100644 route_nat.go create mode 100644 route_nat_gvisor.go create mode 100644 route_non_gvisor.go diff --git a/go.mod b/go.mod index 3cef29a..1693661 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,9 @@ require ( github.com/fsnotify/fsnotify v1.6.0 github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 - github.com/sagernet/sing v0.1.7 + github.com/sagernet/sing v0.2.1-0.20230321172705-3e60222a1a7d golang.org/x/net v0.7.0 - golang.org/x/sys v0.5.0 + golang.org/x/sys v0.6.0 gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c ) diff --git a/go.sum b/go.sum index f45cbfe..1cc7040 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= -github.com/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08= -github.com/sagernet/sing v0.1.7/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= +github.com/sagernet/sing v0.2.1-0.20230321172705-3e60222a1a7d h1:ktk03rtgPqTDyUd2dWg1uzyr5RnptX8grSMvIzedJlQ= +github.com/sagernet/sing v0.2.1-0.20230321172705-3e60222a1a7d/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= @@ -16,8 +16,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4= diff --git a/gvisor.go b/gvisor.go index 9292002..b3a081c 100644 --- a/gvisor.go +++ b/gvisor.go @@ -4,10 +4,13 @@ package tun import ( "context" + "net" + "syscall" "time" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" "gvisor.dev/gvisor/pkg/tcpip" @@ -32,9 +35,12 @@ type GVisor struct { tunMtu uint32 endpointIndependentNat bool udpTimeout int64 + router Router handler Handler + logger logger.Logger stack *stack.Stack endpoint stack.LinkEndpoint + routeMapping *RouteMapping } type GVisorTun interface { @@ -56,7 +62,10 @@ func NewGVisor( tunMtu: options.MTU, endpointIndependentNat: options.EndpointIndependentNat, udpTimeout: options.UDPTimeout, + router: options.Router, handler: options.Handler, + logger: options.Logger, + routeMapping: NewRouteMapping(options.UDPTimeout), }, nil } @@ -103,7 +112,7 @@ func (t *GVisor) Start() error { mOpt := tcpip.TCPModerateReceiveBufferOption(true) ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &mOpt) - ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tcp.NewForwarder(ipStack, 0, 1024, func(r *tcp.ForwarderRequest) { + tcpForwarder := tcp.NewForwarder(ipStack, 0, 1024, func(r *tcp.ForwarderRequest) { var wq waiter.Queue handshakeCtx, cancel := context.WithCancel(context.Background()) go func() { @@ -141,10 +150,47 @@ func (t *GVisor) Start() error { endpoint.Abort() } }() - }).HandlePacket) + }) + ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, func(id stack.TransportEndpointID, buffer *stack.PacketBuffer) bool { + if t.router != nil { + var routeSession RouteSession + routeSession.Network = syscall.IPPROTO_TCP + var ipHdr header.Network + if buffer.NetworkProtocolNumber == header.IPv4ProtocolNumber { + routeSession.IPVersion = 4 + ipHdr = header.IPv4(buffer.NetworkHeader().Slice()) + } else { + routeSession.IPVersion = 6 + ipHdr = header.IPv6(buffer.NetworkHeader().Slice()) + } + tcpHdr := header.TCP(buffer.TransportHeader().Slice()) + routeSession.Source = M.AddrPortFrom(net.IP(ipHdr.SourceAddress()), tcpHdr.SourcePort()) + routeSession.Destination = M.AddrPortFrom(net.IP(ipHdr.DestinationAddress()), tcpHdr.DestinationPort()) + action := t.routeMapping.Lookup(routeSession, func() RouteAction { + if routeSession.IPVersion == 4 { + return t.router.RouteConnection(routeSession, &systemTCPDirectPacketWriter4{t.tun, routeSession.Source}) + } else { + return t.router.RouteConnection(routeSession, &systemTCPDirectPacketWriter6{t.tun, routeSession.Source}) + } + }) + switch actionType := action.(type) { + case *ActionReject: + // TODO: send icmp unreachable + return true + case *ActionDirect: + buffer.IncRef() + err = actionType.WritePacketBuffer(buffer) + if err != nil { + t.logger.Trace("route gvisor tcp packet: ", err) + } + return true + } + } + return tcpForwarder.HandlePacket(id, buffer) + }) if !t.endpointIndependentNat { - ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udp.NewForwarder(ipStack, func(request *udp.ForwarderRequest) { + udpForwarder := udp.NewForwarder(ipStack, func(request *udp.ForwarderRequest) { var wq waiter.Queue endpoint, err := request.CreateEndpoint(&wq) if err != nil { @@ -166,7 +212,44 @@ func (t *GVisor) Start() error { endpoint.Abort() } }() - }).HandlePacket) + }) + ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, buffer *stack.PacketBuffer) bool { + if t.router != nil { + var routeSession RouteSession + routeSession.Network = syscall.IPPROTO_UDP + var ipHdr header.Network + if buffer.NetworkProtocolNumber == header.IPv4ProtocolNumber { + routeSession.IPVersion = 4 + ipHdr = header.IPv4(buffer.NetworkHeader().Slice()) + } else { + routeSession.IPVersion = 6 + ipHdr = header.IPv6(buffer.NetworkHeader().Slice()) + } + udpHdr := header.UDP(buffer.TransportHeader().Slice()) + routeSession.Source = M.AddrPortFrom(net.IP(ipHdr.SourceAddress()), udpHdr.SourcePort()) + routeSession.Destination = M.AddrPortFrom(net.IP(ipHdr.DestinationAddress()), udpHdr.DestinationPort()) + action := t.routeMapping.Lookup(routeSession, func() RouteAction { + if routeSession.IPVersion == 4 { + return t.router.RouteConnection(routeSession, &systemUDPDirectPacketWriter4{t.tun, routeSession.Source}) + } else { + return t.router.RouteConnection(routeSession, &systemUDPDirectPacketWriter6{t.tun, routeSession.Source}) + } + }) + switch actionType := action.(type) { + case *ActionReject: + // TODO: send icmp unreachable + return true + case *ActionDirect: + buffer.IncRef() + err = actionType.WritePacketBuffer(buffer) + if err != nil { + t.logger.Trace("route gvisor udp packet: ", err) + } + return true + } + } + return udpForwarder.HandlePacket(id, buffer) + }) } else { ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, NewUDPForwarder(t.ctx, ipStack, t.handler, t.udpTimeout).HandlePacket) } diff --git a/network_name.go b/network_name.go new file mode 100644 index 0000000..c136332 --- /dev/null +++ b/network_name.go @@ -0,0 +1,41 @@ +package tun + +import ( + "strconv" + + "github.com/sagernet/sing-tun/internal/clashtcpip" + F "github.com/sagernet/sing/common/format" + N "github.com/sagernet/sing/common/network" +) + +func NetworkName(network uint8) string { + switch network { + case clashtcpip.TCP: + return N.NetworkTCP + case clashtcpip.UDP: + return N.NetworkUDP + case clashtcpip.ICMP: + return N.NetworkICMPv4 + case clashtcpip.ICMPv6: + return N.NetworkICMPv6 + } + return F.ToString(network) +} + +func NetworkFromName(name string) uint8 { + switch name { + case N.NetworkTCP: + return clashtcpip.TCP + case N.NetworkUDP: + return clashtcpip.UDP + case N.NetworkICMPv4: + return clashtcpip.ICMP + case N.NetworkICMPv6: + return clashtcpip.ICMPv6 + } + parseNetwork, err := strconv.ParseUint(name, 10, 8) + if err != nil { + return 0 + } + return uint8(parseNetwork) +} diff --git a/route.go b/route.go new file mode 100644 index 0000000..91c91a5 --- /dev/null +++ b/route.go @@ -0,0 +1,89 @@ +package tun + +import ( + "net/netip" + + E "github.com/sagernet/sing/common/exceptions" +) + +type ActionType = uint8 + +const ( + ActionTypeReturn ActionType = iota + ActionTypeReject + ActionTypeDirect +) + +func ParseActionType(action string) (ActionType, error) { + switch action { + case "return": + return ActionTypeReturn, nil + case "reject": + return ActionTypeReject, nil + case "direct": + return ActionTypeDirect, nil + default: + return 0, E.New("unknown action: ", action) + } +} + +func ActionTypeName(actionType ActionType) string { + switch actionType { + case ActionTypeReturn: + return "return" + case ActionTypeReject: + return "reject" + case ActionTypeDirect: + return "direct" + default: + return "unknown" + } +} + +type RouteSession struct { + IPVersion uint8 + Network uint8 + Source netip.AddrPort + Destination netip.AddrPort +} + +type RouteContext interface { + WritePacket(packet []byte) error +} + +type Router interface { + RouteConnection(session RouteSession, context RouteContext) RouteAction +} + +type RouteAction interface { + ActionType() ActionType + Timeout() bool +} + +type ActionReturn struct{} + +func (r *ActionReturn) ActionType() ActionType { + return ActionTypeReturn +} + +func (r *ActionReturn) Timeout() bool { + return false +} + +type ActionReject struct{} + +func (r *ActionReject) ActionType() ActionType { + return ActionTypeReject +} + +func (r *ActionReject) Timeout() bool { + return false +} + +type ActionDirect struct { + DirectDestination +} + +func (r *ActionDirect) ActionType() ActionType { + return ActionTypeDirect +} diff --git a/route_gvisor.go b/route_gvisor.go new file mode 100644 index 0000000..eaf6c1d --- /dev/null +++ b/route_gvisor.go @@ -0,0 +1,16 @@ +//go:build with_gvisor + +package tun + +import ( + "github.com/sagernet/sing/common/buf" + + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +type DirectDestination interface { + WritePacket(buffer *buf.Buffer) error + WritePacketBuffer(buffer *stack.PacketBuffer) error + Close() error + Timeout() bool +} diff --git a/route_mapping.go b/route_mapping.go new file mode 100644 index 0000000..b0ccad8 --- /dev/null +++ b/route_mapping.go @@ -0,0 +1,32 @@ +package tun + +import ( + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/cache" +) + +type RouteMapping struct { + status *cache.LruCache[RouteSession, RouteAction] +} + +func NewRouteMapping(maxAge int64) *RouteMapping { + return &RouteMapping{ + status: cache.New( + cache.WithAge[RouteSession, RouteAction](maxAge), + cache.WithUpdateAgeOnGet[RouteSession, RouteAction](), + cache.WithEvict[RouteSession, RouteAction](func(key RouteSession, conn RouteAction) { + common.Close(conn) + }), + ), + } +} + +func (m *RouteMapping) Lookup(session RouteSession, constructor func() RouteAction) RouteAction { + action, _ := m.status.LoadOrStore(session, constructor) + if action.Timeout() { + common.Close(action) + action = constructor() + m.status.Store(session, action) + } + return action +} diff --git a/route_nat.go b/route_nat.go new file mode 100644 index 0000000..35b1d55 --- /dev/null +++ b/route_nat.go @@ -0,0 +1,119 @@ +package tun + +import ( + "net/netip" + "sync" + + "github.com/sagernet/sing-tun/internal/clashtcpip" +) + +type NatMapping struct { + access sync.RWMutex + sessions map[RouteSession]RouteContext + ipRewrite bool +} + +func NewNatMapping(ipRewrite bool) *NatMapping { + return &NatMapping{ + sessions: make(map[RouteSession]RouteContext), + ipRewrite: ipRewrite, + } +} + +func (m *NatMapping) CreateSession(session RouteSession, context RouteContext) { + if m.ipRewrite { + session.Source = netip.AddrPort{} + } + m.access.Lock() + m.sessions[session] = context + m.access.Unlock() +} + +func (m *NatMapping) DeleteSession(session RouteSession) { + if m.ipRewrite { + session.Source = netip.AddrPort{} + } + m.access.Lock() + delete(m.sessions, session) + m.access.Unlock() +} + +func (m *NatMapping) WritePacket(packet []byte) (bool, error) { + var routeSession RouteSession + var ipHdr clashtcpip.IP + switch ipVersion := packet[0] >> 4; ipVersion { + case 4: + routeSession.IPVersion = 4 + ipHdr = clashtcpip.IPv4Packet(packet) + case 6: + routeSession.IPVersion = 6 + ipHdr = clashtcpip.IPv6Packet(packet) + default: + return false, nil + } + routeSession.Network = ipHdr.Protocol() + switch routeSession.Network { + case clashtcpip.TCP: + tcpHdr := clashtcpip.TCPPacket(ipHdr.Payload()) + routeSession.Destination = netip.AddrPortFrom(ipHdr.SourceIP(), tcpHdr.SourcePort()) + if !m.ipRewrite { + routeSession.Source = netip.AddrPortFrom(ipHdr.DestinationIP(), tcpHdr.DestinationPort()) + } + case clashtcpip.UDP: + udpHdr := clashtcpip.UDPPacket(ipHdr.Payload()) + routeSession.Destination = netip.AddrPortFrom(ipHdr.SourceIP(), udpHdr.SourcePort()) + if !m.ipRewrite { + routeSession.Source = netip.AddrPortFrom(ipHdr.DestinationIP(), udpHdr.DestinationPort()) + } + default: + routeSession.Destination = netip.AddrPortFrom(ipHdr.SourceIP(), 0) + if !m.ipRewrite { + routeSession.Source = netip.AddrPortFrom(ipHdr.DestinationIP(), 0) + } + } + m.access.RLock() + context, loaded := m.sessions[routeSession] + m.access.RUnlock() + if !loaded { + return false, nil + } + return true, context.WritePacket(packet) +} + +type NatWriter struct { + inet4Address netip.Addr + inet6Address netip.Addr +} + +func NewNatWriter(inet4Address netip.Addr, inet6Address netip.Addr) *NatWriter { + return &NatWriter{ + inet4Address: inet4Address, + inet6Address: inet6Address, + } +} + +func (w *NatWriter) RewritePacket(packet []byte) { + var ipHdr clashtcpip.IP + var bindAddr netip.Addr + switch ipVersion := packet[0] >> 4; ipVersion { + case 4: + ipHdr = clashtcpip.IPv4Packet(packet) + bindAddr = w.inet4Address + case 6: + ipHdr = clashtcpip.IPv6Packet(packet) + bindAddr = w.inet6Address + default: + return + } + ipHdr.SetSourceIP(bindAddr) + switch ipHdr.Protocol() { + case clashtcpip.TCP: + tcpHdr := clashtcpip.TCPPacket(ipHdr.Payload()) + tcpHdr.ResetChecksum(ipHdr.PseudoSum()) + case clashtcpip.UDP: + udpHdr := clashtcpip.UDPPacket(ipHdr.Payload()) + udpHdr.ResetChecksum(ipHdr.PseudoSum()) + default: + } + ipHdr.ResetChecksum() +} diff --git a/route_nat_gvisor.go b/route_nat_gvisor.go new file mode 100644 index 0000000..cb7c5ce --- /dev/null +++ b/route_nat_gvisor.go @@ -0,0 +1,41 @@ +//go:build with_gvisor + +package tun + +import ( + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +func (w *NatWriter) RewritePacketBuffer(packetBuffer *stack.PacketBuffer) { + var bindAddr tcpip.Address + if packetBuffer.NetworkProtocolNumber == header.IPv4ProtocolNumber { + bindAddr = tcpip.Address(w.inet4Address.AsSlice()) + } else { + bindAddr = tcpip.Address(w.inet6Address.AsSlice()) + } + var ipHdr header.Network + switch packetBuffer.NetworkProtocolNumber { + case header.IPv4ProtocolNumber: + ipHdr = header.IPv4(packetBuffer.NetworkHeader().Slice()) + case header.IPv6ProtocolNumber: + ipHdr = header.IPv6(packetBuffer.NetworkHeader().Slice()) + default: + return + } + oldAddr := ipHdr.SourceAddress() + if checksumHdr, needChecksum := ipHdr.(header.ChecksummableNetwork); needChecksum { + checksumHdr.SetSourceAddressWithChecksumUpdate(bindAddr) + } else { + ipHdr.SetSourceAddress(bindAddr) + } + switch packetBuffer.TransportProtocolNumber { + case header.TCPProtocolNumber: + tcpHdr := header.TCP(packetBuffer.TransportHeader().Slice()) + tcpHdr.UpdateChecksumPseudoHeaderAddress(oldAddr, bindAddr, true) + case header.UDPProtocolNumber: + udpHdr := header.UDP(packetBuffer.TransportHeader().Slice()) + udpHdr.UpdateChecksumPseudoHeaderAddress(oldAddr, bindAddr, true) + } +} diff --git a/route_non_gvisor.go b/route_non_gvisor.go new file mode 100644 index 0000000..d77d975 --- /dev/null +++ b/route_non_gvisor.go @@ -0,0 +1,11 @@ +//go:build !with_gvisor + +package tun + +import "github.com/sagernet/sing/common/buf" + +type DirectDestination interface { + WritePacket(buffer *buf.Buffer) error + Close() error + Timeout() bool +} diff --git a/stack.go b/stack.go index 83612a9..517c0fb 100644 --- a/stack.go +++ b/stack.go @@ -22,6 +22,7 @@ type StackOptions struct { Inet6Address []netip.Prefix EndpointIndependentNat bool UDPTimeout int64 + Router Router Handler Handler Logger logger.Logger UnderPlatform bool diff --git a/system.go b/system.go index ff8d06c..a857b3a 100644 --- a/system.go +++ b/system.go @@ -4,6 +4,7 @@ import ( "context" "net" "net/netip" + "syscall" "time" "github.com/sagernet/sing-tun/internal/clashtcpip" @@ -20,6 +21,7 @@ type System struct { ctx context.Context tun Tun mtu uint32 + router Router handler Handler logger logger.Logger inet4Prefixes []netip.Prefix @@ -35,6 +37,7 @@ type System struct { tcpPort6 uint16 tcpNat *TCPNat udpNat *udpnat.Service[netip.AddrPort] + routeMapping *RouteMapping } type Session struct { @@ -50,10 +53,12 @@ func NewSystem(options StackOptions) (Stack, error) { tun: options.Tun, mtu: options.MTU, udpTimeout: options.UDPTimeout, + router: options.Router, handler: options.Handler, logger: options.Logger, inet4Prefixes: options.Inet4Address, inet6Prefixes: options.Inet6Address, + routeMapping: NewRouteMapping(options.UDPTimeout), } if len(options.Inet4Address) > 0 { if options.Inet4Address[0].Bits() == 32 { @@ -246,6 +251,21 @@ func (s *System) processIPv4TCP(packet clashtcpip.IPv4Packet, header clashtcpip. packet.SetDestinationIP(session.Source.Addr()) header.SetDestinationPort(session.Source.Port()) } else { + if s.router != nil { + session := RouteSession{4, syscall.IPPROTO_TCP, source, destination} + action := s.routeMapping.Lookup(session, func() RouteAction { + return s.router.RouteConnection(session, &systemTCPDirectPacketWriter4{s.tun, source}) + }) + switch actionType := action.(type) { + case *ActionReject: + // TODO: send ICMP unreachable + return nil + case *ActionDirect: + return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error { + return E.Cause(err, "route ipv4 tcp packet") + }) + } + } natPort := s.tcpNat.Lookup(source, destination) packet.SetSourceIP(s.inet4Address) header.SetSourcePort(natPort) @@ -272,6 +292,21 @@ func (s *System) processIPv6TCP(packet clashtcpip.IPv6Packet, header clashtcpip. packet.SetDestinationIP(session.Source.Addr()) header.SetDestinationPort(session.Source.Port()) } else { + if s.router != nil { + session := RouteSession{6, syscall.IPPROTO_TCP, source, destination} + action := s.routeMapping.Lookup(session, func() RouteAction { + return s.router.RouteConnection(session, &systemTCPDirectPacketWriter6{s.tun, source}) + }) + switch actionType := action.(type) { + case *ActionReject: + // TODO: send RST + return nil + case *ActionDirect: + return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error { + return E.Cause(err, "route ipv6 tcp packet") + }) + } + } natPort := s.tcpNat.Lookup(source, destination) packet.SetSourceIP(s.inet6Address) header.SetSourcePort(natPort) @@ -295,6 +330,21 @@ func (s *System) processIPv4UDP(packet clashtcpip.IPv4Packet, header clashtcpip. if !destination.Addr().IsGlobalUnicast() { return common.Error(s.tun.Write(packet)) } + if s.router != nil { + routeSession := RouteSession{4, syscall.IPPROTO_UDP, source, destination} + action := s.routeMapping.Lookup(routeSession, func() RouteAction { + return s.router.RouteConnection(routeSession, &systemUDPDirectPacketWriter4{s.tun, source}) + }) + switch actionType := action.(type) { + case *ActionReject: + // TODO: send icmp unreachable + return nil + case *ActionDirect: + return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error { + return E.Cause(err, "route ipv4 udp packet") + }) + } + } data := buf.As(header.Payload()) if data.Len() == 0 { return nil @@ -307,7 +357,7 @@ func (s *System) processIPv4UDP(packet clashtcpip.IPv4Packet, header clashtcpip. headerLen := packet.HeaderLen() + clashtcpip.UDPHeaderSize headerCopy := make([]byte, headerLen) copy(headerCopy, packet[:headerLen]) - return &systemPacketWriter4{s.tun, headerCopy, source} + return &systemUDPPacketWriter4{s.tun, headerCopy, source} }) return nil } @@ -318,6 +368,21 @@ func (s *System) processIPv6UDP(packet clashtcpip.IPv6Packet, header clashtcpip. if !destination.Addr().IsGlobalUnicast() { return common.Error(s.tun.Write(packet)) } + if s.router != nil { + routeSession := RouteSession{6, syscall.IPPROTO_UDP, source, destination} + action := s.routeMapping.Lookup(routeSession, func() RouteAction { + return s.router.RouteConnection(routeSession, &systemUDPDirectPacketWriter6{s.tun, source}) + }) + switch actionType := action.(type) { + case *ActionReject: + // TODO: send icmp unreachable + return nil + case *ActionDirect: + return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error { + return E.Cause(err, "route ipv6 udp packet") + }) + } + } data := buf.As(header.Payload()) if data.Len() == 0 { return nil @@ -330,12 +395,27 @@ func (s *System) processIPv6UDP(packet clashtcpip.IPv6Packet, header clashtcpip. headerLen := len(packet) - int(header.Length()) + clashtcpip.UDPHeaderSize headerCopy := make([]byte, headerLen) copy(headerCopy, packet[:headerLen]) - return &systemPacketWriter6{s.tun, headerCopy, source} + return &systemUDPPacketWriter6{s.tun, headerCopy, source} }) return nil } func (s *System) processIPv4ICMP(packet clashtcpip.IPv4Packet, header clashtcpip.ICMPPacket) error { + if s.router != nil { + routeSession := RouteSession{4, syscall.IPPROTO_ICMP, netip.AddrPortFrom(packet.SourceIP(), 0), netip.AddrPortFrom(packet.DestinationIP(), 0)} + action := s.routeMapping.Lookup(routeSession, func() RouteAction { + return s.router.RouteConnection(routeSession, &systemICMPDirectPacketWriter4{s.tun, packet.SourceIP()}) + }) + switch actionType := action.(type) { + case *ActionReject: + // TODO: send icmp unreachable + return nil + case *ActionDirect: + return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error { + return E.Cause(err, "route ipv4 icmp packet") + }) + } + } if header.Type() != clashtcpip.ICMPTypePingRequest || header.Code() != 0 { return nil } @@ -349,6 +429,21 @@ func (s *System) processIPv4ICMP(packet clashtcpip.IPv4Packet, header clashtcpip } func (s *System) processIPv6ICMP(packet clashtcpip.IPv6Packet, header clashtcpip.ICMPv6Packet) error { + if s.router != nil { + routeSession := RouteSession{6, syscall.IPPROTO_ICMPV6, netip.AddrPortFrom(packet.SourceIP(), 0), netip.AddrPortFrom(packet.DestinationIP(), 0)} + action := s.routeMapping.Lookup(routeSession, func() RouteAction { + return s.router.RouteConnection(routeSession, &systemICMPDirectPacketWriter6{s.tun, packet.SourceIP()}) + }) + switch actionType := action.(type) { + case *ActionReject: + // TODO: send icmp unreachable + return nil + case *ActionDirect: + return E.Append(nil, actionType.WritePacket(buf.As(packet).ToOwned()), func(err error) error { + return E.Cause(err, "route ipv6 icmp packet") + }) + } + } if header.Type() != clashtcpip.ICMPv6EchoRequest || header.Code() != 0 { return nil } @@ -361,13 +456,97 @@ func (s *System) processIPv6ICMP(packet clashtcpip.IPv6Packet, header clashtcpip return common.Error(s.tun.Write(packet)) } -type systemPacketWriter4 struct { +type systemTCPDirectPacketWriter4 struct { + tun Tun + source netip.AddrPort +} + +func (w *systemTCPDirectPacketWriter4) WritePacket(p []byte) error { + packet := clashtcpip.IPv4Packet(p) + header := clashtcpip.TCPPacket(packet.Payload()) + packet.SetDestinationIP(w.source.Addr()) + header.SetDestinationPort(w.source.Port()) + header.ResetChecksum(packet.PseudoSum()) + packet.ResetChecksum() + return common.Error(w.tun.Write(packet)) +} + +type systemTCPDirectPacketWriter6 struct { + tun Tun + source netip.AddrPort +} + +func (w *systemTCPDirectPacketWriter6) WritePacket(p []byte) error { + packet := clashtcpip.IPv6Packet(p) + header := clashtcpip.TCPPacket(packet.Payload()) + packet.SetDestinationIP(w.source.Addr()) + header.SetDestinationPort(w.source.Port()) + header.ResetChecksum(packet.PseudoSum()) + packet.ResetChecksum() + return common.Error(w.tun.Write(packet)) +} + +type systemUDPDirectPacketWriter4 struct { + tun Tun + source netip.AddrPort +} + +func (w *systemUDPDirectPacketWriter4) WritePacket(p []byte) error { + packet := clashtcpip.IPv4Packet(p) + header := clashtcpip.UDPPacket(packet.Payload()) + packet.SetDestinationIP(w.source.Addr()) + header.SetDestinationPort(w.source.Port()) + header.ResetChecksum(packet.PseudoSum()) + packet.ResetChecksum() + return common.Error(w.tun.Write(packet)) +} + +type systemUDPDirectPacketWriter6 struct { + tun Tun + source netip.AddrPort +} + +func (w *systemUDPDirectPacketWriter6) WritePacket(p []byte) error { + packet := clashtcpip.IPv6Packet(p) + header := clashtcpip.UDPPacket(packet.Payload()) + packet.SetDestinationIP(w.source.Addr()) + header.SetDestinationPort(w.source.Port()) + header.ResetChecksum(packet.PseudoSum()) + packet.ResetChecksum() + return common.Error(w.tun.Write(packet)) +} + +type systemICMPDirectPacketWriter4 struct { + tun Tun + source netip.Addr +} + +func (w *systemICMPDirectPacketWriter4) WritePacket(p []byte) error { + packet := clashtcpip.IPv4Packet(p) + packet.SetDestinationIP(w.source) + packet.ResetChecksum() + return common.Error(w.tun.Write(packet)) +} + +type systemICMPDirectPacketWriter6 struct { + tun Tun + source netip.Addr +} + +func (w *systemICMPDirectPacketWriter6) WritePacket(p []byte) error { + packet := clashtcpip.IPv6Packet(p) + packet.SetDestinationIP(w.source) + packet.ResetChecksum() + return common.Error(w.tun.Write(packet)) +} + +type systemUDPPacketWriter4 struct { tun Tun header []byte source netip.AddrPort } -func (w *systemPacketWriter4) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { +func (w *systemUDPPacketWriter4) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { newPacket := buf.StackNewSize(len(w.header) + buffer.Len()) defer newPacket.Release() newPacket.Write(w.header) @@ -385,13 +564,13 @@ func (w *systemPacketWriter4) WritePacket(buffer *buf.Buffer, destination M.Sock return common.Error(w.tun.Write(newPacket.Bytes())) } -type systemPacketWriter6 struct { +type systemUDPPacketWriter6 struct { tun Tun header []byte source netip.AddrPort } -func (w *systemPacketWriter6) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { +func (w *systemUDPPacketWriter6) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { newPacket := buf.StackNewSize(len(w.header) + buffer.Len()) defer newPacket.Release() newPacket.Write(w.header) diff --git a/tun_linux.go b/tun_linux.go index 3085929..525dff3 100644 --- a/tun_linux.go +++ b/tun_linux.go @@ -6,6 +6,7 @@ import ( "net/netip" "os" "runtime" + "syscall" "unsafe" "github.com/sagernet/netlink" @@ -383,7 +384,7 @@ func (t *NativeTun) rules() []*netlink.Rule { if p4 { it = netlink.NewRule() it.Priority = priority - it.IPProto = unix.IPPROTO_ICMP + it.IPProto = syscall.IPPROTO_ICMP it.Goto = nopPriority it.Family = unix.AF_INET rules = append(rules, it) @@ -392,7 +393,7 @@ func (t *NativeTun) rules() []*netlink.Rule { if p6 { it = netlink.NewRule() it.Priority = priority6 - it.IPProto = unix.IPPROTO_ICMPV6 + it.IPProto = syscall.IPPROTO_ICMPV6 it.Goto = nopPriority it.Family = unix.AF_INET6 rules = append(rules, it)