From 3ec23c1fc52bf65e5f9cd0b5b94ccdec139c8a84 Mon Sep 17 00:00:00 2001 From: sduoduo233 <85996970+sduoduo233@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:21:50 +0800 Subject: [PATCH] feat: Add DNS outbound to hijack DNS packets (#1078) --- adapter/outbound/dns.go | 143 ++++++++++++++++++++++++++++++++++++++++ adapter/parser.go | 7 ++ constant/adapters.go | 1 + 3 files changed, 151 insertions(+) create mode 100644 adapter/outbound/dns.go diff --git a/adapter/outbound/dns.go b/adapter/outbound/dns.go new file mode 100644 index 00000000..14eaf581 --- /dev/null +++ b/adapter/outbound/dns.go @@ -0,0 +1,143 @@ +package outbound + +import ( + "context" + "fmt" + "net" + "net/netip" + "time" + + "github.com/metacubex/mihomo/component/dialer" + "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/log" + D "github.com/miekg/dns" +) + +type Dns struct { + *Base +} + +type DnsOption struct { + BasicOption + Name string `proxy:"name"` +} + +// DialContext implements C.ProxyAdapter +func (d *Dns) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + return nil, fmt.Errorf("dns outbound does not support tcp") +} + +// ListenPacketContext implements C.ProxyAdapter +func (d *Dns) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + log.Debugln("[DNS] hijack udp:%s from %s", metadata.RemoteAddress(), metadata.SourceAddrPort()) + + return newPacketConn(&dnsPacketConn{ + response: make(chan []byte), + doneReading: make(chan int), + }, d), nil +} + +// dnsPacketConn implements net.PacketConn +type dnsPacketConn struct { + response chan []byte + writeTo net.Addr + doneReading chan int +} + +func (d *dnsPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + buf := <-d.response + + log.Debugln("[DNS] hijack ReadFrom, len %d", len(buf)) + + if buf != nil { + n := copy(p, buf) + return n, d.writeTo, nil + } + + return 0, nil, fmt.Errorf("read from closed dns packet conn") +} + +func (d *dnsPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + log.Debugln("[DNS] hijack WriteTo %s, len %d", addr.String(), len(p)) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + buf, err := RelayDnsPacket(ctx, p, make([]byte, 4096)) + if err != nil { + log.Warnln("[DNS] dns hijack: relay dns packet: %s", err) + return 0, err + } + + d.writeTo = addr + d.response <- buf + + return len(p), nil +} + +func (d *dnsPacketConn) Close() error { + close(d.response) + return nil +} + +func (*dnsPacketConn) LocalAddr() net.Addr { + return net.UDPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:53")) +} + +func (*dnsPacketConn) SetDeadline(t time.Time) error { + return nil +} + +func (*dnsPacketConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (*dnsPacketConn) SetWriteDeadline(t time.Time) error { + return nil +} + +func NewDnsWithOption(option DnsOption) *Dns { + return &Dns{ + Base: &Base{ + name: option.Name, + tp: C.Direct, + udp: true, + tfo: option.TFO, + mpTcp: option.MPTCP, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), + }, + } +} + +// copied from listener/sing_mux/dns.go +func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, error) { + msg := &D.Msg{} + if err := msg.Unpack(payload); err != nil { + return nil, err + } + + r, err := resolver.ServeMsg(ctx, msg) + if err != nil { + m := new(D.Msg) + m.SetRcode(msg, D.RcodeServerFailure) + return m.PackBuffer(target) + } + + r.SetRcode(msg, r.Rcode) + r.Compress = true + return r.PackBuffer(target) +} + +func NewDns() *Dns { + return &Dns{ + Base: &Base{ + name: "DNS", + tp: C.Dns, + udp: true, + prefer: C.DualStack, + }, + } +} diff --git a/adapter/parser.go b/adapter/parser.go index 1d363c1f..fa94708d 100644 --- a/adapter/parser.go +++ b/adapter/parser.go @@ -120,6 +120,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { break } proxy = outbound.NewDirectWithOption(*directOption) + case "dns": + dnsOptions := &outbound.DnsOption{} + err = decoder.Decode(mapping, dnsOptions) + if err != nil { + break + } + proxy = outbound.NewDnsWithOption(*dnsOptions) case "reject": rejectOption := &outbound.RejectOption{} err = decoder.Decode(mapping, rejectOption) diff --git a/constant/adapters.go b/constant/adapters.go index 83c8223a..09c437f5 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -21,6 +21,7 @@ const ( RejectDrop Compatible Pass + Dns Relay Selector