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

View File

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

View File

@@ -2,7 +2,6 @@ package outbound
import (
"context"
"errors"
"net"
"strconv"
"time"
@@ -10,7 +9,6 @@ import (
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls"
"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) {
if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
// create tcp
c, err := t.client.CreateProxy(ctx, uot.RequestDestination(2))
if err != nil {
@@ -60,13 +62,6 @@ func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
}
// 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())
return newPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil
}

View File

@@ -3,15 +3,16 @@ package outbound
import (
"context"
"encoding/json"
"fmt"
"net"
"runtime"
"strings"
"sync"
"syscall"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
)
@@ -19,6 +20,7 @@ import (
type ProxyAdapter interface {
C.ProxyAdapter
DialOptions() []dialer.Option
ResolveUDP(ctx context.Context, metadata *C.Metadata) error
}
type Base struct {
@@ -160,6 +162,17 @@ func (b *Base) DialOptions() (opts []dialer.Option) {
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 {
return nil
}
@@ -203,12 +216,21 @@ func NewBase(opt BaseOption) *Base {
type conn struct {
N.ExtendedConn
chain C.Chain
actualRemoteDestination string
chain C.Chain
adapterAddr 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
@@ -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
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 {
N.EnhancePacketConn
chain C.Chain
adapterName string
connID string
actualRemoteDestination string
chain C.Chain
adapterName string
connID 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 {
return c.actualRemoteDestination
host, _, _ := net.SplitHostPort(c.adapterAddr)
return host
}
// 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
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
func newPacketConn(pc net.PacketConn, a ProxyAdapter) C.PacketConn {
epc := N.NewEnhancePacketConn(pc)
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
}
return &packetConn{epc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), parseRemoteDestination(a.Addr())}
}
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 ""
}
}
return &packetConn{epc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), a.Addr(), a.ResolveUDP}
}
type AddRef interface {

View File

@@ -2,7 +2,8 @@ package outbound
import (
"context"
"errors"
"fmt"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/loopback"
"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 {
return nil, err
}
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DirectHostResolver)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err := d.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
pc, err := dialer.NewDialer(d.DialOptions()...).ListenPacket(ctx, "udp", "", metadata.AddrPort())
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
}
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 {
return true // tell DNSDialer don't send domain to DialContext, avoid lookback to DefaultResolver
}

View File

@@ -3,6 +3,7 @@ package outbound
import (
"context"
"net"
"net/netip"
"time"
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
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())
if err := d.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
@@ -41,6 +45,13 @@ func (d *Dns) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.
}, 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 {
data []byte
put func()

View File

@@ -1,10 +1,12 @@
package outbound
import (
"context"
"encoding/base64"
"fmt"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/resolver"
)
type ECHOptions struct {
@@ -22,7 +24,13 @@ func (o ECHOptions) Parse() (*ech.Config, error) {
if err != nil {
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
}

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) {
if err := h.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
udpConn, err := h.client.DialUDP(h.genHdc(ctx))
if err != nil {
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) {
if err = h.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
pc, err := h.client.ListenPacket(ctx)
if err != nil {
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
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 {
return nil, err
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"io"
"net"
"net/netip"
"time"
"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
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
}
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 {
return &Reject{
Base: &Base{

View File

@@ -2,7 +2,6 @@ package outbound
import (
"context"
"errors"
"fmt"
"net"
"strconv"
@@ -11,7 +10,6 @@ import (
"github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
gost "github.com/metacubex/mihomo/transport/gost-plugin"
"github.com/metacubex/mihomo/transport/restls"
@@ -202,6 +200,9 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
return nil, err
}
}
if err = ss.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer)
if err != nil {
return nil, err
@@ -230,15 +231,9 @@ func (ss *ShadowSocks) ProxyInfo() C.ProxyInfo {
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
if ss.option.UDPOverTCP {
// ss uot 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
if err = ss.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
destination := M.SocksaddrFromNet(metadata.UDPAddr())
if ss.option.UDPOverTCPVersion == uot.LegacyVersion {
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
}
}
if err = ssr.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
addr, err := resolveUDPAddr(ctx, "udp", ssr.addr, ssr.prefer)
if err != nil {
return nil, err

View File

@@ -2,12 +2,10 @@ package outbound
import (
"context"
"errors"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
@@ -53,16 +51,9 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
if s.onlyTcp {
return s.ProxyAdapter.ListenPacketContext(ctx, metadata)
}
// sing-mux 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
if err = s.ProxyAdapter.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
pc, err := s.client.ListenPacket(ctx, M.SocksaddrFromNet(metadata.UDPAddr()))
if err != nil {
return nil, err

View File

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

View File

@@ -109,6 +109,9 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata)
return nil, err
}
}
if err = ss.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := cDialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
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
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
// grpc transport
@@ -250,6 +254,9 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
return nil, err
}
}
if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@@ -271,12 +278,6 @@ func (t *Trojan) SupportWithDialer() C.NetWork {
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
func (t *Trojan) SupportUOT() bool {
return true

View File

@@ -3,7 +3,6 @@ package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"math"
"net"
@@ -14,7 +13,6 @@ import (
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/tuic"
@@ -91,6 +89,10 @@ func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_
// ListenPacketWithDialer implements C.ProxyAdapter
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 {
uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion))
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
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())
if t.option.UDPOverStreamVersion == uot.LegacyVersion {

View File

@@ -19,7 +19,6 @@ import (
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"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
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 !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
var c net.Conn
// 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 !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := dialer.DialContext(ctx, "tcp", v.addr)
@@ -347,13 +336,8 @@ func (v *Vless) SupportWithDialer() C.NetWork {
// ListenPacketOnStreamConn implements C.ProxyAdapter
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 !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
if v.option.XUDP {

View File

@@ -17,7 +17,6 @@ import (
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/ntp"
@@ -330,13 +329,8 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
// ListenPacketContext implements C.ProxyAdapter
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 !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
var c net.Conn
// 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 !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
c, err := dialer.DialContext(ctx, "tcp", v.addr)
@@ -413,13 +402,8 @@ func (v *Vmess) Close() error {
// ListenPacketOnStreamConn implements C.ProxyAdapter
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 !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = v.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
if pc, ok := c.(net.PacketConn); ok {

View File

@@ -4,7 +4,6 @@ import (
"context"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"net"
"net/netip"
@@ -520,16 +519,8 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
if err = w.init(ctx); err != nil {
return nil, err
}
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 nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
if err = w.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
if err != nil {
@@ -541,6 +532,21 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
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
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
return true

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,24 +4,20 @@ import (
"context"
"fmt"
"github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/metacubex/mihomo/component/tls"
)
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) {
if cfg == nil {
return nil
}
echConfigList := cfg.EncryptedClientHelloConfigList
if len(echConfigList) == 0 {
echConfigList, err = resolver.ResolveECH(ctx, tlsConfig.ServerName)
if err != nil {
return fmt.Errorf("resolve ECH config error: %w", err)
}
echConfigList, err := cfg.GetEncryptedClientHelloConfigList(ctx, tlsConfig.ServerName)
if err != nil {
return fmt.Errorf("resolve ECH config error: %w", err)
}
tlsConfig.EncryptedClientHelloConfigList = echConfigList

View File

@@ -26,8 +26,9 @@ var (
)
type ifaceCache struct {
ifMap map[string]*Interface
ifTable bart.Table[*Interface]
ifMapByName map[string]*Interface
ifMapByAddr map[netip.Addr]*Interface
ifTable bart.Table[*Interface]
}
var caches = singledo.NewSingle[*ifaceCache](time.Second * 20)
@@ -40,7 +41,8 @@ func getCache() (*ifaceCache, error) {
}
cache := &ifaceCache{
ifMap: make(map[string]*Interface),
ifMapByName: make(map[string]*Interface),
ifMapByAddr: make(map[netip.Addr]*Interface),
}
for _, iface := range ifaces {
@@ -78,12 +80,13 @@ func getCache() (*ifaceCache, error) {
Flags: iface.Flags,
Addresses: ipNets,
}
cache.ifMap[iface.Name] = ifaceObj
cache.ifMapByName[iface.Name] = ifaceObj
if iface.Flags&net.FlagUp == 0 {
continue // interface down
}
for _, prefix := range ipNets {
cache.ifMapByAddr[prefix.Addr()] = ifaceObj
cache.ifTable.Insert(prefix, ifaceObj)
}
}
@@ -98,7 +101,7 @@ func Interfaces() (map[string]*Interface, error) {
if err != nil {
return nil, err
}
return cache.ifMap, nil
return cache.ifMapByName, nil
}
func ResolveInterface(name string) (*Interface, error) {
@@ -120,6 +123,11 @@ func ResolveInterfaceByAddr(addr netip.Addr) (*Interface, error) {
if err != nil {
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)
if !ok {
return nil, ErrIfaceNotFound
@@ -133,7 +141,8 @@ func IsLocalIp(addr netip.Addr) (bool, error) {
if err != nil {
return false, err
}
return cache.ifTable.Contains(addr), nil
_, ok := cache.ifMapByAddr[addr]
return ok, nil
}
func FlushCache() {

View File

@@ -2,7 +2,6 @@ package proxydialer
import (
"context"
"errors"
"fmt"
"net"
"net/netip"
@@ -10,7 +9,6 @@ import (
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic"
@@ -40,17 +38,16 @@ func (p proxyDialer) DialContext(ctx context.Context, network, address string) (
return nil, err
}
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)
if err != nil {
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
}
var conn C.Conn

View File

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

View File

@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"net/netip"
"strings"
"time"
"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)
if err == nil {
if ip.Is4() || ip.Is4In6() {
ip = ip.Unmap()
if ip.Is4() {
return []netip.Addr{ip}, nil
}
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 strings.Contains(host, ":") {
ip = ip.Unmap()
if ip.Is6() {
return []netip.Addr{ip}, nil
}
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 {
ip = ip.Unmap()
return []netip.Addr{ip}, nil
}

View File

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

View File

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

View File

@@ -12,7 +12,7 @@ import (
)
type fakeSender struct {
resultCh chan *constant.Metadata
constant.PacketSender
}
var _ constant.PacketSender = (*fakeSender)(nil)
@@ -22,18 +22,7 @@ func (e *fakeSender) Send(packet constant.PacketAdapter) {
packet.Drop()
}
func (e *fakeSender) Process(constant.PacketConn, constant.WriteBackProxy) {
panic("not implemented")
}
func (e *fakeSender) ResolveUDP(metadata *constant.Metadata) error {
e.resultCh <- metadata
return nil
}
func (e *fakeSender) Close() {
panic("not implemented")
}
func (e *fakeSender) DoSniff(metadata *constant.Metadata) error { return nil }
type fakeUDPPacket struct {
data []byte
@@ -78,23 +67,28 @@ func asPacket(data string) constant.PacketAdapter {
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{})
if err != nil {
return "", err
return "", "", err
}
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() {
meta := constant.Metadata{}
err = sender.ResolveUDP(&meta)
meta := constant.Metadata{Host: fakeHost}
err := sender.DoSniff(&meta)
if err != nil {
panic(err)
}
resultCh <- &meta
}()
for _, d := range data {
@@ -106,14 +100,15 @@ func testQuicSniffer(data []string, async bool) (string, error) {
}
meta := <-resultCh
return meta.SniffHost, nil
return meta.SniffHost, meta.Host, nil
}
func TestQuicHeaders(t *testing.T) {
cases := []struct {
input []string
domain string
input []string
domain string
invalid bool
}{
//Normal domain quic sniff
{
@@ -171,16 +166,31 @@ func TestQuicHeaders(t *testing.T) {
},
domain: "www.google.com",
},
// invalid packet
{
input: []string{"00000000000000000000"},
invalid: true,
},
}
for _, test := range cases {
data, err := testQuicSniffer(test.input, true)
data, host, err := testQuicSniffer(test.input, true)
assert.NoError(t, err)
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.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"
"net"
"net/http"
"runtime/debug"
"time"
N "github.com/metacubex/mihomo/common/net"
@@ -63,6 +64,7 @@ func NewListenerForHttps(l net.Listener, httpServer *http.Server, tlsConfig *Con
}
return c, nil
}, 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 {
N.EnhancePacketConn
Connection
// Deprecate WriteWithMetadata because of remote resolve DNS cause TURN failed
// WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error)
ResolveUDP(ctx context.Context, metadata *Metadata) error
}
type Dialer interface {
@@ -319,10 +318,15 @@ type PacketSender interface {
Send(PacketAdapter)
// Process is a blocking loop to send PacketAdapter to PacketConn and update the 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()
// 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 {

View File

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

View File

@@ -10,8 +10,10 @@ type Sniffer interface {
SupportPort(port uint16) bool
}
type ReplaceDomain func(metadata *constant.Metadata, host string)
type MultiPacketSniffer interface {
WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender
WrapperSender(packetSender constant.PacketSender, replaceDomain ReplaceDomain) constant.PacketSender
}
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) {
ip, err := netip.ParseAddr(host)
if err == nil {
isIPv4 := ip.Is4() || ip.Is4In6()
ip = ip.Unmap()
isIPv4 := ip.Is4()
if dnsType == D.TypeAAAA && !isIPv4 {
return []netip.Addr{ip}, nil
} 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/fswatch v0.1.1
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/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-quic v0.0.0-20250520025433-6e556a6bef7a
github.com/metacubex/sing-shadowsocks v0.2.9
github.com/metacubex/sing-shadowsocks2 v0.2.3
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f
github.com/metacubex/sing-shadowsocks v0.2.10
github.com/metacubex/sing-shadowsocks2 v0.2.4
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-vmess v0.2.1
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c
github.com/metacubex/sing-vmess v0.2.2
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
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/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/quic-go v0.51.1-0.20250511032541-4e34341cf18b h1:8oDU32eJ+RRhl1FodGgPfxQxtoBAiD9D40XG2XtU/SE=
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 h1:L+1brQNzBhCCxWlicwfK1TlceemCRmrDE4HmcVHc29w=
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/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
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-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.3 h1:QWdN16WFKMk06x4nzkc8SvZ7y2x+TLQrpkPoHs+WSVM=
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/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-20250520025433-6e556a6bef7a/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=
github.com/metacubex/sing-shadowsocks2 v0.2.3/go.mod h1:/WNy/Q8ahLCoPRriWuFZFD0Jy+JNp1MEQl28Zw6SaF8=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I=
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-shadowsocks v0.2.10 h1:Pr7LDbjMANIQHl07zWgl1vDuhpsfDQUpZ8cX6DPabfg=
github.com/metacubex/sing-shadowsocks v0.2.10/go.mod h1:MtRM0ZZjR0kaDOzy9zWSt6/4/UlrnsNBq+1FNAF4vBk=
github.com/metacubex/sing-shadowsocks2 v0.2.4 h1:Ec0x3hHR7xkld5Z09IGh16wtUUpBb2HgqZ9DExd8Q7s=
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/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.20250503065609-efb9f0beb6f6/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
github.com/metacubex/sing-vmess v0.2.1 h1:I6gM3VUjtvJ15D805EUbNH+SRBuqzJeFnuIbKYUsWZ0=
github.com/metacubex/sing-vmess v0.2.1/go.mod h1:DsODWItJtOMZNna8Qhheg8r3tUivrcO3vWgaTYKnfTo=
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c h1:Y6jk7AH5BEg9Dsvczrf/KokYsvxeKSZZlCLHg+hC4ro=
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
github.com/metacubex/sing-vmess v0.2.2 h1:nG6GIKF1UOGmlzs+BIetdGHkFZ20YqFVIYp5Htqzp+4=
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/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=

View File

@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"net"
"runtime/debug"
"time"
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.
return realityConnWrapper{c}, nil
}, 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 (
"context"
"golang.org/x/exp/slices"
"net"
"github.com/metacubex/mihomo/adapter/inbound"
@@ -29,3 +30,18 @@ func getAdditions(ctx context.Context) (additions []inbound.Addition) {
}
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"
vmess "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing/common"
"github.com/metacubex/sing/common/buf"
"github.com/metacubex/sing/common/bufio"
"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 {
defer func() { _ = conn.Close() }()
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() {
mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running
defer mutex.Unlock()
conn2 = nil
writer = nil
}()
rwOptions := network.ReadWaitOptions{}
readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn)
@@ -180,32 +181,59 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
return err
}
cPacket := &packet{
conn: &conn2,
mutex: &mutex,
rAddr: metadata.Source.UDPAddr(),
lAddr: conn.LocalAddr(),
buff: buff,
writer: &writer,
mutex: &mutex,
rAddr: metadata.Source.UDPAddr(),
lAddr: conn.LocalAddr(),
buff: buff,
}
cMetadata := &C.Metadata{
NetWork: C.UDP,
Type: h.Type,
if lAddr := getInAddr(ctx); lAddr != nil {
cPacket.lAddr = lAddr
}
if metadata.Source.IsIP() && metadata.Source.Fqdn == "" {
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)
h.handlePacket(ctx, cPacket, metadata.Source, dest)
}
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) {
log.Warnln("%s listener get error: %+v", h.Type.String(), err)
}
@@ -225,11 +253,11 @@ func ShouldIgnorePacketError(err error) bool {
}
type packet struct {
conn *network.NetPacketConn
mutex *sync.Mutex
rAddr net.Addr
lAddr net.Addr
buff *buf.Buffer
writer *network.NetPacketWriter
mutex *sync.Mutex
rAddr net.Addr
lAddr net.Addr
buff *buf.Buffer
}
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()
defer c.mutex.Unlock()
conn := *c.conn
conn := *c.writer
if conn == nil {
err = errors.New("writeBack to closed connection")
return

View File

@@ -156,11 +156,7 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi
go func() {
conn := bufio.NewPacketConn(ul)
rwOptions := network.ReadWaitOptions{
FrontHeadroom: network.CalculateFrontHeadroom(sl.service),
RearHeadroom: network.CalculateRearHeadroom(sl.service),
MTU: network.CalculateMTU(conn, sl.service),
}
rwOptions := network.NewReadWaitOptions(conn, sl.service)
readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn)
if isReadWaiter {
readWaiter.InitializeReadWaiter(rwOptions)
@@ -188,7 +184,9 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi
}
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",
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)
}
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 {
if h.ShouldHijackDns(metadata.Destination.AddrPort()) {
log.Debugln("[DNS] hijack udp:%s from %s", metadata.Destination.String(), metadata.Source.String())
defer func() { _ = conn.Close() }()
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() {
mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running
defer mutex.Unlock()
conn2 = nil
writer = nil
}()
rwOptions := network.ReadWaitOptions{
FrontHeadroom: network.CalculateFrontHeadroom(conn),
@@ -89,43 +104,47 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
}
return err
}
go func() {
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
}
}()
go relayDnsPacket(ctx, readBuff, rwOptions, dest, &mutex, &writer)
}
return nil
}
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 {
handle := *h
handle.ListenerHandler = h.ListenerHandler.TypeMutation(typ)

View File

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

View File

@@ -5,11 +5,10 @@ import (
"errors"
"net"
"net/netip"
"sync"
"time"
"github.com/metacubex/mihomo/common/lru"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
)
@@ -18,7 +17,11 @@ type packetSender struct {
ctx context.Context
cancel context.CancelFunc
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
@@ -30,10 +33,74 @@ func newPacketSender() C.PacketSender {
ctx: ctx,
cancel: cancel,
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) {
for {
select {
@@ -43,12 +110,7 @@ func (s *packetSender) Process(pc C.PacketConn, proxy C.WriteBackProxy) {
if proxy != nil {
proxy.UpdateWriteBack(packet)
}
if err := s.ResolveUDP(packet.Metadata()); err != nil {
log.Warnln("[UDP] Resolve Ip error: %s", err)
} else {
_ = handleUDPToRemote(packet, pc, packet.Metadata())
}
packet.Drop()
s.processPacket(pc, packet)
}
}
}
@@ -87,25 +149,9 @@ func (s *packetSender) Close() {
s.dropAll()
}
func (s *packetSender) ResolveUDP(metadata *C.Metadata) (err error) {
// 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)
}
func (s *packetSender) DoSniff(metadata *C.Metadata) error { return nil }
metadata.DstIP = ip
}
return nil
}
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error {
addr := metadata.UDPAddr()
func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, addr *net.UDPAddr) error {
if addr == nil {
return errors.New("udp addr invalid")
}
@@ -119,7 +165,7 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata
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() {
sender.Close()
_ = pc.Close()
@@ -141,22 +187,17 @@ func handleUDPToLocal(writeBack C.WriteBack, pc N.EnhancePacketConn, sender C.Pa
} else if fromUDPAddr == nil {
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)
} 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 {
put()
}

View File

@@ -3,7 +3,6 @@ package statistic
import (
"io"
"net"
"net/netip"
"time"
"github.com/metacubex/mihomo/common/atomic"
@@ -116,20 +115,8 @@ func (tt *tcpTracker) Upstream() any {
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 {
metadata.RemoteDst = parseRemoteDestination(conn.RemoteAddr(), conn)
metadata.RemoteDst = conn.RemoteDestination()
t := &tcpTracker{
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 {
metadata.RemoteDst = parseRemoteDestination(nil, conn)
metadata.RemoteDst = conn.RemoteDestination()
ut := &udpTracker{
PacketConn: conn,

View File

@@ -287,17 +287,21 @@ func isHandle(t C.Type) bool {
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 {
return resolver.MappingEnabled() && metadata.Host == "" && metadata.DstIP.IsValid()
}
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
if needLookupIP(metadata) {
host, exist := resolver.FindHostByIP(metadata.DstIP)
@@ -365,14 +369,9 @@ func handleUDPConn(packet C.PacketAdapter) {
log.Warnln("[Metadata] not valid: %#v", metadata)
return
}
fixMetadata(metadata) // fix some metadata not set via metadata.SetRemoteAddr or metadata.SetRemoteAddress
// make a fAddr if request ip is fakeip
var fAddr netip.Addr
if resolver.IsExistFakeIP(metadata.DstIP) {
fAddr = metadata.DstIP
}
if err := preHandleMetadata(metadata); err != nil {
if err := preHandleMetadata(metadata.Clone()); err != nil { // precheck without modify metadata
packet.Drop()
log.Debugln("[Metadata PreHandle] error: %s", err)
return
@@ -388,21 +387,27 @@ func handleUDPConn(packet C.PacketAdapter) {
})
if !loaded {
dial := func() (C.PacketConn, C.WriteBackProxy, error) {
if err := sender.ResolveUDP(metadata); err != nil {
log.Warnln("[UDP] Resolve Ip error: %s", err)
originMetadata := metadata // save origin metadata
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
}
_ = preHandleMetadata(metadata) // error was pre-checked
proxy, rule, err := resolveMetadata(metadata)
if err != nil {
log.Warnln("[UDP] Parse metadata failed: %s", err.Error())
return nil, nil, err
}
dialMetadata := metadata.Pure()
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout)
defer cancel()
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) {
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)
if rawPc.Chains().Last() == "REJECT-DROP" {
_ = pc.Close()
return nil, nil, errors.New("rejected drop packet")
}
oAddrPort := metadata.AddrPort()
sender.AddMapping(originMetadata, dialMetadata)
oAddrPort := dialMetadata.AddrPort()
writeBackProxy := nat.NewWriteBackProxy(packet)
go handleUDPToLocal(writeBackProxy, pc, sender, key, oAddrPort, fAddr)
go handleUDPToLocal(writeBackProxy, pc, sender, key, oAddrPort)
return pc, writeBackProxy, nil
}
@@ -453,6 +454,7 @@ func handleTCPConn(connCtx C.ConnContext) {
log.Warnln("[Metadata] not valid: %#v", metadata)
return
}
fixMetadata(metadata) // fix some metadata not set via metadata.SetRemoteAddr or metadata.SetRemoteAddress
preHandleFailed := false
if err := preHandleMetadata(metadata); err != nil {