Fix missing UDP and ICMP bypass for auto redirect

This commit is contained in:
世界 2026-07-10 12:11:47 +08:00
parent 80ea96e5b4
commit 99fb96a4c8
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
2 changed files with 231 additions and 85 deletions

View file

@ -5,6 +5,7 @@ package tun
import (
"context"
"errors"
"net/netip"
"sync/atomic"
"github.com/sagernet/sing-tun/internal/gtcpip/header"
@ -100,51 +101,147 @@ func (h *nfqueueHandler) Start() error {
return nil
}
func parseIPv6TransportHeader(payload []byte) (transportProto uint8, transportOffset int, ok bool) {
if len(payload) < header.IPv6MinimumSize {
return 0, 0, false
const ipv6AuthenticationHeaderIdentifier header.IPv6ExtensionHeaderIdentifier = 51
type preMatchPacket struct {
protocol uint8
network string
source M.Socksaddr
destination M.Socksaddr
}
func parsePreMatchPacket(packet []byte) (preMatchPacket, bool) {
if len(packet) < 1 {
return preMatchPacket{}, false
}
var (
protocol uint8
transportOffset int
source netip.Addr
destination netip.Addr
)
switch header.IPVersion(packet) {
case header.IPv4Version:
if len(packet) < header.IPv4MinimumSize {
return preMatchPacket{}, false
}
ipHdr := header.IPv4(packet)
transportOffset = int(ipHdr.HeaderLength())
if transportOffset < header.IPv4MinimumSize || transportOffset > len(packet) || int(ipHdr.TotalLength()) < transportOffset || ipHdr.FragmentOffset() != 0 {
return preMatchPacket{}, false
}
protocol = uint8(ipHdr.TransportProtocol())
source = ipHdr.SourceAddr()
destination = ipHdr.DestinationAddr()
case header.IPv6Version:
if len(packet) < header.IPv6MinimumSize {
return preMatchPacket{}, false
}
ipHdr := header.IPv6(packet)
var ok bool
protocol, transportOffset, ok = parsePreMatchIPv6Transport(packet)
if !ok {
return preMatchPacket{}, false
}
source = ipHdr.SourceAddr()
destination = ipHdr.DestinationAddr()
default:
return preMatchPacket{}, false
}
ipv6 := header.IPv6(payload)
nextHeader := ipv6.NextHeader()
transport := packet[transportOffset:]
parsed := preMatchPacket{protocol: protocol}
switch protocol {
case uint8(header.TCPProtocolNumber):
if len(transport) < header.TCPMinimumSize {
return preMatchPacket{}, false
}
tcpHdr := header.TCP(transport)
flags := tcpHdr.Flags()
if !flags.Contains(header.TCPFlagSyn) || flags.Contains(header.TCPFlagAck) {
return preMatchPacket{}, false
}
parsed.network = N.NetworkTCP
parsed.source = M.SocksaddrFrom(source, tcpHdr.SourcePort())
parsed.destination = M.SocksaddrFrom(destination, tcpHdr.DestinationPort())
case uint8(header.UDPProtocolNumber):
if len(transport) < header.UDPMinimumSize {
return preMatchPacket{}, false
}
udpHdr := header.UDP(transport)
if int(udpHdr.Length()) < header.UDPMinimumSize {
return preMatchPacket{}, false
}
parsed.network = N.NetworkUDP
parsed.source = M.SocksaddrFrom(source, udpHdr.SourcePort())
parsed.destination = M.SocksaddrFrom(destination, udpHdr.DestinationPort())
case uint8(header.ICMPv4ProtocolNumber):
if !source.Is4() || len(transport) < header.ICMPv4MinimumSize {
return preMatchPacket{}, false
}
icmpHdr := header.ICMPv4(transport)
if icmpHdr.Type() != header.ICMPv4Echo || icmpHdr.Code() != 0 {
return preMatchPacket{}, false
}
parsed.network = N.NetworkICMP
parsed.source = M.SocksaddrFrom(source, 0)
parsed.destination = M.SocksaddrFrom(destination, 0)
case uint8(header.ICMPv6ProtocolNumber):
if !source.Is6() || len(transport) < header.ICMPv6MinimumSize {
return preMatchPacket{}, false
}
icmpHdr := header.ICMPv6(transport)
if icmpHdr.Type() != header.ICMPv6EchoRequest || icmpHdr.Code() != 0 {
return preMatchPacket{}, false
}
parsed.network = N.NetworkICMP
parsed.source = M.SocksaddrFrom(source, 0)
parsed.destination = M.SocksaddrFrom(destination, 0)
default:
return preMatchPacket{}, false
}
return parsed, true
}
func parsePreMatchIPv6Transport(packet []byte) (transportProto uint8, transportOffset int, ok bool) {
nextHeader := header.IPv6(packet).NextHeader()
offset := header.IPv6MinimumSize
for {
switch nextHeader {
case unix.IPPROTO_HOPOPTS,
unix.IPPROTO_ROUTING,
unix.IPPROTO_DSTOPTS:
if len(payload) < offset+2 {
switch header.IPv6ExtensionHeaderIdentifier(nextHeader) {
case header.IPv6HopByHopOptionsExtHdrIdentifier,
header.IPv6RoutingExtHdrIdentifier,
header.IPv6DestinationOptionsExtHdrIdentifier:
if len(packet) < offset+2 {
return 0, 0, false
}
nextHeader = payload[offset]
extLen := int(payload[offset+1]+1) * 8
if len(payload) < offset+extLen {
nextHeader = packet[offset]
extensionLength := (int(packet[offset+1]) + 1) * 8
if len(packet) < offset+extensionLength {
return 0, 0, false
}
offset += extLen
case unix.IPPROTO_FRAGMENT:
if len(payload) < offset+8 {
offset += extensionLength
case header.IPv6FragmentExtHdrIdentifier:
if len(packet) < offset+header.IPv6FragmentHeaderSize {
return 0, 0, false
}
nextHeader = payload[offset]
offset += 8
case unix.IPPROTO_AH:
if len(payload) < offset+2 {
fragmentHdr := header.IPv6Fragment(packet[offset:])
if fragmentHdr.FragmentOffset() != 0 {
return 0, 0, false
}
nextHeader = payload[offset]
extLen := int(payload[offset+1]+2) * 4
if len(payload) < offset+extLen {
nextHeader = fragmentHdr.NextHeader()
offset += header.IPv6FragmentHeaderSize
case ipv6AuthenticationHeaderIdentifier:
if len(packet) < offset+2 {
return 0, 0, false
}
offset += extLen
case unix.IPPROTO_NONE:
nextHeader = packet[offset]
extensionLength := (int(packet[offset+1]) + 2) * 4
if len(packet) < offset+extensionLength {
return 0, 0, false
}
offset += extensionLength
case header.IPv6NoNextHeaderIdentifier:
return 0, 0, false
default:
return nextHeader, offset, true
}
@ -162,56 +259,13 @@ func (h *nfqueueHandler) handlePacket(attr nfqueue.Attribute) int {
packetID := *attr.PacketID
payload := *attr.Payload
if len(payload) < header.IPv4MinimumSize {
packet, loaded := parsePreMatchPacket(payload)
if !loaded {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
var srcAddr, dstAddr M.Socksaddr
var tcpOffset int
version := payload[0] >> 4
switch version {
case 4:
ipv4 := header.IPv4(payload)
if !ipv4.IsValid(len(payload)) || ipv4.Protocol() != uint8(unix.IPPROTO_TCP) {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
srcAddr = M.SocksaddrFrom(ipv4.SourceAddr(), 0)
dstAddr = M.SocksaddrFrom(ipv4.DestinationAddr(), 0)
tcpOffset = int(ipv4.HeaderLength())
case 6:
transportProto, transportOffset, ok := parseIPv6TransportHeader(payload)
if !ok || transportProto != unix.IPPROTO_TCP {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
ipv6 := header.IPv6(payload)
srcAddr = M.SocksaddrFrom(ipv6.SourceAddr(), 0)
dstAddr = M.SocksaddrFrom(ipv6.DestinationAddr(), 0)
tcpOffset = transportOffset
default:
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
if len(payload) < tcpOffset+header.TCPMinimumSize {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
tcp := header.TCP(payload[tcpOffset:])
srcAddr = M.SocksaddrFrom(srcAddr.Addr, tcp.SourcePort())
dstAddr = M.SocksaddrFrom(dstAddr.Addr, tcp.DestinationPort())
flags := tcp.Flags()
if !flags.Contains(header.TCPFlagSyn) || flags.Contains(header.TCPFlagAck) {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
return 0
}
_, pErr := h.handler.PrepareConnection(N.NetworkTCP, srcAddr, dstAddr, nil, 0)
_, pErr := h.handler.PrepareConnection(packet.network, packet.source, packet.destination, nil, 0)
// Use NfRepeat for bypass/reset so the packet re-enters the chain
// from the beginning, allowing mark-checking rules to save the mark
@ -221,7 +275,11 @@ func (h *nfqueueHandler) handlePacket(attr nfqueue.Attribute) int {
case errors.Is(pErr, ErrBypass):
h.setVerdict(packetID, nfqueue.NfRepeat, h.outputMark)
case errors.Is(pErr, ErrReset):
h.setVerdict(packetID, nfqueue.NfRepeat, h.resetMark)
if packet.protocol == uint8(unix.IPPROTO_TCP) {
h.setVerdict(packetID, nfqueue.NfRepeat, h.resetMark)
} else {
h.setVerdict(packetID, nfqueue.NfAccept, 0)
}
case errors.Is(pErr, ErrDrop):
h.setVerdict(packetID, nfqueue.NfDrop, 0)
default: