Compare commits

...

23 Commits

Author SHA1 Message Date
wwqgtxx
c0f452b540 chore: more unmap for 4in6 address 2025-05-29 10:14:06 +08:00
wwqgtxx
6c9abe16cc fix: vmess listener error 2025-05-28 21:33:44 +08:00
wwqgtxx
213d80c1e2 fix: quic sniffer should consider skipDomain 2025-05-28 10:06:53 +08:00
wwqgtxx
1db89da122 fix: quic sniffer should not replace domain when no valid host is read 2025-05-28 09:22:28 +08:00
wwqgtxx
689c58f661 chore: clear dstIP when overrideDest in sniffer 2025-05-27 22:47:21 +08:00
wwqgtxx
33590c4066 fix: destination should unmap before find interface 2025-05-27 18:26:35 +08:00
wwqgtxx
60ae9dce56 chore: recover log leval for preHandleMetadata 2025-05-27 18:10:44 +08:00
wwqgtxx
4741ac6702 fix: in-port not work with shadowsocks listener 2025-05-27 16:32:42 +08:00
wwqgtxx
ef3d7e4dd7 chore: remove unneeded dns resolve when proxydialer dial udp 2025-05-27 15:04:01 +08:00
wwqgtxx
a1c7881229 chore: rebuild udp dns resolve
The DNS resolution of the overall UDP part has been delayed to the connection initiation stage. During the rule matching process, it will only be triggered when the IP rule without no-resolve is matched.

For direct and wireguard outbound, the same logic as the TCP part will be followed, that is, when direct-nameserver (or DNS configured by wireguard) exists, the result of the matching process will be discarded and the domain name will be re-resolved. This re-resolution logic is only effective for fakeip.

For reject and DNS outbound, no resolution is required.

For other outbound, resolution will still be performed when the connection is initiated, and the domain name will not be sent directly to the remote server at present.
2025-05-27 10:45:26 +08:00
wwqgtxx
12e3952b74 chore: code cleanup 2025-05-26 12:33:24 +08:00
wwqgtxx
88419cbd12 chore: better parse remote dst 2025-05-26 01:12:35 +08:00
wwqgtxx
4ed830330e chore: remove confused code 2025-05-25 22:22:23 +08:00
wwqgtxx
3ed6ff9402 chore: export pipeDeadline 2025-05-25 22:07:29 +08:00
wwqgtxx
34de62d21d chore: better get localAddr 2025-05-24 23:19:38 +08:00
wwqgtxx
d2e255f257 fix: some error in tun 2025-05-24 22:23:10 +08:00
wwqgtxx
a0c46bb4b7 chore: remove the redundant layer of udpnat in sing-tun to reduce resource usage when processing udp 2025-05-24 15:57:49 +08:00
wwqgtxx
9e3bf14b1a chore: handle two interfaces have the same prefix but different address 2025-05-24 11:32:36 +08:00
wwqgtxx
28c387a9b6 chore: restore break change in sing-tun 2025-05-23 20:19:18 +08:00
wwqgtxx
15eda703b4 fix: hysteria2 panic 2025-05-23 20:12:38 +08:00
wwqgtxx
b1d12a15db chore: proxy's ech should fetch from proxy-nameserver 2025-05-22 17:42:40 +08:00
wwqgtxx
5a21bf3642 fix: listener close panic 2025-05-22 17:01:24 +08:00
wwqgtxx
199fb8fd5d chore: update quic-go to 0.52.0 2025-05-22 10:28:10 +08:00
53 changed files with 592 additions and 460 deletions

View File

@@ -7,9 +7,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/netip"
"net/url" "net/url"
"strconv"
"strings" "strings"
"time" "time"
@@ -316,15 +314,7 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
return return
} }
} }
uintPort, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return
}
addr = C.Metadata{ err = addr.SetRemoteAddress(net.JoinHostPort(u.Hostname(), port))
Host: u.Hostname(),
DstIP: netip.Addr{},
DstPort: uint16(uintPort),
}
return return
} }

View File

@@ -4,7 +4,6 @@ import (
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
"strconv"
"strings" "strings"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
@@ -41,23 +40,8 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
// trim FQDN (#737) // trim FQDN (#737)
host = strings.TrimRight(host, ".") host = strings.TrimRight(host, ".")
var uint16Port uint16 metadata := &C.Metadata{}
if port, err := strconv.ParseUint(port, 10, 16); err == nil { _ = metadata.SetRemoteAddress(net.JoinHostPort(host, port))
uint16Port = uint16(port)
}
metadata := &C.Metadata{
NetWork: C.TCP,
Host: host,
DstIP: netip.Addr{},
DstPort: uint16Port,
}
ip, err := netip.ParseAddr(host)
if err == nil {
metadata.DstIP = ip
}
return metadata return metadata
} }

View File

@@ -2,7 +2,6 @@ package outbound
import ( import (
"context" "context"
"errors"
"net" "net"
"strconv" "strconv"
"time" "time"
@@ -10,7 +9,6 @@ import (
CN "github.com/metacubex/mihomo/common/net" CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls" "github.com/metacubex/mihomo/transport/anytls"
"github.com/metacubex/mihomo/transport/vmess" "github.com/metacubex/mihomo/transport/vmess"
@@ -53,6 +51,10 @@ func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con
} }
func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
// create tcp // create tcp
c, err := t.client.CreateProxy(ctx, uot.RequestDestination(2)) c, err := t.client.CreateProxy(ctx, uot.RequestDestination(2))
if err != nil { if err != nil {
@@ -60,13 +62,6 @@ func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
} }
// create uot on tcp // create uot on tcp
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
destination := M.SocksaddrFromNet(metadata.UDPAddr()) destination := M.SocksaddrFromNet(metadata.UDPAddr())
return newPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil return newPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil
} }

View File

@@ -3,15 +3,16 @@ package outbound
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"net" "net"
"runtime" "runtime"
"strings"
"sync" "sync"
"syscall" "syscall"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
@@ -19,6 +20,7 @@ import (
type ProxyAdapter interface { type ProxyAdapter interface {
C.ProxyAdapter C.ProxyAdapter
DialOptions() []dialer.Option DialOptions() []dialer.Option
ResolveUDP(ctx context.Context, metadata *C.Metadata) error
} }
type Base struct { type Base struct {
@@ -160,6 +162,17 @@ func (b *Base) DialOptions() (opts []dialer.Option) {
return opts return opts
} }
func (b *Base) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return fmt.Errorf("can't resolve ip: %w", err)
}
metadata.DstIP = ip
}
return nil
}
func (b *Base) Close() error { func (b *Base) Close() error {
return nil return nil
} }
@@ -203,12 +216,21 @@ func NewBase(opt BaseOption) *Base {
type conn struct { type conn struct {
N.ExtendedConn N.ExtendedConn
chain C.Chain chain C.Chain
actualRemoteDestination string adapterAddr string
} }
func (c *conn) RemoteDestination() string { func (c *conn) RemoteDestination() string {
return c.actualRemoteDestination if remoteAddr := c.RemoteAddr(); remoteAddr != nil {
m := C.Metadata{}
if err := m.SetRemoteAddr(remoteAddr); err != nil {
if m.Valid() {
return m.String()
}
}
}
host, _, _ := net.SplitHostPort(c.adapterAddr)
return host
} }
// Chains implements C.Connection // Chains implements C.Connection
@@ -241,19 +263,25 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn
c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly
} }
return &conn{N.NewExtendedConn(c), []string{a.Name()}, parseRemoteDestination(a.Addr())} return &conn{N.NewExtendedConn(c), []string{a.Name()}, a.Addr()}
} }
type packetConn struct { type packetConn struct {
N.EnhancePacketConn N.EnhancePacketConn
chain C.Chain chain C.Chain
adapterName string adapterName string
connID string connID string
actualRemoteDestination string adapterAddr string
resolveUDP func(ctx context.Context, metadata *C.Metadata) error
}
func (c *packetConn) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
return c.resolveUDP(ctx, metadata)
} }
func (c *packetConn) RemoteDestination() string { func (c *packetConn) RemoteDestination() string {
return c.actualRemoteDestination host, _, _ := net.SplitHostPort(c.adapterAddr)
return host
} }
// Chains implements C.Connection // Chains implements C.Connection
@@ -287,24 +315,12 @@ func (c *packetConn) AddRef(ref any) {
c.EnhancePacketConn = N.NewRefPacketConn(c.EnhancePacketConn, ref) // add ref for autoCloseProxyAdapter c.EnhancePacketConn = N.NewRefPacketConn(c.EnhancePacketConn, ref) // add ref for autoCloseProxyAdapter
} }
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { func newPacketConn(pc net.PacketConn, a ProxyAdapter) C.PacketConn {
epc := N.NewEnhancePacketConn(pc) epc := N.NewEnhancePacketConn(pc)
if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn
epc = N.NewDeadlineEnhancePacketConn(epc) // most conn from outbound can't handle readDeadline correctly epc = N.NewDeadlineEnhancePacketConn(epc) // most conn from outbound can't handle readDeadline correctly
} }
return &packetConn{epc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), parseRemoteDestination(a.Addr())} return &packetConn{epc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), a.Addr(), a.ResolveUDP}
}
func parseRemoteDestination(addr string) string {
if dst, _, err := net.SplitHostPort(addr); err == nil {
return dst
} else {
if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") {
return dst
} else {
return ""
}
}
} }
type AddRef interface { type AddRef interface {

View File

@@ -2,7 +2,8 @@ package outbound
import ( import (
"context" "context"
"errors" "fmt"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/loopback" "github.com/metacubex/mihomo/component/loopback"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
@@ -38,13 +39,8 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
if err := d.loopBack.CheckPacketConn(metadata); err != nil { if err := d.loopBack.CheckPacketConn(metadata); err != nil {
return nil, err return nil, err
} }
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr if err := d.ResolveUDP(ctx, metadata); err != nil {
if !metadata.Resolved() { return nil, err
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DirectHostResolver)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
} }
pc, err := dialer.NewDialer(d.DialOptions()...).ListenPacket(ctx, "udp", "", metadata.AddrPort()) pc, err := dialer.NewDialer(d.DialOptions()...).ListenPacket(ctx, "udp", "", metadata.AddrPort())
if err != nil { if err != nil {
@@ -53,6 +49,17 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
return d.loopBack.NewPacketConn(newPacketConn(pc, d)), nil return d.loopBack.NewPacketConn(newPacketConn(pc, d)), nil
} }
func (d *Direct) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
if (!metadata.Resolved() || resolver.DirectHostResolver != resolver.DefaultResolver) && metadata.Host != "" {
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DirectHostResolver)
if err != nil {
return fmt.Errorf("can't resolve ip: %w", err)
}
metadata.DstIP = ip
}
return nil
}
func (d *Direct) IsL3Protocol(metadata *C.Metadata) bool { func (d *Direct) IsL3Protocol(metadata *C.Metadata) bool {
return true // tell DNSDialer don't send domain to DialContext, avoid lookback to DefaultResolver return true // tell DNSDialer don't send domain to DialContext, avoid lookback to DefaultResolver
} }

View File

@@ -3,6 +3,7 @@ package outbound
import ( import (
"context" "context"
"net" "net"
"net/netip"
"time" "time"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
@@ -31,6 +32,9 @@ func (d *Dns) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, er
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (d *Dns) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { func (d *Dns) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
log.Debugln("[DNS] hijack udp:%s from %s", metadata.RemoteAddress(), metadata.SourceAddrPort()) log.Debugln("[DNS] hijack udp:%s from %s", metadata.RemoteAddress(), metadata.SourceAddrPort())
if err := d.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@@ -41,6 +45,13 @@ func (d *Dns) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.
}, d), nil }, d), nil
} }
func (d *Dns) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
if !metadata.Resolved() {
metadata.DstIP = netip.AddrFrom4([4]byte{127, 0, 0, 2})
}
return nil
}
type dnsPacket struct { type dnsPacket struct {
data []byte data []byte
put func() put func()

View File

@@ -1,10 +1,12 @@
package outbound package outbound
import ( import (
"context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/resolver"
) )
type ECHOptions struct { type ECHOptions struct {
@@ -22,7 +24,13 @@ func (o ECHOptions) Parse() (*ech.Config, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("base64 decode ech config string failed: %v", err) return nil, fmt.Errorf("base64 decode ech config string failed: %v", err)
} }
echConfig.EncryptedClientHelloConfigList = list echConfig.GetEncryptedClientHelloConfigList = func(ctx context.Context, serverName string) ([]byte, error) {
return list, nil
}
} else {
echConfig.GetEncryptedClientHelloConfigList = func(ctx context.Context, serverName string) ([]byte, error) {
return resolver.ResolveECHWithResolver(ctx, serverName, resolver.ProxyServerHostResolver)
}
} }
return echConfig, nil return echConfig, nil
} }

View File

@@ -60,6 +60,9 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
} }
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
if err := h.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
udpConn, err := h.client.DialUDP(h.genHdc(ctx)) udpConn, err := h.client.DialUDP(h.genHdc(ctx))
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -77,6 +77,9 @@ func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.
} }
func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = h.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
pc, err := h.client.ListenPacket(ctx) pc, err := h.client.ListenPacket(ctx)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -54,6 +54,9 @@ func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = m.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
if err := m.ensureClientIsRunning(); err != nil { if err := m.ensureClientIsRunning(); err != nil {
return nil, err return nil, err
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"io" "io"
"net" "net"
"net/netip"
"time" "time"
"github.com/metacubex/mihomo/common/buf" "github.com/metacubex/mihomo/common/buf"
@@ -29,9 +30,19 @@ func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) {
if err := r.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
return newPacketConn(&nopPacketConn{}, r), nil return newPacketConn(&nopPacketConn{}, r), nil
} }
func (r *Reject) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
if !metadata.Resolved() {
metadata.DstIP = netip.IPv4Unspecified()
}
return nil
}
func NewRejectWithOption(option RejectOption) *Reject { func NewRejectWithOption(option RejectOption) *Reject {
return &Reject{ return &Reject{
Base: &Base{ Base: &Base{

View File

@@ -2,7 +2,6 @@ package outbound
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@@ -11,7 +10,6 @@ import (
"github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
gost "github.com/metacubex/mihomo/transport/gost-plugin" gost "github.com/metacubex/mihomo/transport/gost-plugin"
"github.com/metacubex/mihomo/transport/restls" "github.com/metacubex/mihomo/transport/restls"
@@ -202,6 +200,9 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
return nil, err return nil, err
} }
} }
if err = ss.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer) addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -230,15 +231,9 @@ func (ss *ShadowSocks) ProxyInfo() C.ProxyInfo {
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
if ss.option.UDPOverTCP { if ss.option.UDPOverTCP {
// ss uot use stream-oriented udp with a special address, so we need a net.UDPAddr if err = ss.ResolveUDP(ctx, metadata); err != nil {
if !metadata.Resolved() { return nil, err
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
} }
destination := M.SocksaddrFromNet(metadata.UDPAddr()) destination := M.SocksaddrFromNet(metadata.UDPAddr())
if ss.option.UDPOverTCPVersion == uot.LegacyVersion { if ss.option.UDPOverTCPVersion == uot.LegacyVersion {
return newPacketConn(N.NewThreadSafePacketConn(uot.NewConn(c, uot.Request{Destination: destination})), ss), nil return newPacketConn(N.NewThreadSafePacketConn(uot.NewConn(c, uot.Request{Destination: destination})), ss), nil

View File

@@ -105,6 +105,9 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
return nil, err return nil, err
} }
} }
if err = ssr.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
addr, err := resolveUDPAddr(ctx, "udp", ssr.addr, ssr.prefer) addr, err := resolveUDPAddr(ctx, "udp", ssr.addr, ssr.prefer)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -2,12 +2,10 @@ package outbound
import ( import (
"context" "context"
"errors"
CN "github.com/metacubex/mihomo/common/net" CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
@@ -53,16 +51,9 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
if s.onlyTcp { if s.onlyTcp {
return s.ProxyAdapter.ListenPacketContext(ctx, metadata) return s.ProxyAdapter.ListenPacketContext(ctx, metadata)
} }
if err = s.ProxyAdapter.ResolveUDP(ctx, metadata); err != nil {
// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr return nil, err
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
} }
pc, err := s.client.ListenPacket(ctx, M.SocksaddrFromNet(metadata.UDPAddr())) pc, err := s.client.ListenPacket(ctx, M.SocksaddrFromNet(metadata.UDPAddr()))
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -127,6 +127,9 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
return nil, err return nil, err
} }
} }
if err = s.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := dialer.DialContext(ctx, "tcp", s.addr) c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -109,6 +109,9 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
return nil, err return nil, err
} }
} }
if err = ss.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := cDialer.DialContext(ctx, "tcp", ss.addr) c, err := cDialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
err = fmt.Errorf("%s connect error: %w", ss.addr, err) err = fmt.Errorf("%s connect error: %w", ss.addr, err)

View File

@@ -219,6 +219,10 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
var c net.Conn var c net.Conn
// grpc transport // grpc transport
@@ -250,6 +254,9 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
return nil, err return nil, err
} }
} }
if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := dialer.DialContext(ctx, "tcp", t.addr) c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@@ -271,12 +278,6 @@ func (t *Trojan) SupportWithDialer() C.NetWork {
return C.ALLNet return C.ALLNet
} }
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err
}
// SupportUOT implements C.ProxyAdapter // SupportUOT implements C.ProxyAdapter
func (t *Trojan) SupportUOT() bool { func (t *Trojan) SupportUOT() bool {
return true return true

View File

@@ -3,7 +3,6 @@ package outbound
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"math" "math"
"net" "net"
@@ -14,7 +13,6 @@ import (
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/tuic" "github.com/metacubex/mihomo/transport/tuic"
@@ -91,6 +89,10 @@ func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
if t.option.UDPOverStream { if t.option.UDPOverStream {
uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion)) uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion))
uotMetadata := *metadata uotMetadata := *metadata
@@ -102,13 +104,6 @@ func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, meta
} }
// tuic uos use stream-oriented udp with a special address, so we need a net.UDPAddr // tuic uos use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
destination := M.SocksaddrFromNet(metadata.UDPAddr()) destination := M.SocksaddrFromNet(metadata.UDPAddr())
if t.option.UDPOverStreamVersion == uot.LegacyVersion { if t.option.UDPOverStreamVersion == uot.LegacyVersion {

View File

@@ -19,7 +19,6 @@ import (
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/gun" "github.com/metacubex/mihomo/transport/gun"
@@ -277,13 +276,8 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr if err = v.ResolveUDP(ctx, metadata); err != nil {
if !metadata.Resolved() { return nil, err
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
} }
var c net.Conn var c net.Conn
// gun transport // gun transport
@@ -315,13 +309,8 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
} }
} }
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr if err = v.ResolveUDP(ctx, metadata); err != nil {
if !metadata.Resolved() { return nil, err
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
} }
c, err := dialer.DialContext(ctx, "tcp", v.addr) c, err := dialer.DialContext(ctx, "tcp", v.addr)
@@ -347,13 +336,8 @@ func (v *Vless) SupportWithDialer() C.NetWork {
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr if err = v.ResolveUDP(ctx, metadata); err != nil {
if !metadata.Resolved() { return nil, err
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
} }
if v.option.XUDP { if v.option.XUDP {

View File

@@ -17,7 +17,6 @@ import (
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech" "github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/ntp" "github.com/metacubex/mihomo/ntp"
@@ -330,13 +329,8 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr if err = v.ResolveUDP(ctx, metadata); err != nil {
if !metadata.Resolved() { return nil, err
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
} }
var c net.Conn var c net.Conn
// gun transport // gun transport
@@ -367,13 +361,8 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
} }
} }
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr if err = v.ResolveUDP(ctx, metadata); err != nil {
if !metadata.Resolved() { return nil, err
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
} }
c, err := dialer.DialContext(ctx, "tcp", v.addr) c, err := dialer.DialContext(ctx, "tcp", v.addr)
@@ -413,13 +402,8 @@ func (v *Vmess) Close() error {
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr if err = v.ResolveUDP(ctx, metadata); err != nil {
if !metadata.Resolved() { return nil, err
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
} }
if pc, ok := c.(net.PacketConn); ok { if pc, ok := c.(net.PacketConn); ok {

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
@@ -520,16 +519,8 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
if err = w.init(ctx); err != nil { if err = w.init(ctx); err != nil {
return nil, err return nil, err
} }
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" { if err = w.ResolveUDP(ctx, metadata); err != nil {
r := resolver.DefaultResolver return nil, err
if w.resolver != nil {
r = w.resolver
}
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
} }
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap()) pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
if err != nil { if err != nil {
@@ -541,6 +532,21 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
return newPacketConn(pc, w), nil return newPacketConn(pc, w), nil
} }
func (w *WireGuard) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
r := resolver.DefaultResolver
if w.resolver != nil {
r = w.resolver
}
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return fmt.Errorf("can't resolve ip: %w", err)
}
metadata.DstIP = ip
}
return nil
}
// IsL3Protocol implements C.ProxyAdapter // IsL3Protocol implements C.ProxyAdapter
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool { func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
return true return true

View File

@@ -20,7 +20,7 @@ type connReadResult struct {
type Conn struct { type Conn struct {
network.ExtendedConn network.ExtendedConn
deadline atomic.TypedValue[time.Time] deadline atomic.TypedValue[time.Time]
pipeDeadline pipeDeadline pipeDeadline PipeDeadline
disablePipe atomic.Bool disablePipe atomic.Bool
inRead atomic.Bool inRead atomic.Bool
resultCh chan *connReadResult resultCh chan *connReadResult
@@ -34,7 +34,7 @@ func IsConn(conn any) bool {
func NewConn(conn net.Conn) *Conn { func NewConn(conn net.Conn) *Conn {
c := &Conn{ c := &Conn{
ExtendedConn: bufio.NewExtendedConn(conn), ExtendedConn: bufio.NewExtendedConn(conn),
pipeDeadline: makePipeDeadline(), pipeDeadline: MakePipeDeadline(),
resultCh: make(chan *connReadResult, 1), resultCh: make(chan *connReadResult, 1),
} }
c.resultCh <- nil c.resultCh <- nil
@@ -58,7 +58,7 @@ func (c *Conn) Read(p []byte) (n int, err error) {
c.resultCh <- nil c.resultCh <- nil
break break
} }
case <-c.pipeDeadline.wait(): case <-c.pipeDeadline.Wait():
return 0, os.ErrDeadlineExceeded return 0, os.ErrDeadlineExceeded
} }
@@ -104,7 +104,7 @@ func (c *Conn) ReadBuffer(buffer *buf.Buffer) (err error) {
c.resultCh <- nil c.resultCh <- nil
break break
} }
case <-c.pipeDeadline.wait(): case <-c.pipeDeadline.Wait():
return os.ErrDeadlineExceeded return os.ErrDeadlineExceeded
} }
@@ -130,7 +130,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
return c.ExtendedConn.SetReadDeadline(t) return c.ExtendedConn.SetReadDeadline(t)
} }
c.deadline.Store(t) c.deadline.Store(t)
c.pipeDeadline.set(t) c.pipeDeadline.Set(t)
return nil return nil
} }

View File

@@ -19,7 +19,7 @@ type readResult struct {
type NetPacketConn struct { type NetPacketConn struct {
net.PacketConn net.PacketConn
deadline atomic.TypedValue[time.Time] deadline atomic.TypedValue[time.Time]
pipeDeadline pipeDeadline pipeDeadline PipeDeadline
disablePipe atomic.Bool disablePipe atomic.Bool
inRead atomic.Bool inRead atomic.Bool
resultCh chan any resultCh chan any
@@ -28,7 +28,7 @@ type NetPacketConn struct {
func NewNetPacketConn(pc net.PacketConn) net.PacketConn { func NewNetPacketConn(pc net.PacketConn) net.PacketConn {
npc := &NetPacketConn{ npc := &NetPacketConn{
PacketConn: pc, PacketConn: pc,
pipeDeadline: makePipeDeadline(), pipeDeadline: MakePipeDeadline(),
resultCh: make(chan any, 1), resultCh: make(chan any, 1),
} }
npc.resultCh <- nil npc.resultCh <- nil
@@ -83,7 +83,7 @@ FOR:
c.resultCh <- nil c.resultCh <- nil
break FOR break FOR
} }
case <-c.pipeDeadline.wait(): case <-c.pipeDeadline.Wait():
return 0, nil, os.ErrDeadlineExceeded return 0, nil, os.ErrDeadlineExceeded
} }
} }
@@ -122,7 +122,7 @@ func (c *NetPacketConn) SetReadDeadline(t time.Time) error {
return c.PacketConn.SetReadDeadline(t) return c.PacketConn.SetReadDeadline(t)
} }
c.deadline.Store(t) c.deadline.Store(t)
c.pipeDeadline.set(t) c.pipeDeadline.Set(t)
return nil return nil
} }

View File

@@ -52,7 +52,7 @@ FOR:
c.netPacketConn.resultCh <- nil c.netPacketConn.resultCh <- nil
break FOR break FOR
} }
case <-c.netPacketConn.pipeDeadline.wait(): case <-c.netPacketConn.pipeDeadline.Wait():
return nil, nil, nil, os.ErrDeadlineExceeded return nil, nil, nil, os.ErrDeadlineExceeded
} }
} }

View File

@@ -69,7 +69,7 @@ FOR:
c.netPacketConn.resultCh <- nil c.netPacketConn.resultCh <- nil
break FOR break FOR
} }
case <-c.netPacketConn.pipeDeadline.wait(): case <-c.netPacketConn.pipeDeadline.Wait():
return M.Socksaddr{}, os.ErrDeadlineExceeded return M.Socksaddr{}, os.ErrDeadlineExceeded
} }
} }
@@ -146,7 +146,7 @@ FOR:
c.netPacketConn.resultCh <- nil c.netPacketConn.resultCh <- nil
break FOR break FOR
} }
case <-c.netPacketConn.pipeDeadline.wait(): case <-c.netPacketConn.pipeDeadline.Wait():
return nil, M.Socksaddr{}, os.ErrDeadlineExceeded return nil, M.Socksaddr{}, os.ErrDeadlineExceeded
} }
} }

View File

@@ -9,24 +9,24 @@ import (
"time" "time"
) )
// pipeDeadline is an abstraction for handling timeouts. // PipeDeadline is an abstraction for handling timeouts.
type pipeDeadline struct { type PipeDeadline struct {
mu sync.Mutex // Guards timer and cancel mu sync.Mutex // Guards timer and cancel
timer *time.Timer timer *time.Timer
cancel chan struct{} // Must be non-nil cancel chan struct{} // Must be non-nil
} }
func makePipeDeadline() pipeDeadline { func MakePipeDeadline() PipeDeadline {
return pipeDeadline{cancel: make(chan struct{})} return PipeDeadline{cancel: make(chan struct{})}
} }
// set sets the point in time when the deadline will time out. // Set sets the point in time when the deadline will time out.
// A timeout event is signaled by closing the channel returned by waiter. // A timeout event is signaled by closing the channel returned by waiter.
// Once a timeout has occurred, the deadline can be refreshed by specifying a // Once a timeout has occurred, the deadline can be refreshed by specifying a
// t value in the future. // t value in the future.
// //
// A zero value for t prevents timeout. // A zero value for t prevents timeout.
func (d *pipeDeadline) set(t time.Time) { func (d *PipeDeadline) Set(t time.Time) {
d.mu.Lock() d.mu.Lock()
defer d.mu.Unlock() defer d.mu.Unlock()
@@ -61,8 +61,8 @@ func (d *pipeDeadline) set(t time.Time) {
} }
} }
// wait returns a channel that is closed when the deadline is exceeded. // Wait returns a channel that is closed when the deadline is exceeded.
func (d *pipeDeadline) wait() chan struct{} { func (d *PipeDeadline) Wait() chan struct{} {
d.mu.Lock() d.mu.Lock()
defer d.mu.Unlock() defer d.mu.Unlock()
return d.cancel return d.cancel

View File

@@ -33,8 +33,8 @@ type pipe struct {
localDone chan struct{} localDone chan struct{}
remoteDone <-chan struct{} remoteDone <-chan struct{}
readDeadline pipeDeadline readDeadline PipeDeadline
writeDeadline pipeDeadline writeDeadline PipeDeadline
readWaitOptions N.ReadWaitOptions readWaitOptions N.ReadWaitOptions
} }
@@ -56,15 +56,15 @@ func Pipe() (net.Conn, net.Conn) {
rdRx: cb1, rdTx: cn1, rdRx: cb1, rdTx: cn1,
wrTx: cb2, wrRx: cn2, wrTx: cb2, wrRx: cn2,
localDone: done1, remoteDone: done2, localDone: done1, remoteDone: done2,
readDeadline: makePipeDeadline(), readDeadline: MakePipeDeadline(),
writeDeadline: makePipeDeadline(), writeDeadline: MakePipeDeadline(),
} }
p2 := &pipe{ p2 := &pipe{
rdRx: cb2, rdTx: cn2, rdRx: cb2, rdTx: cn2,
wrTx: cb1, wrRx: cn1, wrTx: cb1, wrRx: cn1,
localDone: done2, remoteDone: done1, localDone: done2, remoteDone: done1,
readDeadline: makePipeDeadline(), readDeadline: MakePipeDeadline(),
writeDeadline: makePipeDeadline(), writeDeadline: MakePipeDeadline(),
} }
return p1, p2 return p1, p2
} }
@@ -86,7 +86,7 @@ func (p *pipe) read(b []byte) (n int, err error) {
return 0, io.ErrClosedPipe return 0, io.ErrClosedPipe
case isClosedChan(p.remoteDone): case isClosedChan(p.remoteDone):
return 0, io.EOF return 0, io.EOF
case isClosedChan(p.readDeadline.wait()): case isClosedChan(p.readDeadline.Wait()):
return 0, os.ErrDeadlineExceeded return 0, os.ErrDeadlineExceeded
} }
@@ -99,7 +99,7 @@ func (p *pipe) read(b []byte) (n int, err error) {
return 0, io.ErrClosedPipe return 0, io.ErrClosedPipe
case <-p.remoteDone: case <-p.remoteDone:
return 0, io.EOF return 0, io.EOF
case <-p.readDeadline.wait(): case <-p.readDeadline.Wait():
return 0, os.ErrDeadlineExceeded return 0, os.ErrDeadlineExceeded
} }
} }
@@ -118,7 +118,7 @@ func (p *pipe) write(b []byte) (n int, err error) {
return 0, io.ErrClosedPipe return 0, io.ErrClosedPipe
case isClosedChan(p.remoteDone): case isClosedChan(p.remoteDone):
return 0, io.ErrClosedPipe return 0, io.ErrClosedPipe
case isClosedChan(p.writeDeadline.wait()): case isClosedChan(p.writeDeadline.Wait()):
return 0, os.ErrDeadlineExceeded return 0, os.ErrDeadlineExceeded
} }
@@ -134,7 +134,7 @@ func (p *pipe) write(b []byte) (n int, err error) {
return n, io.ErrClosedPipe return n, io.ErrClosedPipe
case <-p.remoteDone: case <-p.remoteDone:
return n, io.ErrClosedPipe return n, io.ErrClosedPipe
case <-p.writeDeadline.wait(): case <-p.writeDeadline.Wait():
return n, os.ErrDeadlineExceeded return n, os.ErrDeadlineExceeded
} }
} }
@@ -145,8 +145,8 @@ func (p *pipe) SetDeadline(t time.Time) error {
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
return io.ErrClosedPipe return io.ErrClosedPipe
} }
p.readDeadline.set(t) p.readDeadline.Set(t)
p.writeDeadline.set(t) p.writeDeadline.Set(t)
return nil return nil
} }
@@ -154,7 +154,7 @@ func (p *pipe) SetReadDeadline(t time.Time) error {
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
return io.ErrClosedPipe return io.ErrClosedPipe
} }
p.readDeadline.set(t) p.readDeadline.Set(t)
return nil return nil
} }
@@ -162,7 +162,7 @@ func (p *pipe) SetWriteDeadline(t time.Time) error {
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
return io.ErrClosedPipe return io.ErrClosedPipe
} }
p.writeDeadline.set(t) p.writeDeadline.Set(t)
return nil return nil
} }
@@ -192,7 +192,7 @@ func (p *pipe) waitReadBuffer() (buffer *buf.Buffer, err error) {
return nil, io.ErrClosedPipe return nil, io.ErrClosedPipe
case isClosedChan(p.remoteDone): case isClosedChan(p.remoteDone):
return nil, io.EOF return nil, io.EOF
case isClosedChan(p.readDeadline.wait()): case isClosedChan(p.readDeadline.Wait()):
return nil, os.ErrDeadlineExceeded return nil, os.ErrDeadlineExceeded
} }
select { select {
@@ -211,7 +211,7 @@ func (p *pipe) waitReadBuffer() (buffer *buf.Buffer, err error) {
return nil, io.ErrClosedPipe return nil, io.ErrClosedPipe
case <-p.remoteDone: case <-p.remoteDone:
return nil, io.EOF return nil, io.EOF
case <-p.readDeadline.wait(): case <-p.readDeadline.Wait():
return nil, os.ErrDeadlineExceeded return nil, os.ErrDeadlineExceeded
} }
} }

View File

@@ -34,8 +34,8 @@ func (l *handleContextListener) init() {
} }
} }
}() }()
if c, err := l.handle(l.ctx, c); err == nil { if conn, err := l.handle(l.ctx, c); err == nil {
l.conns <- c l.conns <- conn
} else { } else {
// handle failed, close the underlying connection. // handle failed, close the underlying connection.
_ = c.Close() _ = c.Close()

View File

@@ -73,7 +73,7 @@ func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.
} }
if opt.interfaceName == "" { if opt.interfaceName == "" {
if finder := DefaultInterfaceFinder.Load(); finder != nil { if finder := DefaultInterfaceFinder.Load(); finder != nil {
opt.interfaceName = finder.FindInterfaceName(rAddrPort.Addr()) opt.interfaceName = finder.FindInterfaceName(rAddrPort.Addr().Unmap())
} }
} }
if rAddrPort.Addr().Unmap().IsLoopback() { if rAddrPort.Addr().Unmap().IsLoopback() {

View File

@@ -4,24 +4,20 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
) )
type Config struct { type Config struct {
EncryptedClientHelloConfigList []byte GetEncryptedClientHelloConfigList func(ctx context.Context, serverName string) ([]byte, error)
} }
func (cfg *Config) ClientHandle(ctx context.Context, tlsConfig *tlsC.Config) (err error) { func (cfg *Config) ClientHandle(ctx context.Context, tlsConfig *tlsC.Config) (err error) {
if cfg == nil { if cfg == nil {
return nil return nil
} }
echConfigList := cfg.EncryptedClientHelloConfigList echConfigList, err := cfg.GetEncryptedClientHelloConfigList(ctx, tlsConfig.ServerName)
if len(echConfigList) == 0 { if err != nil {
echConfigList, err = resolver.ResolveECH(ctx, tlsConfig.ServerName) return fmt.Errorf("resolve ECH config error: %w", err)
if err != nil {
return fmt.Errorf("resolve ECH config error: %w", err)
}
} }
tlsConfig.EncryptedClientHelloConfigList = echConfigList tlsConfig.EncryptedClientHelloConfigList = echConfigList

View File

@@ -26,8 +26,9 @@ var (
) )
type ifaceCache struct { type ifaceCache struct {
ifMap map[string]*Interface ifMapByName map[string]*Interface
ifTable bart.Table[*Interface] ifMapByAddr map[netip.Addr]*Interface
ifTable bart.Table[*Interface]
} }
var caches = singledo.NewSingle[*ifaceCache](time.Second * 20) var caches = singledo.NewSingle[*ifaceCache](time.Second * 20)
@@ -40,7 +41,8 @@ func getCache() (*ifaceCache, error) {
} }
cache := &ifaceCache{ cache := &ifaceCache{
ifMap: make(map[string]*Interface), ifMapByName: make(map[string]*Interface),
ifMapByAddr: make(map[netip.Addr]*Interface),
} }
for _, iface := range ifaces { for _, iface := range ifaces {
@@ -78,12 +80,13 @@ func getCache() (*ifaceCache, error) {
Flags: iface.Flags, Flags: iface.Flags,
Addresses: ipNets, Addresses: ipNets,
} }
cache.ifMap[iface.Name] = ifaceObj cache.ifMapByName[iface.Name] = ifaceObj
if iface.Flags&net.FlagUp == 0 { if iface.Flags&net.FlagUp == 0 {
continue // interface down continue // interface down
} }
for _, prefix := range ipNets { for _, prefix := range ipNets {
cache.ifMapByAddr[prefix.Addr()] = ifaceObj
cache.ifTable.Insert(prefix, ifaceObj) cache.ifTable.Insert(prefix, ifaceObj)
} }
} }
@@ -98,7 +101,7 @@ func Interfaces() (map[string]*Interface, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return cache.ifMap, nil return cache.ifMapByName, nil
} }
func ResolveInterface(name string) (*Interface, error) { func ResolveInterface(name string) (*Interface, error) {
@@ -120,6 +123,11 @@ func ResolveInterfaceByAddr(addr netip.Addr) (*Interface, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// maybe two interfaces have the same prefix but different address
// so direct check address equal before do a route lookup (longest prefix match)
if iface, ok := cache.ifMapByAddr[addr]; ok {
return iface, nil
}
iface, ok := cache.ifTable.Lookup(addr) iface, ok := cache.ifTable.Lookup(addr)
if !ok { if !ok {
return nil, ErrIfaceNotFound return nil, ErrIfaceNotFound
@@ -133,7 +141,8 @@ func IsLocalIp(addr netip.Addr) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
return cache.ifTable.Contains(addr), nil _, ok := cache.ifMapByAddr[addr]
return ok, nil
} }
func FlushCache() { func FlushCache() {

View File

@@ -2,7 +2,6 @@ package proxydialer
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
@@ -10,7 +9,6 @@ import (
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/tunnel" "github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
@@ -40,17 +38,16 @@ func (p proxyDialer) DialContext(ctx context.Context, network, address string) (
return nil, err return nil, err
} }
if strings.Contains(network, "udp") { // using in wireguard outbound if strings.Contains(network, "udp") { // using in wireguard outbound
if !currentMeta.Resolved() {
ip, err := resolver.ResolveIP(ctx, currentMeta.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
currentMeta.DstIP = ip
}
pc, err := p.listenPacket(ctx, currentMeta) pc, err := p.listenPacket(ctx, currentMeta)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !currentMeta.Resolved() { // should not happen, maybe by a wrongly implemented proxy, but we can handle this (:
err = pc.ResolveUDP(ctx, currentMeta)
if err != nil {
return nil, err
}
}
return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil
} }
var conn C.Conn var conn C.Conn

View File

@@ -77,7 +77,7 @@ func NewHostValue(value any) (HostValue, error) {
isDomain = false isDomain = false
for _, str := range valueArr { for _, str := range valueArr {
if ip, err := netip.ParseAddr(str); err == nil { if ip, err := netip.ParseAddr(str); err == nil {
ips = append(ips, ip) ips = append(ips, ip.Unmap())
} else { } else {
return HostValue{}, err return HostValue{}, err
} }
@@ -85,7 +85,7 @@ func NewHostValue(value any) (HostValue, error) {
} else if len(valueArr) == 1 { } else if len(valueArr) == 1 {
host := valueArr[0] host := valueArr[0]
if ip, err := netip.ParseAddr(host); err == nil { if ip, err := netip.ParseAddr(host); err == nil {
ips = append(ips, ip) ips = append(ips, ip.Unmap())
isDomain = false isDomain = false
} else { } else {
domain = host domain = host

View File

@@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/netip" "net/netip"
"strings"
"time" "time"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
@@ -68,7 +67,8 @@ func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]net
ip, err := netip.ParseAddr(host) ip, err := netip.ParseAddr(host)
if err == nil { if err == nil {
if ip.Is4() || ip.Is4In6() { ip = ip.Unmap()
if ip.Is4() {
return []netip.Addr{ip}, nil return []netip.Addr{ip}, nil
} }
return []netip.Addr{}, ErrIPVersion return []netip.Addr{}, ErrIPVersion
@@ -117,7 +117,8 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
} }
if ip, err := netip.ParseAddr(host); err == nil { if ip, err := netip.ParseAddr(host); err == nil {
if strings.Contains(host, ":") { ip = ip.Unmap()
if ip.Is6() {
return []netip.Addr{ip}, nil return []netip.Addr{ip}, nil
} }
return nil, ErrIPVersion return nil, ErrIPVersion
@@ -166,6 +167,7 @@ func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip
} }
if ip, err := netip.ParseAddr(host); err == nil { if ip, err := netip.ParseAddr(host); err == nil {
ip = ip.Unmap()
return []netip.Addr{ip}, nil return []netip.Addr{ip}, nil
} }

View File

@@ -72,8 +72,16 @@ func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter, packetSender C.PacketSend
overrideDest := config.OverrideDest overrideDest := config.OverrideDest
if inWhitelist { if inWhitelist {
replaceDomain := func(metadata *C.Metadata, host string) {
if sd.domainCanReplace(host) {
replaceDomain(metadata, host, overrideDest)
} else {
log.Debugln("[Sniffer] Skip sni[%s]", host)
}
}
if wrapable, ok := current.(sniffer.MultiPacketSniffer); ok { if wrapable, ok := current.(sniffer.MultiPacketSniffer); ok {
return wrapable.WrapperSender(packetSender, overrideDest) return wrapable.WrapperSender(packetSender, replaceDomain)
} }
host, err := current.SniffData(packet.Data()) host, err := current.SniffData(packet.Data())
@@ -81,7 +89,7 @@ func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter, packetSender C.PacketSend
continue continue
} }
replaceDomain(metadata, host, overrideDest) replaceDomain(metadata, host)
return packetSender return packetSender
} }
} }
@@ -128,11 +136,9 @@ func (sd *Dispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool
return false return false
} }
for _, matcher := range sd.skipDomain { if !sd.domainCanReplace(host) {
if matcher.MatchDomain(host) { log.Debugln("[Sniffer] Skip sni[%s]", host)
log.Debugln("[Sniffer] Skip sni[%s]", host) return false
return false
}
} }
sd.skipList.Delete(dst) sd.skipList.Delete(dst)
@@ -152,10 +158,20 @@ func replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
metadata.RemoteAddress(), metadata.RemoteAddress(),
metadata.Host, host) metadata.Host, host)
metadata.Host = host metadata.Host = host
metadata.DstIP = netip.Addr{}
} }
metadata.DNSMode = C.DNSNormal metadata.DNSMode = C.DNSNormal
} }
func (sd *Dispatcher) domainCanReplace(host string) bool {
for _, matcher := range sd.skipDomain {
if matcher.MatchDomain(host) {
return false
}
}
return true
}
func (sd *Dispatcher) Enable() bool { func (sd *Dispatcher) Enable() bool {
return sd != nil && sd.enable return sd != nil && sd.enable
} }

View File

@@ -74,24 +74,25 @@ func (sniffer *QuicSniffer) SniffData(b []byte) (string, error) {
return "", ErrorUnsupportedSniffer return "", ErrorUnsupportedSniffer
} }
func (sniffer *QuicSniffer) WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender { func (sniffer *QuicSniffer) WrapperSender(packetSender constant.PacketSender, replaceDomain sniffer.ReplaceDomain) constant.PacketSender {
return &quicPacketSender{ return &quicPacketSender{
sender: packetSender, PacketSender: packetSender,
chClose: make(chan struct{}), replaceDomain: replaceDomain,
override: override, chClose: make(chan struct{}),
} }
} }
var _ constant.PacketSender = (*quicPacketSender)(nil) var _ constant.PacketSender = (*quicPacketSender)(nil)
type quicPacketSender struct { type quicPacketSender struct {
lock sync.RWMutex lock sync.RWMutex
ranges utils.IntRanges[uint64] ranges utils.IntRanges[uint64]
buffer []byte buffer []byte
result string result *string
override bool
sender constant.PacketSender replaceDomain sniffer.ReplaceDomain
constant.PacketSender
chClose chan struct{} chClose chan struct{}
closed bool closed bool
@@ -100,7 +101,7 @@ type quicPacketSender struct {
// Send will send PacketAdapter nonblocking // Send will send PacketAdapter nonblocking
// the implement must call UDPPacket.Drop() inside Send // the implement must call UDPPacket.Drop() inside Send
func (q *quicPacketSender) Send(current constant.PacketAdapter) { func (q *quicPacketSender) Send(current constant.PacketAdapter) {
defer q.sender.Send(current) defer q.PacketSender.Send(current)
q.lock.RLock() q.lock.RLock()
if q.closed { if q.closed {
@@ -116,29 +117,27 @@ func (q *quicPacketSender) Send(current constant.PacketAdapter) {
} }
} }
// Process is a blocking loop to send PacketAdapter to PacketConn and update the WriteBackProxy // DoSniff wait sniffer recv all fragments and update the domain
func (q *quicPacketSender) Process(conn constant.PacketConn, proxy constant.WriteBackProxy) { func (q *quicPacketSender) DoSniff(metadata *constant.Metadata) error {
q.sender.Process(conn, proxy)
}
// ResolveUDP wait sniffer recv all fragments and update the domain
func (q *quicPacketSender) ResolveUDP(data *constant.Metadata) error {
select { select {
case <-q.chClose: case <-q.chClose:
q.lock.RLock() q.lock.RLock()
replaceDomain(data, q.result, q.override) if q.result != nil {
host := *q.result
q.replaceDomain(metadata, host)
}
q.lock.RUnlock() q.lock.RUnlock()
break break
case <-time.After(quicWaitConn): case <-time.After(quicWaitConn):
q.close() q.close()
} }
return q.sender.ResolveUDP(data) return q.PacketSender.DoSniff(metadata)
} }
// Close stop the Process loop // Close stop the Process loop
func (q *quicPacketSender) Close() { func (q *quicPacketSender) Close() {
q.sender.Close() q.PacketSender.Close()
q.close() q.close()
} }
@@ -433,7 +432,7 @@ func (q *quicPacketSender) tryAssemble() error {
} }
q.lock.Lock() q.lock.Lock()
q.result = *domain q.result = domain
q.closeLocked() q.closeLocked()
q.lock.Unlock() q.lock.Unlock()

View File

@@ -12,7 +12,7 @@ import (
) )
type fakeSender struct { type fakeSender struct {
resultCh chan *constant.Metadata constant.PacketSender
} }
var _ constant.PacketSender = (*fakeSender)(nil) var _ constant.PacketSender = (*fakeSender)(nil)
@@ -22,18 +22,7 @@ func (e *fakeSender) Send(packet constant.PacketAdapter) {
packet.Drop() packet.Drop()
} }
func (e *fakeSender) Process(constant.PacketConn, constant.WriteBackProxy) { func (e *fakeSender) DoSniff(metadata *constant.Metadata) error { return nil }
panic("not implemented")
}
func (e *fakeSender) ResolveUDP(metadata *constant.Metadata) error {
e.resultCh <- metadata
return nil
}
func (e *fakeSender) Close() {
panic("not implemented")
}
type fakeUDPPacket struct { type fakeUDPPacket struct {
data []byte data []byte
@@ -78,23 +67,28 @@ func asPacket(data string) constant.PacketAdapter {
return pktAdp return pktAdp
} }
func testQuicSniffer(data []string, async bool) (string, error) { const fakeHost = "fake.host.com"
func testQuicSniffer(data []string, async bool) (string, string, error) {
q, err := NewQuicSniffer(SnifferConfig{}) q, err := NewQuicSniffer(SnifferConfig{})
if err != nil { if err != nil {
return "", err return "", "", err
} }
resultCh := make(chan *constant.Metadata, 1) resultCh := make(chan *constant.Metadata, 1)
emptySender := &fakeSender{resultCh: resultCh} emptySender := &fakeSender{}
sender := q.WrapperSender(emptySender, true) sender := q.WrapperSender(emptySender, func(metadata *constant.Metadata, host string) {
replaceDomain(metadata, host, true)
})
go func() { go func() {
meta := constant.Metadata{} meta := constant.Metadata{Host: fakeHost}
err = sender.ResolveUDP(&meta) err := sender.DoSniff(&meta)
if err != nil { if err != nil {
panic(err) panic(err)
} }
resultCh <- &meta
}() }()
for _, d := range data { for _, d := range data {
@@ -106,14 +100,15 @@ func testQuicSniffer(data []string, async bool) (string, error) {
} }
meta := <-resultCh meta := <-resultCh
return meta.SniffHost, nil return meta.SniffHost, meta.Host, nil
} }
func TestQuicHeaders(t *testing.T) { func TestQuicHeaders(t *testing.T) {
cases := []struct { cases := []struct {
input []string input []string
domain string domain string
invalid bool
}{ }{
//Normal domain quic sniff //Normal domain quic sniff
{ {
@@ -171,16 +166,31 @@ func TestQuicHeaders(t *testing.T) {
}, },
domain: "www.google.com", domain: "www.google.com",
}, },
// invalid packet
{
input: []string{"00000000000000000000"},
invalid: true,
},
} }
for _, test := range cases { for _, test := range cases {
data, err := testQuicSniffer(test.input, true) data, host, err := testQuicSniffer(test.input, true)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, test.domain, data) assert.Equal(t, test.domain, data)
if test.invalid {
assert.Equal(t, fakeHost, host)
} else {
assert.Equal(t, test.domain, host)
}
data, err = testQuicSniffer(test.input, false) data, host, err = testQuicSniffer(test.input, false)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, test.domain, data) assert.Equal(t, test.domain, data)
if test.invalid {
assert.Equal(t, fakeHost, host)
} else {
assert.Equal(t, test.domain, host)
}
} }
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"net" "net"
"net/http" "net/http"
"runtime/debug"
"time" "time"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
@@ -63,6 +64,7 @@ func NewListenerForHttps(l net.Listener, httpServer *http.Server, tlsConfig *Con
} }
return c, nil return c, nil
}, func(a any) { }, func(a any) {
log.Errorln("https server panic: %s", a) stack := debug.Stack()
log.Errorln("https server panic: %s\n%s", a, stack)
}) })
} }

View File

@@ -92,8 +92,7 @@ type Conn interface {
type PacketConn interface { type PacketConn interface {
N.EnhancePacketConn N.EnhancePacketConn
Connection Connection
// Deprecate WriteWithMetadata because of remote resolve DNS cause TURN failed ResolveUDP(ctx context.Context, metadata *Metadata) error
// WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error)
} }
type Dialer interface { type Dialer interface {
@@ -319,10 +318,15 @@ type PacketSender interface {
Send(PacketAdapter) Send(PacketAdapter)
// Process is a blocking loop to send PacketAdapter to PacketConn and update the WriteBackProxy // Process is a blocking loop to send PacketAdapter to PacketConn and update the WriteBackProxy
Process(PacketConn, WriteBackProxy) Process(PacketConn, WriteBackProxy)
// ResolveUDP do a local resolve UDP dns blocking if metadata is not resolved
ResolveUDP(*Metadata) error
// Close stop the Process loop // Close stop the Process loop
Close() Close()
// DoSniff will blocking after sniffer work done
DoSniff(*Metadata) error
// AddMapping add a destination NAT record
AddMapping(originMetadata *Metadata, metadata *Metadata)
// RestoreReadFrom restore destination NAT for ReadFrom
// the implement must ensure returned netip.Add is valid (or just return input addr)
RestoreReadFrom(addr netip.Addr) netip.Addr
} }
type NatTable interface { type NatTable interface {

View File

@@ -261,6 +261,11 @@ func (m *Metadata) Pure() *Metadata {
return m return m
} }
func (m *Metadata) Clone() *Metadata {
copyM := *m
return &copyM
}
func (m *Metadata) AddrPort() netip.AddrPort { func (m *Metadata) AddrPort() netip.AddrPort {
return netip.AddrPortFrom(m.DstIP.Unmap(), m.DstPort) return netip.AddrPortFrom(m.DstIP.Unmap(), m.DstPort)
} }

View File

@@ -10,8 +10,10 @@ type Sniffer interface {
SupportPort(port uint16) bool SupportPort(port uint16) bool
} }
type ReplaceDomain func(metadata *constant.Metadata, host string)
type MultiPacketSniffer interface { type MultiPacketSniffer interface {
WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender WrapperSender(packetSender constant.PacketSender, replaceDomain ReplaceDomain) constant.PacketSender
} }
const ( const (

View File

@@ -348,7 +348,8 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (ips []netip.Addr, err error) { func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (ips []netip.Addr, err error) {
ip, err := netip.ParseAddr(host) ip, err := netip.ParseAddr(host)
if err == nil { if err == nil {
isIPv4 := ip.Is4() || ip.Is4In6() ip = ip.Unmap()
isIPv4 := ip.Is4()
if dnsType == D.TypeAAAA && !isIPv4 { if dnsType == D.TypeAAAA && !isIPv4 {
return []netip.Addr{ip}, nil return []netip.Addr{ip}, nil
} else if dnsType == D.TypeA && isIPv4 { } else if dnsType == D.TypeA && isIPv4 {

14
go.mod
View File

@@ -23,16 +23,16 @@ require (
github.com/metacubex/chacha v0.1.2 github.com/metacubex/chacha v0.1.2
github.com/metacubex/fswatch v0.1.1 github.com/metacubex/fswatch v0.1.1
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639
github.com/metacubex/randv2 v0.2.0 github.com/metacubex/randv2 v0.2.0
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 github.com/metacubex/sing v0.5.3
github.com/metacubex/sing-mux v0.3.2 github.com/metacubex/sing-mux v0.3.2
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f
github.com/metacubex/sing-shadowsocks v0.2.9 github.com/metacubex/sing-shadowsocks v0.2.10
github.com/metacubex/sing-shadowsocks2 v0.2.3 github.com/metacubex/sing-shadowsocks2 v0.2.4
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c
github.com/metacubex/sing-vmess v0.2.1 github.com/metacubex/sing-vmess v0.2.2
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4

28
go.sum
View File

@@ -111,27 +111,27 @@ github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA= github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b h1:8oDU32eJ+RRhl1FodGgPfxQxtoBAiD9D40XG2XtU/SE= github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639 h1:L+1brQNzBhCCxWlicwfK1TlceemCRmrDE4HmcVHc29w=
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b/go.mod h1:9R1NOzCgTcWsdWvOMlmtMuF0uKzuOpsfvEf7U3I8zM0= github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639/go.mod h1:Kc6h++Q/zf3AxcUCevJhJwgrskJumv+pZdR8g/E/10k=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxMzzmnXams+ebwabcry4Ydep/zNiesQ= github.com/metacubex/sing v0.5.3 h1:QWdN16WFKMk06x4nzkc8SvZ7y2x+TLQrpkPoHs+WSVM=
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing v0.5.3/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw= github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw= github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a h1:Ho73vGiB94LmtK5T+tKVwtCNEi/YiHmPjlqpHSAmAVs= github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I=
github.com/metacubex/sing-quic v0.0.0-20250520025433-6e556a6bef7a/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM= github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00= github.com/metacubex/sing-shadowsocks v0.2.10 h1:Pr7LDbjMANIQHl07zWgl1vDuhpsfDQUpZ8cX6DPabfg=
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA= github.com/metacubex/sing-shadowsocks v0.2.10/go.mod h1:MtRM0ZZjR0kaDOzy9zWSt6/4/UlrnsNBq+1FNAF4vBk=
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0= github.com/metacubex/sing-shadowsocks2 v0.2.4 h1:Ec0x3hHR7xkld5Z09IGh16wtUUpBb2HgqZ9DExd8Q7s=
github.com/metacubex/sing-shadowsocks2 v0.2.3/go.mod h1:/WNy/Q8ahLCoPRriWuFZFD0Jy+JNp1MEQl28Zw6SaF8= github.com/metacubex/sing-shadowsocks2 v0.2.4/go.mod h1:WP8+S0kqtnSbX1vlIpo5i8Irm/ijZITEPBcZ26B5unY=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 h1:TAwL91XPa6x1QK55CRm+VTzPvLPUfEr/uFDnOZArqEU= github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c h1:Y6jk7AH5BEg9Dsvczrf/KokYsvxeKSZZlCLHg+hC4ro=
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U= github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
github.com/metacubex/sing-vmess v0.2.1 h1:I6gM3VUjtvJ15D805EUbNH+SRBuqzJeFnuIbKYUsWZ0= github.com/metacubex/sing-vmess v0.2.2 h1:nG6GIKF1UOGmlzs+BIetdGHkFZ20YqFVIYp5Htqzp+4=
github.com/metacubex/sing-vmess v0.2.1/go.mod h1:DsODWItJtOMZNna8Qhheg8r3tUivrcO3vWgaTYKnfTo= github.com/metacubex/sing-vmess v0.2.2/go.mod h1:CVDNcdSLVYFgTHQlubr88d8CdqupAUDqLjROos+H9xk=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo= github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=

View File

@@ -7,6 +7,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"runtime/debug"
"time" "time"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
@@ -89,7 +90,8 @@ func (b Builder) NewListener(l net.Listener) net.Listener {
// We fixed it by calling Close() directly. // We fixed it by calling Close() directly.
return realityConnWrapper{c}, nil return realityConnWrapper{c}, nil
}, func(a any) { }, func(a any) {
log.Errorln("reality server panic: %s", a) stack := debug.Stack()
log.Errorln("reality server panic: %s\n%s", a, stack)
}) })
} }

View File

@@ -3,6 +3,7 @@ package sing
import ( import (
"context" "context"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"net"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
@@ -29,3 +30,18 @@ func getAdditions(ctx context.Context) (additions []inbound.Addition) {
} }
return return
} }
var ctxKeyInAddr = contextKey("InAddr")
func WithInAddr(ctx context.Context, inAddr net.Addr) context.Context {
return context.WithValue(ctx, ctxKeyInAddr, inAddr)
}
func getInAddr(ctx context.Context) net.Addr {
if v := ctx.Value(ctxKeyInAddr); v != nil {
if a, ok := v.(net.Addr); ok {
return a
}
}
return nil
}

View File

@@ -16,6 +16,7 @@ import (
mux "github.com/metacubex/sing-mux" mux "github.com/metacubex/sing-mux"
vmess "github.com/metacubex/sing-vmess" vmess "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing/common"
"github.com/metacubex/sing/common/buf" "github.com/metacubex/sing/common/buf"
"github.com/metacubex/sing/common/bufio" "github.com/metacubex/sing/common/bufio"
"github.com/metacubex/sing/common/bufio/deadline" "github.com/metacubex/sing/common/bufio/deadline"
@@ -146,11 +147,11 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta
func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error { func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error {
defer func() { _ = conn.Close() }() defer func() { _ = conn.Close() }()
mutex := sync.Mutex{} mutex := sync.Mutex{}
conn2 := bufio.NewNetPacketConn(conn) // a new interface to set nil in defer writer := bufio.NewNetPacketWriter(conn) // a new interface to set nil in defer
defer func() { defer func() {
mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running
defer mutex.Unlock() defer mutex.Unlock()
conn2 = nil writer = nil
}() }()
rwOptions := network.ReadWaitOptions{} rwOptions := network.ReadWaitOptions{}
readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn) readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn)
@@ -180,32 +181,59 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
return err return err
} }
cPacket := &packet{ cPacket := &packet{
conn: &conn2, writer: &writer,
mutex: &mutex, mutex: &mutex,
rAddr: metadata.Source.UDPAddr(), rAddr: metadata.Source.UDPAddr(),
lAddr: conn.LocalAddr(), lAddr: conn.LocalAddr(),
buff: buff, buff: buff,
} }
if lAddr := getInAddr(ctx); lAddr != nil {
cMetadata := &C.Metadata{ cPacket.lAddr = lAddr
NetWork: C.UDP,
Type: h.Type,
} }
if metadata.Source.IsIP() && metadata.Source.Fqdn == "" { h.handlePacket(ctx, cPacket, metadata.Source, dest)
cMetadata.RawSrcAddr = metadata.Source.Unwrap().UDPAddr()
}
if dest.IsIP() && dest.Fqdn == "" {
cMetadata.RawDstAddr = dest.Unwrap().UDPAddr()
}
inbound.ApplyAdditions(cMetadata, inbound.WithDstAddr(dest), inbound.WithSrcAddr(metadata.Source), inbound.WithInAddr(conn.LocalAddr()))
inbound.ApplyAdditions(cMetadata, h.Additions...)
inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...)
h.Tunnel.HandleUDPPacket(cPacket, cMetadata)
} }
return nil return nil
} }
type localAddr interface {
LocalAddr() net.Addr
}
func (h *ListenerHandler) NewPacket(ctx context.Context, key netip.AddrPort, buffer *buf.Buffer, metadata M.Metadata, init func(natConn network.PacketConn) network.PacketWriter) {
writer := bufio.NewNetPacketWriter(init(nil))
mutex := sync.Mutex{}
cPacket := &packet{
writer: &writer,
mutex: &mutex,
rAddr: metadata.Source.UDPAddr(),
buff: buffer,
}
if conn, ok := common.Cast[localAddr](writer); ok {
cPacket.rAddr = conn.LocalAddr()
} else {
cPacket.rAddr = metadata.Source.UDPAddr() // tun does not have real inAddr
}
h.handlePacket(ctx, cPacket, metadata.Source, metadata.Destination)
}
func (h *ListenerHandler) handlePacket(ctx context.Context, cPacket *packet, source M.Socksaddr, destination M.Socksaddr) {
cMetadata := &C.Metadata{
NetWork: C.UDP,
Type: h.Type,
}
if source.IsIP() && source.Fqdn == "" {
cMetadata.RawSrcAddr = source.Unwrap().UDPAddr()
}
if destination.IsIP() && destination.Fqdn == "" {
cMetadata.RawDstAddr = destination.Unwrap().UDPAddr()
}
inbound.ApplyAdditions(cMetadata, inbound.WithDstAddr(destination), inbound.WithSrcAddr(source), inbound.WithInAddr(cPacket.InAddr()))
inbound.ApplyAdditions(cMetadata, h.Additions...)
inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...)
h.Tunnel.HandleUDPPacket(cPacket, cMetadata)
}
func (h *ListenerHandler) NewError(ctx context.Context, err error) { func (h *ListenerHandler) NewError(ctx context.Context, err error) {
log.Warnln("%s listener get error: %+v", h.Type.String(), err) log.Warnln("%s listener get error: %+v", h.Type.String(), err)
} }
@@ -225,11 +253,11 @@ func ShouldIgnorePacketError(err error) bool {
} }
type packet struct { type packet struct {
conn *network.NetPacketConn writer *network.NetPacketWriter
mutex *sync.Mutex mutex *sync.Mutex
rAddr net.Addr rAddr net.Addr
lAddr net.Addr lAddr net.Addr
buff *buf.Buffer buff *buf.Buffer
} }
func (c *packet) Data() []byte { func (c *packet) Data() []byte {
@@ -245,7 +273,7 @@ func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
conn := *c.conn conn := *c.writer
if conn == nil { if conn == nil {
err = errors.New("writeBack to closed connection") err = errors.New("writeBack to closed connection")
return return

View File

@@ -156,11 +156,7 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi
go func() { go func() {
conn := bufio.NewPacketConn(ul) conn := bufio.NewPacketConn(ul)
rwOptions := network.ReadWaitOptions{ rwOptions := network.NewReadWaitOptions(conn, sl.service)
FrontHeadroom: network.CalculateFrontHeadroom(sl.service),
RearHeadroom: network.CalculateRearHeadroom(sl.service),
MTU: network.CalculateMTU(conn, sl.service),
}
readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn) readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn)
if isReadWaiter { if isReadWaiter {
readWaiter.InitializeReadWaiter(rwOptions) readWaiter.InitializeReadWaiter(rwOptions)
@@ -188,7 +184,9 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi
} }
continue continue
} }
_ = sl.service.NewPacket(context.TODO(), conn, buff, M.Metadata{ ctx := context.TODO()
ctx = sing.WithInAddr(ctx, ul.LocalAddr())
_ = sl.service.NewPacket(ctx, conn, buff, M.Metadata{
Protocol: "shadowsocks", Protocol: "shadowsocks",
Source: dest, Source: dest,
}) })

View File

@@ -43,16 +43,31 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta
return h.ListenerHandler.NewConnection(ctx, conn, metadata) return h.ListenerHandler.NewConnection(ctx, conn, metadata)
} }
func (h *ListenerHandler) NewPacket(ctx context.Context, key netip.AddrPort, buffer *buf.Buffer, metadata M.Metadata, init func(natConn network.PacketConn) network.PacketWriter) {
if h.ShouldHijackDns(metadata.Destination.AddrPort()) {
log.Debugln("[DNS] hijack udp:%s from %s", metadata.Destination.String(), metadata.Source.String())
writer := init(nil)
rwOptions := network.ReadWaitOptions{
FrontHeadroom: network.CalculateFrontHeadroom(writer),
RearHeadroom: network.CalculateRearHeadroom(writer),
MTU: resolver.SafeDnsPacketSize,
}
go relayDnsPacket(ctx, buffer, rwOptions, metadata.Destination, nil, &writer)
return
}
h.ListenerHandler.NewPacket(ctx, key, buffer, metadata, init)
}
func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error { func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error {
if h.ShouldHijackDns(metadata.Destination.AddrPort()) { if h.ShouldHijackDns(metadata.Destination.AddrPort()) {
log.Debugln("[DNS] hijack udp:%s from %s", metadata.Destination.String(), metadata.Source.String()) log.Debugln("[DNS] hijack udp:%s from %s", metadata.Destination.String(), metadata.Source.String())
defer func() { _ = conn.Close() }() defer func() { _ = conn.Close() }()
mutex := sync.Mutex{} mutex := sync.Mutex{}
conn2 := conn // a new interface to set nil in defer var writer network.PacketWriter = conn // a new interface to set nil in defer
defer func() { defer func() {
mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running
defer mutex.Unlock() defer mutex.Unlock()
conn2 = nil writer = nil
}() }()
rwOptions := network.ReadWaitOptions{ rwOptions := network.ReadWaitOptions{
FrontHeadroom: network.CalculateFrontHeadroom(conn), FrontHeadroom: network.CalculateFrontHeadroom(conn),
@@ -89,43 +104,47 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
} }
return err return err
} }
go func() { go relayDnsPacket(ctx, readBuff, rwOptions, dest, &mutex, &writer)
ctx, cancel := context.WithTimeout(ctx, resolver.DefaultDnsRelayTimeout)
defer cancel()
inData := readBuff.Bytes()
writeBuff := readBuff
writeBuff.Resize(writeBuff.Start(), 0)
if len(writeBuff.FreeBytes()) < resolver.SafeDnsPacketSize { // only create a new buffer when space don't enough
writeBuff = rwOptions.NewPacketBuffer()
}
msg, err := resolver.RelayDnsPacket(ctx, inData, writeBuff.FreeBytes())
if writeBuff != readBuff {
readBuff.Release()
}
if err != nil {
writeBuff.Release()
return
}
writeBuff.Truncate(len(msg))
mutex.Lock()
defer mutex.Unlock()
conn := conn2
if conn == nil {
writeBuff.Release()
return
}
err = conn.WritePacket(writeBuff, dest) // WritePacket will release writeBuff
if err != nil {
writeBuff.Release()
return
}
}()
} }
return nil return nil
} }
return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata) return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata)
} }
func relayDnsPacket(ctx context.Context, readBuff *buf.Buffer, rwOptions network.ReadWaitOptions, dest M.Socksaddr, mutex *sync.Mutex, writer *network.PacketWriter) {
ctx, cancel := context.WithTimeout(ctx, resolver.DefaultDnsRelayTimeout)
defer cancel()
inData := readBuff.Bytes()
writeBuff := readBuff
writeBuff.Resize(writeBuff.Start(), 0)
if len(writeBuff.FreeBytes()) < resolver.SafeDnsPacketSize { // only create a new buffer when space don't enough
writeBuff = rwOptions.NewPacketBuffer()
}
msg, err := resolver.RelayDnsPacket(ctx, inData, writeBuff.FreeBytes())
if writeBuff != readBuff {
readBuff.Release()
}
if err != nil {
writeBuff.Release()
return
}
writeBuff.Truncate(len(msg))
if mutex != nil {
mutex.Lock()
defer mutex.Unlock()
}
conn := *writer
if conn == nil {
writeBuff.Release()
return
}
err = conn.WritePacket(writeBuff, dest) // WritePacket will release writeBuff
if err != nil {
writeBuff.Release()
return
}
}
func (h *ListenerHandler) TypeMutation(typ C.Type) *ListenerHandler { func (h *ListenerHandler) TypeMutation(typ C.Type) *ListenerHandler {
handle := *h handle := *h
handle.ListenerHandler = h.ListenerHandler.TypeMutation(typ) handle.ListenerHandler = h.ListenerHandler.TypeMutation(typ)

View File

@@ -408,7 +408,7 @@ func Dial(network, address string) (*TCPConn, error) {
ifaceName := dialer.DefaultInterface.Load() ifaceName := dialer.DefaultInterface.Load()
if ifaceName == "" { if ifaceName == "" {
if finder := dialer.DefaultInterfaceFinder.Load(); finder != nil { if finder := dialer.DefaultInterfaceFinder.Load(); finder != nil {
ifaceName = finder.FindInterfaceName(rAddrPort.Addr()) ifaceName = finder.FindInterfaceName(rAddrPort.Addr().Unmap())
} }
} }
if len(ifaceName) > 0 { if len(ifaceName) > 0 {

View File

@@ -5,11 +5,10 @@ import (
"errors" "errors"
"net" "net"
"net/netip" "net/netip"
"sync"
"time" "time"
"github.com/metacubex/mihomo/common/lru"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
@@ -18,7 +17,11 @@ type packetSender struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
ch chan C.PacketAdapter ch chan C.PacketAdapter
cache *lru.LruCache[string, netip.Addr]
// destination NAT mapping
originToTarget map[string]netip.Addr
targetToOrigin map[netip.Addr]netip.Addr
mappingMutex sync.RWMutex
} }
// newPacketSender return a chan based C.PacketSender // newPacketSender return a chan based C.PacketSender
@@ -30,10 +33,74 @@ func newPacketSender() C.PacketSender {
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
ch: ch, ch: ch,
cache: lru.New[string, netip.Addr](lru.WithSize[string, netip.Addr](senderCapacity)),
originToTarget: make(map[string]netip.Addr),
targetToOrigin: make(map[netip.Addr]netip.Addr),
} }
} }
func (s *packetSender) AddMapping(originMetadata *C.Metadata, metadata *C.Metadata) {
s.mappingMutex.Lock()
defer s.mappingMutex.Unlock()
originKey := originMetadata.String()
originAddr := originMetadata.DstIP
targetAddr := metadata.DstIP
if addr := s.originToTarget[originKey]; !addr.IsValid() { // overwrite only if the record is illegal
s.originToTarget[originKey] = targetAddr
}
if addr := s.targetToOrigin[targetAddr]; !addr.IsValid() { // overwrite only if the record is illegal
s.targetToOrigin[targetAddr] = originAddr
}
}
func (s *packetSender) RestoreReadFrom(addr netip.Addr) netip.Addr {
s.mappingMutex.RLock()
defer s.mappingMutex.RUnlock()
if originAddr := s.targetToOrigin[addr]; originAddr.IsValid() {
return originAddr
}
return addr
}
func (s *packetSender) processPacket(pc C.PacketConn, packet C.PacketAdapter) {
defer packet.Drop()
metadata := packet.Metadata()
var addr *net.UDPAddr
s.mappingMutex.RLock()
targetAddr := s.originToTarget[metadata.String()]
s.mappingMutex.RUnlock()
if targetAddr.IsValid() {
addr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(targetAddr, metadata.DstPort))
}
if addr == nil {
originMetadata := metadata // save origin metadata
metadata = metadata.Clone() // don't modify PacketAdapter's metadata
_ = preHandleMetadata(metadata) // error was pre-checked
metadata = metadata.Pure()
if metadata.Host != "" {
// TODO: ResolveUDP may take a long time to block the Process loop
// but we want keep sequence sending so can't open a new goroutine
if err := pc.ResolveUDP(s.ctx, metadata); err != nil {
log.Warnln("[UDP] Resolve Ip error: %s", err)
return
}
}
if !metadata.DstIP.IsValid() {
log.Warnln("[UDP] Destination ip not valid: %#v", metadata)
return
}
s.AddMapping(originMetadata, metadata)
addr = metadata.UDPAddr()
}
_ = handleUDPToRemote(packet, pc, addr)
}
func (s *packetSender) Process(pc C.PacketConn, proxy C.WriteBackProxy) { func (s *packetSender) Process(pc C.PacketConn, proxy C.WriteBackProxy) {
for { for {
select { select {
@@ -43,12 +110,7 @@ func (s *packetSender) Process(pc C.PacketConn, proxy C.WriteBackProxy) {
if proxy != nil { if proxy != nil {
proxy.UpdateWriteBack(packet) proxy.UpdateWriteBack(packet)
} }
if err := s.ResolveUDP(packet.Metadata()); err != nil { s.processPacket(pc, packet)
log.Warnln("[UDP] Resolve Ip error: %s", err)
} else {
_ = handleUDPToRemote(packet, pc, packet.Metadata())
}
packet.Drop()
} }
} }
} }
@@ -87,25 +149,9 @@ func (s *packetSender) Close() {
s.dropAll() s.dropAll()
} }
func (s *packetSender) ResolveUDP(metadata *C.Metadata) (err error) { func (s *packetSender) DoSniff(metadata *C.Metadata) error { return nil }
// local resolve UDP dns
if !metadata.Resolved() {
ip, ok := s.cache.Get(metadata.Host)
if !ok {
ip, err = resolver.ResolveIP(s.ctx, metadata.Host)
if err != nil {
return err
}
s.cache.Set(metadata.Host, ip)
}
metadata.DstIP = ip func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, addr *net.UDPAddr) error {
}
return nil
}
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error {
addr := metadata.UDPAddr()
if addr == nil { if addr == nil {
return errors.New("udp addr invalid") return errors.New("udp addr invalid")
} }
@@ -119,7 +165,7 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
return nil return nil
} }
func handleUDPToLocal(writeBack C.WriteBack, pc N.EnhancePacketConn, sender C.PacketSender, key string, oAddrPort netip.AddrPort, fAddr netip.Addr) { func handleUDPToLocal(writeBack C.WriteBack, pc C.PacketConn, sender C.PacketSender, key string, oAddrPort netip.AddrPort) {
defer func() { defer func() {
sender.Close() sender.Close()
_ = pc.Close() _ = pc.Close()
@@ -141,22 +187,17 @@ func handleUDPToLocal(writeBack C.WriteBack, pc N.EnhancePacketConn, sender C.Pa
} else if fromUDPAddr == nil { } else if fromUDPAddr == nil {
fromUDPAddr = net.UDPAddrFromAddrPort(oAddrPort) // oAddrPort was Unmapped fromUDPAddr = net.UDPAddrFromAddrPort(oAddrPort) // oAddrPort was Unmapped
log.Warnln("server return a nil *net.UDPAddr, force replace to (%s), this may be caused by a wrongly implemented server", oAddrPort) log.Warnln("server return a nil *net.UDPAddr, force replace to (%s), this may be caused by a wrongly implemented server", oAddrPort)
} else {
_fromUDPAddr := *fromUDPAddr
fromUDPAddr = &_fromUDPAddr // make a copy
if fromAddr, ok := netip.AddrFromSlice(fromUDPAddr.IP); ok {
fromAddr = fromAddr.Unmap()
if fAddr.IsValid() && (oAddrPort.Addr() == fromAddr) { // oAddrPort was Unmapped
fromAddr = fAddr.Unmap()
}
fromUDPAddr.IP = fromAddr.AsSlice()
if fromAddr.Is4() {
fromUDPAddr.Zone = "" // only ipv6 can have the zone
}
}
} }
_, err = writeBack.WriteBack(data, fromUDPAddr) fromAddrPort := fromUDPAddr.AddrPort()
fromAddr := fromAddrPort.Addr().Unmap()
// restore DestinationNAT
fromAddr = sender.RestoreReadFrom(fromAddr).Unmap()
fromAddrPort = netip.AddrPortFrom(fromAddr, fromAddrPort.Port())
_, err = writeBack.WriteBack(data, net.UDPAddrFromAddrPort(fromAddrPort))
if put != nil { if put != nil {
put() put()
} }

View File

@@ -3,7 +3,6 @@ package statistic
import ( import (
"io" "io"
"net" "net"
"net/netip"
"time" "time"
"github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/atomic"
@@ -116,20 +115,8 @@ func (tt *tcpTracker) Upstream() any {
return tt.Conn return tt.Conn
} }
func parseRemoteDestination(addr net.Addr, conn C.Connection) string {
if addr != nil {
if addrPort, err := netip.ParseAddrPort(addr.String()); err == nil && addrPort.Addr().IsValid() {
return addrPort.Addr().String()
}
}
if conn != nil {
return conn.RemoteDestination()
}
return ""
}
func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64, pushToManager bool) *tcpTracker { func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64, pushToManager bool) *tcpTracker {
metadata.RemoteDst = parseRemoteDestination(conn.RemoteAddr(), conn) metadata.RemoteDst = conn.RemoteDestination()
t := &tcpTracker{ t := &tcpTracker{
Conn: conn, Conn: conn,
@@ -220,7 +207,7 @@ func (ut *udpTracker) Upstream() any {
} }
func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64, pushToManager bool) *udpTracker { func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64, pushToManager bool) *udpTracker {
metadata.RemoteDst = parseRemoteDestination(nil, conn) metadata.RemoteDst = conn.RemoteDestination()
ut := &udpTracker{ ut := &udpTracker{
PacketConn: conn, PacketConn: conn,

View File

@@ -287,17 +287,21 @@ func isHandle(t C.Type) bool {
return status == Running || (status == Inner && t == C.INNER) return status == Running || (status == Inner && t == C.INNER)
} }
func fixMetadata(metadata *C.Metadata) {
// first unmap dstIP
metadata.DstIP = metadata.DstIP.Unmap()
// handle IP string on host
if ip, err := netip.ParseAddr(metadata.Host); err == nil {
metadata.DstIP = ip.Unmap()
metadata.Host = ""
}
}
func needLookupIP(metadata *C.Metadata) bool { func needLookupIP(metadata *C.Metadata) bool {
return resolver.MappingEnabled() && metadata.Host == "" && metadata.DstIP.IsValid() return resolver.MappingEnabled() && metadata.Host == "" && metadata.DstIP.IsValid()
} }
func preHandleMetadata(metadata *C.Metadata) error { func preHandleMetadata(metadata *C.Metadata) error {
// handle IP string on host
if ip, err := netip.ParseAddr(metadata.Host); err == nil {
metadata.DstIP = ip
metadata.Host = ""
}
// preprocess enhanced-mode metadata // preprocess enhanced-mode metadata
if needLookupIP(metadata) { if needLookupIP(metadata) {
host, exist := resolver.FindHostByIP(metadata.DstIP) host, exist := resolver.FindHostByIP(metadata.DstIP)
@@ -365,14 +369,9 @@ func handleUDPConn(packet C.PacketAdapter) {
log.Warnln("[Metadata] not valid: %#v", metadata) log.Warnln("[Metadata] not valid: %#v", metadata)
return return
} }
fixMetadata(metadata) // fix some metadata not set via metadata.SetRemoteAddr or metadata.SetRemoteAddress
// make a fAddr if request ip is fakeip if err := preHandleMetadata(metadata.Clone()); err != nil { // precheck without modify metadata
var fAddr netip.Addr
if resolver.IsExistFakeIP(metadata.DstIP) {
fAddr = metadata.DstIP
}
if err := preHandleMetadata(metadata); err != nil {
packet.Drop() packet.Drop()
log.Debugln("[Metadata PreHandle] error: %s", err) log.Debugln("[Metadata PreHandle] error: %s", err)
return return
@@ -388,21 +387,27 @@ func handleUDPConn(packet C.PacketAdapter) {
}) })
if !loaded { if !loaded {
dial := func() (C.PacketConn, C.WriteBackProxy, error) { dial := func() (C.PacketConn, C.WriteBackProxy, error) {
if err := sender.ResolveUDP(metadata); err != nil { originMetadata := metadata // save origin metadata
log.Warnln("[UDP] Resolve Ip error: %s", err) metadata = metadata.Clone() // don't modify PacketAdapter's metadata
if err := sender.DoSniff(metadata); err != nil {
log.Warnln("[UDP] DoSniff error: %s", err.Error())
return nil, nil, err return nil, nil, err
} }
_ = preHandleMetadata(metadata) // error was pre-checked
proxy, rule, err := resolveMetadata(metadata) proxy, rule, err := resolveMetadata(metadata)
if err != nil { if err != nil {
log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
return nil, nil, err return nil, nil, err
} }
dialMetadata := metadata.Pure()
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout)
defer cancel() defer cancel()
rawPc, err := retry(ctx, func(ctx context.Context) (C.PacketConn, error) { rawPc, err := retry(ctx, func(ctx context.Context) (C.PacketConn, error) {
return proxy.ListenPacketContext(ctx, metadata.Pure()) return proxy.ListenPacketContext(ctx, dialMetadata)
}, func(err error) { }, func(err error) {
logMetadataErr(metadata, rule, proxy, err) logMetadataErr(metadata, rule, proxy, err)
}) })
@@ -413,15 +418,11 @@ func handleUDPConn(packet C.PacketAdapter) {
pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule, 0, 0, true) pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule, 0, 0, true)
if rawPc.Chains().Last() == "REJECT-DROP" { sender.AddMapping(originMetadata, dialMetadata)
_ = pc.Close() oAddrPort := dialMetadata.AddrPort()
return nil, nil, errors.New("rejected drop packet")
}
oAddrPort := metadata.AddrPort()
writeBackProxy := nat.NewWriteBackProxy(packet) writeBackProxy := nat.NewWriteBackProxy(packet)
go handleUDPToLocal(writeBackProxy, pc, sender, key, oAddrPort, fAddr) go handleUDPToLocal(writeBackProxy, pc, sender, key, oAddrPort)
return pc, writeBackProxy, nil return pc, writeBackProxy, nil
} }
@@ -453,6 +454,7 @@ func handleTCPConn(connCtx C.ConnContext) {
log.Warnln("[Metadata] not valid: %#v", metadata) log.Warnln("[Metadata] not valid: %#v", metadata)
return return
} }
fixMetadata(metadata) // fix some metadata not set via metadata.SetRemoteAddr or metadata.SetRemoteAddress
preHandleFailed := false preHandleFailed := false
if err := preHandleMetadata(metadata); err != nil { if err := preHandleMetadata(metadata); err != nil {