sing-tun-lx/flow_parse.go
2026-07-27 23:11:48 +08:00

256 lines
7.7 KiB
Go

package tun
import (
"encoding/binary"
"net/netip"
"github.com/sagernet/sing-tun/gtcpip/header"
)
type flowKey struct {
protocol uint8
source netip.AddrPort
destination netip.AddrPort
}
func (k flowKey) reversed() flowKey {
return flowKey{protocol: k.protocol, source: k.destination, destination: k.source}
}
type forwardPacket struct {
ipVersion uint8
protocol uint8
network header.Network
transport []byte
source netip.AddrPort
destination netip.AddrPort
tcpFlags header.TCPFlags
icmpType uint8
fragment bool
hasFlow bool
}
func (p *forwardPacket) flowKey() flowKey {
return flowKey{protocol: p.protocol, source: p.source, destination: p.destination}
}
func (p *forwardPacket) isTCPSyn() bool {
return p.protocol == uint8(header.TCPProtocolNumber) && p.tcpFlags&header.TCPFlagSyn != 0
}
func parseForwardPacket(packet []byte) (forwardPacket, bool) {
switch header.IPVersion(packet) {
case header.IPv4Version:
ipHdr := header.IPv4(packet)
if !ipHdr.IsValid(len(packet)) {
return forwardPacket{}, false
}
parsed := forwardPacket{
ipVersion: 4,
protocol: uint8(ipHdr.TransportProtocol()),
network: ipHdr,
source: netip.AddrPortFrom(ipHdr.SourceAddr(), 0),
destination: netip.AddrPortFrom(ipHdr.DestinationAddr(), 0),
}
if ipHdr.More() || ipHdr.FragmentOffset() != 0 {
parsed.fragment = true
return parsed, true
}
parsed.parseTransport(ipHdr.Payload())
return parsed, true
case header.IPv6Version:
ipHdr := header.IPv6(packet)
if !ipHdr.IsValid(len(packet)) {
return forwardPacket{}, false
}
protocol, payload, fragment, transportPresent := skipIPv6ExtensionHeaders(uint8(ipHdr.TransportProtocol()), ipHdr.Payload())
parsed := forwardPacket{
ipVersion: 6,
protocol: protocol,
network: ipHdr,
source: netip.AddrPortFrom(ipHdr.SourceAddr(), 0),
destination: netip.AddrPortFrom(ipHdr.DestinationAddr(), 0),
fragment: fragment,
}
if fragment || !transportPresent {
return parsed, true
}
parsed.parseTransport(payload)
return parsed, true
default:
return forwardPacket{}, false
}
}
func skipIPv6ExtensionHeaders(protocol uint8, payload []byte) (uint8, []byte, bool, bool) {
for {
switch header.IPv6ExtensionHeaderIdentifier(protocol) {
case header.IPv6HopByHopOptionsExtHdrIdentifier, header.IPv6RoutingExtHdrIdentifier, header.IPv6DestinationOptionsExtHdrIdentifier:
if len(payload) < 2 {
return protocol, payload, false, false
}
extensionLength := (int(payload[1]) + 1) * 8
if len(payload) < extensionLength {
return protocol, payload, false, false
}
protocol = payload[0]
payload = payload[extensionLength:]
case header.IPv6FragmentExtHdrIdentifier:
return protocol, payload, true, false
default:
return protocol, payload, false, true
}
}
}
func (p *forwardPacket) parseTransport(payload []byte) {
p.transport = payload
switch p.protocol {
case uint8(header.TCPProtocolNumber):
if len(payload) < header.TCPMinimumSize {
return
}
tcpHdr := header.TCP(payload)
p.source = netip.AddrPortFrom(p.source.Addr(), tcpHdr.SourcePort())
p.destination = netip.AddrPortFrom(p.destination.Addr(), tcpHdr.DestinationPort())
p.tcpFlags = tcpHdr.Flags()
p.hasFlow = true
case uint8(header.UDPProtocolNumber):
if len(payload) < header.UDPMinimumSize {
return
}
udpHdr := header.UDP(payload)
p.source = netip.AddrPortFrom(p.source.Addr(), udpHdr.SourcePort())
p.destination = netip.AddrPortFrom(p.destination.Addr(), udpHdr.DestinationPort())
p.hasFlow = true
case uint8(header.ICMPv4ProtocolNumber):
if len(payload) < header.ICMPv4MinimumSize {
return
}
icmpHdr := header.ICMPv4(payload)
p.icmpType = uint8(icmpHdr.Type())
switch icmpHdr.Type() {
case header.ICMPv4Echo, header.ICMPv4EchoReply:
identifier := icmpHdr.Ident()
p.source = netip.AddrPortFrom(p.source.Addr(), identifier)
p.destination = netip.AddrPortFrom(p.destination.Addr(), identifier)
p.hasFlow = true
}
case uint8(header.ICMPv6ProtocolNumber):
if len(payload) < header.ICMPv6MinimumSize {
return
}
icmpHdr := header.ICMPv6(payload)
p.icmpType = uint8(icmpHdr.Type())
switch icmpHdr.Type() {
case header.ICMPv6EchoRequest, header.ICMPv6EchoReply:
identifier := icmpHdr.Ident()
p.source = netip.AddrPortFrom(p.source.Addr(), identifier)
p.destination = netip.AddrPortFrom(p.destination.Addr(), identifier)
p.hasFlow = true
}
}
}
func (p *forwardPacket) isICMPError() bool {
switch p.protocol {
case uint8(header.ICMPv4ProtocolNumber):
switch header.ICMPv4Type(p.icmpType) {
case header.ICMPv4DstUnreachable, header.ICMPv4SrcQuench, header.ICMPv4Redirect, header.ICMPv4TimeExceeded, header.ICMPv4ParamProblem:
return true
}
return false
case uint8(header.ICMPv6ProtocolNumber):
return header.ICMPv6Type(p.icmpType).IsErrorType()
default:
return false
}
}
func (p *forwardPacket) icmpErrorInner() ([]byte, bool) {
var innerOffset int
switch p.protocol {
case uint8(header.ICMPv4ProtocolNumber):
innerOffset = header.ICMPv4MinimumSize
case uint8(header.ICMPv6ProtocolNumber):
innerOffset = header.ICMPv6ErrorHeaderSize
default:
return nil, false
}
if len(p.transport) <= innerOffset {
return nil, false
}
return p.transport[innerOffset:], true
}
type embeddedPacket struct {
network header.Network
payload []byte
protocol uint8
source netip.AddrPort
destination netip.AddrPort
}
func (p *embeddedPacket) flowKey() flowKey {
return flowKey{protocol: p.protocol, source: p.source, destination: p.destination}
}
func parseEmbedded(inner []byte) (embeddedPacket, bool) {
switch header.IPVersion(inner) {
case header.IPv4Version:
if len(inner) < header.IPv4MinimumSize {
return embeddedPacket{}, false
}
ipHdr := header.IPv4(inner)
headerLength := int(ipHdr.HeaderLength())
if headerLength < header.IPv4MinimumSize || headerLength > len(inner) {
return embeddedPacket{}, false
}
return parseEmbeddedTransport(ipHdr, inner[headerLength:], uint8(ipHdr.TransportProtocol()), ipHdr.SourceAddr(), ipHdr.DestinationAddr())
case header.IPv6Version:
if len(inner) < header.IPv6MinimumSize {
return embeddedPacket{}, false
}
ipHdr := header.IPv6(inner)
protocol, payload, _, transportPresent := skipIPv6ExtensionHeaders(uint8(ipHdr.TransportProtocol()), inner[header.IPv6MinimumSize:])
if !transportPresent {
return embeddedPacket{}, false
}
return parseEmbeddedTransport(ipHdr, payload, protocol, ipHdr.SourceAddr(), ipHdr.DestinationAddr())
default:
return embeddedPacket{}, false
}
}
func parseEmbeddedTransport(network header.Network, payload []byte, protocol uint8, source, destination netip.Addr) (embeddedPacket, bool) {
embedded := embeddedPacket{
network: network,
payload: payload,
protocol: protocol,
}
switch protocol {
case uint8(header.TCPProtocolNumber), uint8(header.UDPProtocolNumber):
if len(payload) < 4 {
return embeddedPacket{}, false
}
embedded.source = netip.AddrPortFrom(source, binary.BigEndian.Uint16(payload[0:]))
embedded.destination = netip.AddrPortFrom(destination, binary.BigEndian.Uint16(payload[2:]))
case uint8(header.ICMPv4ProtocolNumber):
if len(payload) < header.ICMPv4MinimumSize {
return embeddedPacket{}, false
}
identifier := header.ICMPv4(payload).Ident()
embedded.source = netip.AddrPortFrom(source, identifier)
embedded.destination = netip.AddrPortFrom(destination, identifier)
case uint8(header.ICMPv6ProtocolNumber):
if len(payload) < header.ICMPv6MinimumSize {
return embeddedPacket{}, false
}
identifier := header.ICMPv6(payload).Ident()
embedded.source = netip.AddrPortFrom(source, identifier)
embedded.destination = netip.AddrPortFrom(destination, identifier)
default:
return embeddedPacket{}, false
}
return embedded, true
}