Add stateless DNS hijack

This commit is contained in:
世界 2026-07-19 13:02:55 +08:00
parent 1ba7d79118
commit 79084fa798
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
5 changed files with 113 additions and 6 deletions

View file

@ -21,6 +21,7 @@ const (
ActionReject
ActionDrop
ActionBypass
ActionHijackDNS
)
type FlowTracker interface {

View file

@ -291,6 +291,13 @@ func (d *ForwardDispatcher) judgeAndInstall(key flowKey, packet *forwardPacket,
case ActionDrop:
d.installSimple(key, ActionDrop, packet.protocol, now)
return true
case ActionHijackDNS:
if packet.protocol == uint8(header.UDPProtocolNumber) {
d.hijackDNSPacket(packet)
return true
}
d.installSimple(key, ActionAccept, packet.protocol, now)
return false
default:
d.installSimple(key, ActionAccept, packet.protocol, now)
return false

89
flow_dns.go Normal file
View file

@ -0,0 +1,89 @@
package tun
import (
"net/netip"
"github.com/sagernet/sing-tun/gtcpip/checksum"
"github.com/sagernet/sing-tun/gtcpip/header"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func (d *ForwardDispatcher) hijackDNSPacket(packet *forwardPacket) {
writer := &dnsResponseWriter{
writeback: d.writeback,
source: packet.source,
}
d.handler.NewDNSPacket(header.UDP(packet.transport).Payload(), M.SocksaddrFromNetIP(packet.source), M.SocksaddrFromNetIP(packet.destination), writer)
}
var _ N.PacketWriter = (*dnsResponseWriter)(nil)
type dnsResponseWriter struct {
writeback ForwardWriteback
source netip.AddrPort
}
func (w *dnsResponseWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
if !destination.IsIP() {
return E.New("invalid destination: ", destination)
}
sourceAddr := w.source.Addr().Unmap()
destinationAddr := destination.Addr.Unmap()
headroom := w.writeback.ReturnHeadroom()
udpLen := header.UDPMinimumSize + buffer.Len()
var (
packet []byte
udpHdr header.UDP
ipHdr header.Network
)
if sourceAddr.Is4() {
if !destinationAddr.Is4() {
return E.New("send IPv6 packet to IPv4 connection")
}
size := header.IPv4MinimumSize + udpLen
packet = make([]byte, headroom+size)
inet4Hdr := header.IPv4(packet[headroom:])
inet4Hdr.Encode(&header.IPv4Fields{
TotalLength: uint16(size),
TTL: synthesizedTTL,
Protocol: uint8(header.UDPProtocolNumber),
SrcAddr: destinationAddr,
DstAddr: sourceAddr,
})
udpHdr = header.UDP(inet4Hdr.Payload())
ipHdr = inet4Hdr
} else {
if destinationAddr.Is4() {
destinationAddr = netip.AddrFrom16(destinationAddr.As16())
}
size := header.IPv6MinimumSize + udpLen
packet = make([]byte, headroom+size)
inet6Hdr := header.IPv6(packet[headroom:])
inet6Hdr.Encode(&header.IPv6Fields{
PayloadLength: uint16(udpLen),
TransportProtocol: header.UDPProtocolNumber,
HopLimit: synthesizedTTL,
SrcAddr: destinationAddr,
DstAddr: sourceAddr,
})
udpHdr = header.UDP(inet6Hdr.Payload())
ipHdr = inet6Hdr
}
udpHdr.Encode(&header.UDPFields{
SrcPort: destination.Port,
DstPort: w.source.Port(),
Length: uint16(udpLen),
})
copy(udpHdr.Payload(), buffer.Bytes())
udpHdr.SetChecksum(^checksum.Checksum(udpHdr.Payload(), udpHdr.CalculateChecksum(
header.PseudoHeaderChecksum(header.UDPProtocolNumber, ipHdr.SourceAddressSlice(), ipHdr.DestinationAddressSlice(), uint16(udpLen)),
)))
if inet4Hdr, isInet4 := ipHdr.(header.IPv4); isInet4 {
inet4Hdr.SetChecksum(^inet4Hdr.CalculateChecksum())
}
return w.writeback.WriteReturnPackets([][]byte{packet})
}

View file

@ -71,18 +71,26 @@ func (f *UDPForwarder) PreparePacketConnection(source M.Socksaddr, destination M
firstPacket = append(firstPacket[:len(firstPacket):len(firstPacket)], view.AsSlice()...)
}
})
var sourceNetwork tcpip.NetworkProtocolNumber
if source.Addr.Is4() {
sourceNetwork = header.IPv4ProtocolNumber
} else {
sourceNetwork = header.IPv6ProtocolNumber
}
switch f.handler.JudgeFlow(uint8(header.UDPProtocolNumber), source.AddrPort(), destination.AddrPort(), firstPacket).Action {
case ActionReject:
gWriteUnreachable(f.stack, userData.(*stack.PacketBuffer))
return false, nil, nil, nil
case ActionDrop:
return false, nil, nil, nil
}
var sourceNetwork tcpip.NetworkProtocolNumber
if source.Addr.Is4() {
sourceNetwork = header.IPv4ProtocolNumber
} else {
sourceNetwork = header.IPv6ProtocolNumber
case ActionHijackDNS:
f.handler.NewDNSPacket(firstPacket, source, destination, &UDPBackWriter{
stack: f.stack,
source: AddressFromAddr(source.Addr),
sourcePort: source.Port,
sourceNetwork: sourceNetwork,
})
return false, nil, nil, nil
}
writer := &UDPBackWriter{
stack: f.stack,

2
tun.go
View file

@ -14,12 +14,14 @@ import (
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ranges"
)
type Handler interface {
JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) FlowVerdict
NewDNSPacket(payload []byte, source M.Socksaddr, destination M.Socksaddr, writer N.PacketWriter)
N.TCPConnectionHandlerEx
N.UDPConnectionHandlerEx
}