Compare commits

...

52 Commits

Author SHA1 Message Date
wwqgtxx
5b975275f5 fix: incorrect checking of strings.Split return value
strings.Split will never return a slice of length 0 if sep is not empty, so any code that checks if the return value is of length 0 is incorrect and useless.
2025-06-25 16:20:37 +08:00
ayanamist
166392fe17 chore: sniffer replace domain only if domain is valid (#2122) 2025-06-24 21:44:26 +08:00
ayanamist
5c6aa433ca chore: unconditionally allow clients with passwords for password-free socks5 inbound (#2123) 2025-06-24 19:01:12 +08:00
xishang0128
2c55dc2557 action: fix run build on pull_request 2025-06-24 19:01:03 +08:00
wwqgtxx
56c0b088e8 doc: update path doc 2025-06-21 22:46:55 +08:00
Restia-Ashbell
5344e869a8 fix: ssr uri decode (#2116) 2025-06-21 12:19:13 +08:00
wwqgtxx
6cfaf15cbf fix: missing error return 2025-06-21 12:08:41 +08:00
wwqgtxx
31f0060b30 fix: chacha20 counter overflow
the implement it's a not safe chacha20 using but for compatible
2025-06-21 10:42:14 +08:00
wwqgtxx
c60750d549 chore: allow tun to skip the system ipv6 check when starting by environment variable SKIP_SYSTEM_IPV6_CHECK 2025-06-14 15:57:54 +08:00
wwqgtxx
ebf5918e94 fix: v2ray-plugin mux maybe not close underlay connection 2025-06-14 12:32:45 +08:00
riolurs
93ca18517c chore: converter support fingerprint for anytls 2025-06-13 23:05:06 +08:00
beck
32d447ce99 fix: convert https (#2102) 2025-06-12 17:10:09 +08:00
beck
617fef84ae feat: converter support anytls/socks/http (#2100) 2025-06-12 16:17:25 +08:00
wwqgtxx
d19199322d action: don't trigger cmfa update on pull request 2025-06-12 15:33:19 +08:00
wwqgtxx
87795e3a07 chore: add yaml marshal for common/atomic 2025-06-12 15:24:29 +08:00
wwqgtxx
85bb40aaf8 chore: add Int32Enum for common/atomic 2025-06-12 15:24:29 +08:00
wwqgtxx
082bcec281 chore: apply find process mode in direct/global mode 2025-06-12 00:27:51 +08:00
wwqgtxx
9283cb0f5f feat: add loopback-address support for tun 2025-06-11 17:45:28 +08:00
wwqgtxx
ae7967f662 chore: the resolve and findProcess behaviors of Logic and SubRules follow the order and needs of the internal rules 2025-06-10 20:11:50 +08:00
wwqgtxx
01f8f2db2f chore: cleanup allocator code 2025-06-10 10:54:08 +08:00
wwqgtxx
255ff5e977 chore: add rate limiting support for reality listener 2025-06-10 10:40:26 +08:00
wwqgtxx
939e4109d7 chore: write dns reply in single syscall 2025-06-07 00:38:39 +08:00
wwqgtxx
40587b62b8 feat: all dns client support skip-cert-verify params 2025-06-06 00:52:12 +08:00
wwqgtxx
85e6d25de5 feat: all dns client support ecs and ecs-override params 2025-06-06 00:45:58 +08:00
wwqgtxx
29a37f4f4b feat: all dns client support disable-ipv4 and disable-ipv6 params 2025-06-06 00:24:57 +08:00
wwqgtxx
2f9a3b3469 chore: cleanup code 2025-06-05 21:20:38 +08:00
wwqgtxx
40ea0ba098 fix: correct constructor for 2022-blake3-chacha8-poly1305 2025-06-05 13:47:26 +08:00
wwqgtxx
8d7f947a80 fix: TypedValue.CompareAndSwap
84aa7ff3bb
2025-06-05 13:43:30 +08:00
wwqgtxx
71a8705636 fix: remote dst parse 2025-05-31 22:57:05 +08:00
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
123 changed files with 1583 additions and 1323 deletions

View File

@@ -198,10 +198,11 @@ jobs:
- name: Set variables - name: Set variables
run: | run: |
VERSION="${GITHUB_REF_NAME,,}-$(git rev-parse --short HEAD)" VERSION="${GITHUB_REF_NAME,,}-$(git rev-parse --short HEAD)"
VERSION="${VERSION//\//-}"
PackageVersion="$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | jq -r '.tag_name' | sed 's/v//g' | awk -F '.' '{$NF = $NF + 1; print}' OFS='.').${VERSION/-/.}" PackageVersion="$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | jq -r '.tag_name' | sed 's/v//g' | awk -F '.' '{$NF = $NF + 1; print}' OFS='.').${VERSION/-/.}"
if [ -n "${{ github.event.inputs.version }}" ]; then if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION=${{ github.event.inputs.version }} VERSION=${{ github.event.inputs.version }}
PackageVersion="${VERSION#v}" >> $GITHUB_ENV PackageVersion="${VERSION#v}"
fi fi
echo "VERSION=${VERSION}" >> $GITHUB_ENV echo "VERSION=${VERSION}" >> $GITHUB_ENV
echo "PackageVersion=${PackageVersion}" >> $GITHUB_ENV echo "PackageVersion=${PackageVersion}" >> $GITHUB_ENV

View File

@@ -10,9 +10,6 @@ on:
- Alpha - Alpha
tags: tags:
- "v*" - "v*"
pull_request_target:
branches:
- Alpha
jobs: jobs:
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies # Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies

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

63
common/atomic/enum.go Normal file
View File

@@ -0,0 +1,63 @@
package atomic
import (
"encoding/json"
"fmt"
"sync/atomic"
)
type Int32Enum[T ~int32] struct {
value atomic.Int32
}
func (i *Int32Enum[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
func (i *Int32Enum[T]) UnmarshalJSON(b []byte) error {
var v T
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int32Enum[T]) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Int32Enum[T]) UnmarshalYAML(unmarshal func(any) error) error {
var v T
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int32Enum[T]) String() string {
return fmt.Sprint(i.Load())
}
func (i *Int32Enum[T]) Store(v T) {
i.value.Store(int32(v))
}
func (i *Int32Enum[T]) Load() T {
return T(i.value.Load())
}
func (i *Int32Enum[T]) Swap(new T) T {
return T(i.value.Swap(int32(new)))
}
func (i *Int32Enum[T]) CompareAndSwap(old, new T) bool {
return i.value.CompareAndSwap(int32(old), int32(new))
}
func NewInt32Enum[T ~int32](v T) *Int32Enum[T] {
a := &Int32Enum[T]{}
a.Store(v)
return a
}

View File

@@ -29,6 +29,19 @@ func (i *Bool) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Bool) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Bool) UnmarshalYAML(unmarshal func(any) error) error {
var v bool
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Bool) String() string { func (i *Bool) String() string {
v := i.Load() v := i.Load()
return strconv.FormatBool(v) return strconv.FormatBool(v)
@@ -58,6 +71,19 @@ func (p *Pointer[T]) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (p *Pointer[T]) MarshalYAML() (any, error) {
return p.Load(), nil
}
func (p *Pointer[T]) UnmarshalYAML(unmarshal func(any) error) error {
var v *T
if err := unmarshal(&v); err != nil {
return err
}
p.Store(v)
return nil
}
func (p *Pointer[T]) String() string { func (p *Pointer[T]) String() string {
return fmt.Sprint(p.Load()) return fmt.Sprint(p.Load())
} }
@@ -84,6 +110,19 @@ func (i *Int32) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Int32) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Int32) UnmarshalYAML(unmarshal func(any) error) error {
var v int32
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int32) String() string { func (i *Int32) String() string {
v := i.Load() v := i.Load()
return strconv.FormatInt(int64(v), 10) return strconv.FormatInt(int64(v), 10)
@@ -111,6 +150,19 @@ func (i *Int64) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Int64) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Int64) UnmarshalYAML(unmarshal func(any) error) error {
var v int64
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int64) String() string { func (i *Int64) String() string {
v := i.Load() v := i.Load()
return strconv.FormatInt(int64(v), 10) return strconv.FormatInt(int64(v), 10)
@@ -138,6 +190,19 @@ func (i *Uint32) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Uint32) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Uint32) UnmarshalYAML(unmarshal func(any) error) error {
var v uint32
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uint32) String() string { func (i *Uint32) String() string {
v := i.Load() v := i.Load()
return strconv.FormatUint(uint64(v), 10) return strconv.FormatUint(uint64(v), 10)
@@ -165,6 +230,19 @@ func (i *Uint64) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Uint64) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Uint64) UnmarshalYAML(unmarshal func(any) error) error {
var v uint64
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uint64) String() string { func (i *Uint64) String() string {
v := i.Load() v := i.Load()
return strconv.FormatUint(uint64(v), 10) return strconv.FormatUint(uint64(v), 10)
@@ -192,6 +270,19 @@ func (i *Uintptr) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (i *Uintptr) MarshalYAML() (any, error) {
return i.Load(), nil
}
func (i *Uintptr) UnmarshalYAML(unmarshal func(any) error) error {
var v uintptr
if err := unmarshal(&v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uintptr) String() string { func (i *Uintptr) String() string {
v := i.Load() v := i.Load()
return strconv.FormatUint(uint64(v), 10) return strconv.FormatUint(uint64(v), 10)

View File

@@ -27,11 +27,16 @@ type tValue[T any] struct {
} }
func (t *TypedValue[T]) Load() T { func (t *TypedValue[T]) Load() T {
value, _ := t.LoadOk()
return value
}
func (t *TypedValue[T]) LoadOk() (_ T, ok bool) {
value := t.value.Load() value := t.value.Load()
if value == nil { if value == nil {
return DefaultValue[T]() return DefaultValue[T](), false
} }
return value.(tValue[T]).value return value.(tValue[T]).value, true
} }
func (t *TypedValue[T]) Store(value T) { func (t *TypedValue[T]) Store(value T) {
@@ -47,7 +52,11 @@ func (t *TypedValue[T]) Swap(new T) T {
} }
func (t *TypedValue[T]) CompareAndSwap(old, new T) bool { func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{new}) return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{new}) ||
// In the edge-case where [atomic.Value.Store] is uninitialized
// and trying to compare with the zero value of T,
// then compare-and-swap with the nil any value.
(any(old) == any(DefaultValue[T]()) && t.value.CompareAndSwap(any(nil), tValue[T]{new}))
} }
func (t *TypedValue[T]) MarshalJSON() ([]byte, error) { func (t *TypedValue[T]) MarshalJSON() ([]byte, error) {
@@ -63,6 +72,19 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (t *TypedValue[T]) MarshalYAML() (any, error) {
return t.Load(), nil
}
func (t *TypedValue[T]) UnmarshalYAML(unmarshal func(any) error) error {
var v T
if err := unmarshal(&v); err != nil {
return err
}
t.Store(v)
return nil
}
func NewTypedValue[T any](t T) (v TypedValue[T]) { func NewTypedValue[T any](t T) (v TypedValue[T]) {
v.Store(t) v.Store(t)
return return

View File

@@ -0,0 +1,77 @@
package atomic
import (
"io"
"os"
"testing"
)
func TestTypedValue(t *testing.T) {
{
// Always wrapping should not allocate for simple values
// because tValue[T] has the same memory layout as T.
var v TypedValue[bool]
bools := []bool{true, false}
if n := int(testing.AllocsPerRun(1000, func() {
for _, b := range bools {
v.Store(b)
}
})); n != 0 {
t.Errorf("AllocsPerRun = %d, want 0", n)
}
}
{
var v TypedValue[int]
got, gotOk := v.LoadOk()
if got != 0 || gotOk {
t.Fatalf("LoadOk = (%v, %v), want (0, false)", got, gotOk)
}
v.Store(1)
got, gotOk = v.LoadOk()
if got != 1 || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (1, true)", got, gotOk)
}
}
{
var v TypedValue[error]
got, gotOk := v.LoadOk()
if got != nil || gotOk {
t.Fatalf("LoadOk = (%v, %v), want (nil, false)", got, gotOk)
}
v.Store(io.EOF)
got, gotOk = v.LoadOk()
if got != io.EOF || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (EOF, true)", got, gotOk)
}
err := &os.PathError{}
v.Store(err)
got, gotOk = v.LoadOk()
if got != err || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (%v, true)", got, gotOk, err)
}
v.Store(nil)
got, gotOk = v.LoadOk()
if got != nil || !gotOk {
t.Fatalf("LoadOk = (%v, %v), want (nil, true)", got, gotOk)
}
}
{
c1, c2, c3 := make(chan struct{}), make(chan struct{}), make(chan struct{})
var v TypedValue[chan struct{}]
if v.CompareAndSwap(c1, c2) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if v.CompareAndSwap(nil, c1) != true {
t.Fatalf("CompareAndSwap = false, want true")
}
if v.CompareAndSwap(c2, c3) != false {
t.Fatalf("CompareAndSwap = true, want false")
}
if v.CompareAndSwap(c1, c2) != true {
t.Fatalf("CompareAndSwap = false, want true")
}
}
}

View File

@@ -2,6 +2,7 @@ package convert
import ( import (
"encoding/base64" "encoding/base64"
"fmt"
"strings" "strings"
) )
@@ -43,3 +44,22 @@ func decodeUrlSafe(data string) string {
} }
return string(dcBuf) return string(dcBuf)
} }
func TryDecodeBase64(s string) (decoded []byte, err error) {
if len(s)%4 == 0 {
if decoded, err = base64.StdEncoding.DecodeString(s); err == nil {
return
}
if decoded, err = base64.URLEncoding.DecodeString(s); err == nil {
return
}
} else {
if decoded, err = base64.RawStdEncoding.DecodeString(s); err == nil {
return
}
if decoded, err = base64.RawURLEncoding.DecodeString(s); err == nil {
return
}
}
return nil, fmt.Errorf("invalid base64-encoded string")
}

View File

@@ -456,12 +456,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
proxies = append(proxies, ss) proxies = append(proxies, ss)
case "ssr": case "ssr":
dcBuf, err := encRaw.DecodeString(body) dcBuf, err := TryDecodeBase64(body)
if err != nil { if err != nil {
continue continue
} }
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64&protoparam=&remarks=urlsafebase64&group=urlsafebase64&udpport=0&uot=1 // ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64param&protoparam=urlsafebase64param&remarks=urlsafebase64remarks&group=urlsafebase64group&udpport=0&uot=1
before, after, ok := strings.Cut(string(dcBuf), "/?") before, after, ok := strings.Cut(string(dcBuf), "/?")
if !ok { if !ok {
@@ -490,7 +490,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
name := uniqueName(names, remarks) name := uniqueName(names, remarks)
obfsParam := decodeUrlSafe(query.Get("obfsparam")) obfsParam := decodeUrlSafe(query.Get("obfsparam"))
protocolParam := query.Get("protoparam") protocolParam := decodeUrlSafe(query.Get("protoparam"))
ssr := make(map[string]any, 20) ssr := make(map[string]any, 20)
@@ -513,6 +513,101 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
} }
proxies = append(proxies, ssr) proxies = append(proxies, ssr)
case "socks", "socks5", "socks5h", "http", "https":
link, err := url.Parse(line)
if err != nil {
continue
}
server := link.Hostname()
if server == "" {
continue
}
portStr := link.Port()
if portStr == "" {
continue
}
remarks := link.Fragment
if remarks == "" {
remarks = fmt.Sprintf("%s:%s", server, portStr)
}
name := uniqueName(names, remarks)
encodeStr := link.User.String()
var username, password string
if encodeStr != "" {
decodeStr := string(DecodeBase64([]byte(encodeStr)))
splitStr := strings.Split(decodeStr, ":")
// todo: should use url.QueryUnescape ?
username = splitStr[0]
if len(splitStr) == 2 {
password = splitStr[1]
}
}
socks := make(map[string]any, 10)
socks["name"] = name
socks["type"] = func() string {
switch scheme {
case "socks", "socks5", "socks5h":
return "socks5"
case "http", "https":
return "http"
}
return scheme
}()
socks["server"] = server
socks["port"] = portStr
socks["username"] = username
socks["password"] = password
socks["skip-cert-verify"] = true
if scheme == "https" {
socks["tls"] = true
}
proxies = append(proxies, socks)
case "anytls":
// https://github.com/anytls/anytls-go/blob/main/docs/uri_scheme.md
link, err := url.Parse(line)
if err != nil {
continue
}
username := link.User.Username()
password, exist := link.User.Password()
if !exist {
password = username
}
query := link.Query()
server := link.Hostname()
if server == "" {
continue
}
portStr := link.Port()
if portStr == "" {
continue
}
insecure, sni := query.Get("insecure"), query.Get("sni")
insecureBool := insecure == "1"
fingerprint := query.Get("hpkp")
remarks := link.Fragment
if remarks == "" {
remarks = fmt.Sprintf("%s:%s", server, portStr)
}
name := uniqueName(names, remarks)
anytls := make(map[string]any, 10)
anytls["name"] = name
anytls["type"] = "anytls"
anytls["server"] = server
anytls["port"] = portStr
anytls["username"] = username
anytls["password"] = password
anytls["sni"] = sni
anytls["fingerprint"] = fingerprint
anytls["skip-cert-verify"] = insecureBool
anytls["udp"] = true
proxies = append(proxies, anytls)
} }
} }

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

@@ -8,18 +8,23 @@ import (
"sync" "sync"
) )
var defaultAllocator = NewAllocator() var DefaultAllocator = NewAllocator()
// Allocator for incoming frames, optimized to prevent overwriting after zeroing type Allocator interface {
type Allocator struct { Get(size int) []byte
Put(buf []byte) error
}
// defaultAllocator for incoming frames, optimized to prevent overwriting after zeroing
type defaultAllocator struct {
buffers [11]sync.Pool buffers [11]sync.Pool
} }
// NewAllocator initiates a []byte allocator for frames less than 65536 bytes, // NewAllocator initiates a []byte allocator for frames less than 65536 bytes,
// the waste(memory fragmentation) of space allocation is guaranteed to be // the waste(memory fragmentation) of space allocation is guaranteed to be
// no more than 50%. // no more than 50%.
func NewAllocator() *Allocator { func NewAllocator() Allocator {
return &Allocator{ return &defaultAllocator{
buffers: [...]sync.Pool{ // 64B -> 64K buffers: [...]sync.Pool{ // 64B -> 64K
{New: func() any { return new([1 << 6]byte) }}, {New: func() any { return new([1 << 6]byte) }},
{New: func() any { return new([1 << 7]byte) }}, {New: func() any { return new([1 << 7]byte) }},
@@ -37,7 +42,7 @@ func NewAllocator() *Allocator {
} }
// Get a []byte from pool with most appropriate cap // Get a []byte from pool with most appropriate cap
func (alloc *Allocator) Get(size int) []byte { func (alloc *defaultAllocator) Get(size int) []byte {
switch { switch {
case size < 0: case size < 0:
panic("alloc.Get: len out of range") panic("alloc.Get: len out of range")
@@ -87,7 +92,7 @@ func (alloc *Allocator) Get(size int) []byte {
// Put returns a []byte to pool for future use, // Put returns a []byte to pool for future use,
// which the cap must be exactly 2^n // which the cap must be exactly 2^n
func (alloc *Allocator) Put(buf []byte) error { func (alloc *defaultAllocator) Put(buf []byte) error {
if cap(buf) == 0 || cap(buf) > 65536 { if cap(buf) == 0 || cap(buf) > 65536 {
return nil return nil
} }

View File

@@ -3,13 +3,12 @@
package pool package pool
const ( const (
// RelayBufferSize using for tcp
// io.Copy default buffer size is 32 KiB // io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 16 * 1024 RelayBufferSize = 16 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually // UDPBufferSize using for udp
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU // Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib // set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 8 * 1024 UDPBufferSize = 8 * 1024
) )

View File

@@ -3,13 +3,12 @@
package pool package pool
const ( const (
// RelayBufferSize using for tcp
// io.Copy default buffer size is 32 KiB // io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB RelayBufferSize = 32 * 1024
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 20 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually // UDPBufferSize using for udp
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU // Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib // set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 16 * 1024 UDPBufferSize = 16 * 1024
) )

View File

@@ -1,9 +1,9 @@
package pool package pool
func Get(size int) []byte { func Get(size int) []byte {
return defaultAllocator.Get(size) return DefaultAllocator.Get(size)
} }
func Put(buf []byte) error { func Put(buf []byte) error {
return defaultAllocator.Put(buf) return DefaultAllocator.Put(buf)
} }

View File

@@ -3,5 +3,5 @@ package pool
import "github.com/metacubex/sing/common/buf" import "github.com/metacubex/sing/common/buf"
func init() { func init() {
buf.DefaultAllocator = defaultAllocator buf.DefaultAllocator = DefaultAllocator
} }

View File

@@ -239,13 +239,15 @@ func (n *num) UnmarshalText(text []byte) (err error) {
func TestStructure_TextUnmarshaller(t *testing.T) { func TestStructure_TextUnmarshaller(t *testing.T) {
rawMap := map[string]any{ rawMap := map[string]any{
"num": "255", "num": "255",
"num_p": "127", "num_p": "127",
"num_arr": []string{"1", "2", "3"},
} }
s := &struct { s := &struct {
Num num `test:"num"` Num num `test:"num"`
NumP *num `test:"num_p"` NumP *num `test:"num_p"`
NumArr []num `test:"num_arr"`
}{} }{}
err := decoder.Decode(rawMap, s) err := decoder.Decode(rawMap, s)
@@ -253,6 +255,7 @@ func TestStructure_TextUnmarshaller(t *testing.T) {
assert.Equal(t, 255, s.Num.a) assert.Equal(t, 255, s.Num.a)
assert.NotNil(t, s.NumP) assert.NotNil(t, s.NumP)
assert.Equal(t, s.NumP.a, 127) assert.Equal(t, s.NumP.a, 127)
assert.Equal(t, s.NumArr, []num{{1}, {2}, {3}})
// test WeaklyTypedInput // test WeaklyTypedInput
rawMap["num"] = 256 rawMap["num"] = 256

View File

@@ -41,3 +41,11 @@ func NewAuthenticator(users []AuthUser) Authenticator {
} }
return au return au
} }
var AlwaysValid Authenticator = alwaysValid{}
type alwaysValid struct{}
func (alwaysValid) Verify(string, string) bool { return true }
func (alwaysValid) Users() []string { return nil }

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

@@ -1,7 +1,6 @@
package geodata package geodata
import ( import (
"errors"
"fmt" "fmt"
"strings" "strings"
@@ -76,13 +75,13 @@ func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, error) {
if countryCode[0] == '!' { if countryCode[0] == '!' {
not = true not = true
countryCode = countryCode[1:] countryCode = countryCode[1:]
if countryCode == "" {
return nil, fmt.Errorf("country code could not be empty")
}
} }
countryCode = strings.ToLower(countryCode) countryCode = strings.ToLower(countryCode)
parts := strings.Split(countryCode, "@") parts := strings.Split(countryCode, "@")
if len(parts) == 0 {
return nil, errors.New("empty rule")
}
listName := strings.TrimSpace(parts[0]) listName := strings.TrimSpace(parts[0])
attrVal := parts[1:] attrVal := parts[1:]
attrs := parseAttrs(attrVal) attrs := parseAttrs(attrVal)

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

@@ -1,57 +1,52 @@
package process package process
import ( import (
"encoding/json"
"errors" "errors"
"strings" "strings"
) )
const ( const (
FindProcessAlways = "always" FindProcessStrict FindProcessMode = iota
FindProcessStrict = "strict" FindProcessAlways
FindProcessOff = "off" FindProcessOff
) )
var ( var (
validModes = map[string]struct{}{ validModes = map[string]FindProcessMode{
FindProcessAlways: {}, FindProcessStrict.String(): FindProcessStrict,
FindProcessOff: {}, FindProcessAlways.String(): FindProcessAlways,
FindProcessStrict: {}, FindProcessOff.String(): FindProcessOff,
} }
) )
type FindProcessMode string type FindProcessMode int32
func (m FindProcessMode) Always() bool { // UnmarshalText unserialize FindProcessMode
return m == FindProcessAlways func (m *FindProcessMode) UnmarshalText(data []byte) error {
} return m.Set(string(data))
func (m FindProcessMode) Off() bool {
return m == FindProcessOff
}
func (m *FindProcessMode) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
if err := unmarshal(&tp); err != nil {
return err
}
return m.Set(tp)
}
func (m *FindProcessMode) UnmarshalJSON(data []byte) error {
var tp string
if err := json.Unmarshal(data, &tp); err != nil {
return err
}
return m.Set(tp)
} }
func (m *FindProcessMode) Set(value string) error { func (m *FindProcessMode) Set(value string) error {
mode := strings.ToLower(value) mode, exist := validModes[strings.ToLower(value)]
_, exist := validModes[mode]
if !exist { if !exist {
return errors.New("invalid find process mode") return errors.New("invalid find process mode")
} }
*m = FindProcessMode(mode) *m = mode
return nil return nil
} }
// MarshalText serialize FindProcessMode
func (m FindProcessMode) MarshalText() ([]byte, error) {
return []byte(m.String()), nil
}
func (m FindProcessMode) String() string {
switch m {
case FindProcessAlways:
return "always"
case FindProcessOff:
return "off"
default:
return "strict"
}
}

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

@@ -46,17 +46,24 @@ func RelayDnsConn(ctx context.Context, conn net.Conn, readTimeout time.Duration)
ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout) ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout)
defer cancel() defer cancel()
inData := buff[:n] inData := buff[:n]
msg, err := relayDnsPacket(ctx, inData, buff, 0) outBuff := buff[2:]
msg, err := relayDnsPacket(ctx, inData, outBuff, 0)
if err != nil { if err != nil {
return err return err
} }
err = binary.Write(conn, binary.BigEndian, uint16(len(msg))) if &msg[0] == &outBuff[0] { // msg is still in the buff
if err != nil { binary.BigEndian.PutUint16(buff[:2], uint16(len(msg)))
return err outBuff = buff[:2+len(msg)]
} else { // buff not big enough (WTF???)
newBuff := pool.Get(len(msg) + 2)
defer pool.Put(newBuff)
binary.BigEndian.PutUint16(newBuff[:2], uint16(len(msg)))
copy(newBuff[2:], msg)
outBuff = newBuff
} }
_, err = conn.Write(msg) _, err = conn.Write(outBuff)
if err != nil { if err != nil {
return err return err
} }

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

@@ -6,6 +6,8 @@ import (
"net/netip" "net/netip"
"time" "time"
"github.com/metacubex/sing/common/metadata"
"github.com/metacubex/mihomo/common/lru" "github.com/metacubex/mihomo/common/lru"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
@@ -72,8 +74,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 +91,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 +138,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 +160,23 @@ 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 {
if host == "." || !metadata.IsDomainName(host) {
return false
}
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

@@ -270,6 +270,7 @@ type RawTun struct {
AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"` AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"` AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"` AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
LoopbackAddress []netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"` RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"` RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"`
@@ -1168,10 +1169,19 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
} }
proxyName := u.Fragment var proxyName string
params := map[string]string{}
for _, s := range strings.Split(u.Fragment, "&") {
arr := strings.SplitN(s, "=", 2)
switch len(arr) {
case 1:
proxyName = arr[0]
case 2:
params[arr[0]] = arr[1]
}
}
var addr, dnsNetType string var addr, dnsNetType string
params := map[string]string{}
switch u.Scheme { switch u.Scheme {
case "udp": case "udp":
addr, err = hostWithDefaultPort(u.Host, "53") addr, err = hostWithDefaultPort(u.Host, "53")
@@ -1189,23 +1199,8 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
addr, err = hostWithDefaultPort(u.Host, "80") addr, err = hostWithDefaultPort(u.Host, "80")
} }
if err == nil { if err == nil {
proxyName = ""
clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User} clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User}
addr = clearURL.String() addr = clearURL.String()
if len(u.Fragment) != 0 {
for _, s := range strings.Split(u.Fragment, "&") {
arr := strings.Split(s, "=")
if len(arr) == 0 {
continue
} else if len(arr) == 1 {
proxyName = arr[0]
} else if len(arr) == 2 {
params[arr[0]] = arr[1]
} else {
params[arr[0]] = strings.Join(arr[1:], "=")
}
}
}
} }
case "quic": case "quic":
addr, err = hostWithDefaultPort(u.Host, "853") addr, err = hostWithDefaultPort(u.Host, "853")
@@ -1563,6 +1558,7 @@ func parseTun(rawTun RawTun, general *General) error {
AutoRedirect: rawTun.AutoRedirect, AutoRedirect: rawTun.AutoRedirect,
AutoRedirectInputMark: rawTun.AutoRedirectInputMark, AutoRedirectInputMark: rawTun.AutoRedirectInputMark,
AutoRedirectOutputMark: rawTun.AutoRedirectOutputMark, AutoRedirectOutputMark: rawTun.AutoRedirectOutputMark,
LoopbackAddress: rawTun.LoopbackAddress,
StrictRoute: rawTun.StrictRoute, StrictRoute: rawTun.StrictRoute,
RouteAddress: rawTun.RouteAddress, RouteAddress: rawTun.RouteAddress,
RouteAddressSet: rawTun.RouteAddressSet, RouteAddressSet: rawTun.RouteAddressSet,

View File

@@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"os"
"strconv"
"strings" "strings"
"github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/outboundgroup"
@@ -150,6 +152,9 @@ func proxyGroupsDagSort(groupsConfig []map[string]any) error {
} }
func verifyIP6() bool { func verifyIP6() bool {
if skip, _ := strconv.ParseBool(os.Getenv("SKIP_SYSTEM_IPV6_CHECK")); skip {
return true
}
if iAddrs, err := net.InterfaceAddrs(); err == nil { if iAddrs, err := net.InterfaceAddrs(); err == nil {
for _, addr := range iAddrs { for _, addr := range iAddrs {
if prefix, err := netip.ParsePrefix(addr.String()); err == nil { if prefix, err := netip.ParsePrefix(addr.String()); err == nil {

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

@@ -1,7 +1,6 @@
package constant package constant
import ( import (
"encoding/json"
"errors" "errors"
"strings" "strings"
) )
@@ -22,44 +21,6 @@ const (
type DNSMode int type DNSMode int
// UnmarshalYAML unserialize EnhancedMode with yaml
func (e *DNSMode) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
if err := unmarshal(&tp); err != nil {
return err
}
mode, exist := DNSModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
// MarshalYAML serialize EnhancedMode with yaml
func (e DNSMode) MarshalYAML() (any, error) {
return e.String(), nil
}
// UnmarshalJSON unserialize EnhancedMode with json
func (e *DNSMode) UnmarshalJSON(data []byte) error {
var tp string
if err := json.Unmarshal(data, &tp); err != nil {
return err
}
mode, exist := DNSModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
// MarshalJSON serialize EnhancedMode with json
func (e DNSMode) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String())
}
// UnmarshalText unserialize EnhancedMode // UnmarshalText unserialize EnhancedMode
func (e *DNSMode) UnmarshalText(data []byte) error { func (e *DNSMode) UnmarshalText(data []byte) error {
mode, exist := DNSModeMapping[strings.ToLower(string(data))] mode, exist := DNSModeMapping[strings.ToLower(string(data))]
@@ -157,40 +118,6 @@ func (e FilterMode) String() string {
} }
} }
func (e FilterMode) MarshalYAML() (interface{}, error) {
return e.String(), nil
}
func (e *FilterMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
var tp string
if err := unmarshal(&tp); err != nil {
return err
}
mode, exist := FilterModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
func (e FilterMode) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String())
}
func (e *FilterMode) UnmarshalJSON(data []byte) error {
var tp string
if err := json.Unmarshal(data, &tp); err != nil {
return err
}
mode, exist := FilterModeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid mode")
}
*e = mode
return nil
}
func (e FilterMode) MarshalText() ([]byte, error) { func (e FilterMode) MarshalText() ([]byte, error) {
return []byte(e.String()), nil return []byte(e.String()), nil
} }

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

@@ -91,9 +91,7 @@ type RuleProvider interface {
Provider Provider
Behavior() RuleBehavior Behavior() RuleBehavior
Count() int Count() int
Match(*constant.Metadata) bool Match(metadata *constant.Metadata, helper constant.RuleMatchHelper) bool
ShouldResolveIP() bool
ShouldFindProcess() bool
Strategy() any Strategy() any
} }

View File

@@ -111,14 +111,17 @@ func (rt RuleType) String() string {
type Rule interface { type Rule interface {
RuleType() RuleType RuleType() RuleType
Match(metadata *Metadata) (bool, string) Match(metadata *Metadata, helper RuleMatchHelper) (bool, string)
Adapter() string Adapter() string
Payload() string Payload() string
ShouldResolveIP() bool
ShouldFindProcess() bool
ProviderNames() []string ProviderNames() []string
} }
type RuleMatchHelper struct {
ResolveIP func()
FindProcess func()
}
type RuleGroup interface { type RuleGroup interface {
Rule Rule
GetRecodeSize() int GetRecodeSize() int

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

@@ -1,7 +1,6 @@
package constant package constant
import ( import (
"encoding/json"
"errors" "errors"
"strings" "strings"
) )
@@ -20,42 +19,6 @@ const (
type TUNStack int type TUNStack int
// UnmarshalYAML unserialize TUNStack with yaml
func (e *TUNStack) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
if err := unmarshal(&tp); err != nil {
return err
}
mode, exist := StackTypeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid tun stack")
}
*e = mode
return nil
}
// MarshalYAML serialize TUNStack with yaml
func (e TUNStack) MarshalYAML() (any, error) {
return e.String(), nil
}
// UnmarshalJSON unserialize TUNStack with json
func (e *TUNStack) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
mode, exist := StackTypeMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid tun stack")
}
*e = mode
return nil
}
// MarshalJSON serialize TUNStack with json
func (e TUNStack) MarshalJSON() ([]byte, error) {
return json.Marshal(e.String())
}
// UnmarshalText unserialize TUNStack // UnmarshalText unserialize TUNStack
func (e *TUNStack) UnmarshalText(data []byte) error { func (e *TUNStack) UnmarshalText(data []byte) error {
mode, exist := StackTypeMapping[strings.ToLower(string(data))] mode, exist := StackTypeMapping[strings.ToLower(string(data))]

View File

@@ -6,8 +6,10 @@ import (
"fmt" "fmt"
"net" "net"
"strings" "strings"
"time"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
@@ -105,3 +107,24 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
} }
func (c *client) ResetConnection() {} func (c *client) ResetConnection() {}
func newClient(addr string, resolver *Resolver, netType string, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) *client {
host, port, _ := net.SplitHostPort(addr)
c := &client{
Client: &D.Client{
Net: netType,
TLSConfig: &tls.Config{
ServerName: host,
},
UDPSize: 4096,
Timeout: 5 * time.Second,
},
port: port,
host: host,
dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
}
if params["skip-cert-verify"] == "true" {
c.TLSConfig.InsecureSkipVerify = true
}
return c
}

View File

@@ -9,7 +9,6 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"net/netip"
"net/url" "net/url"
"runtime" "runtime"
"strconv" "strconv"
@@ -71,8 +70,6 @@ type dnsOverHTTPS struct {
dialer *dnsDialer dialer *dnsDialer
addr string addr string
skipCertVerify bool skipCertVerify bool
ecsPrefix netip.Prefix
ecsOverride bool
} }
// type check // type check
@@ -105,28 +102,6 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
doh.skipCertVerify = true doh.skipCertVerify = true
} }
if ecs := params["ecs"]; ecs != "" {
prefix, err := netip.ParsePrefix(ecs)
if err != nil {
addr, err := netip.ParseAddr(ecs)
if err != nil {
log.Warnln("DOH [%s] config with invalid ecs: %s", doh.addr, ecs)
} else {
doh.ecsPrefix = netip.PrefixFrom(addr, addr.BitLen())
}
} else {
doh.ecsPrefix = prefix
}
}
if doh.ecsPrefix.IsValid() {
log.Debugln("DOH [%s] config with ecs: %s", doh.addr, doh.ecsPrefix)
}
if params["ecs-override"] == "true" {
doh.ecsOverride = true
}
runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close) runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close)
return doh return doh
@@ -154,10 +129,6 @@ func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.
} }
}() }()
if doh.ecsPrefix.IsValid() {
setEdns0Subnet(m, doh.ecsPrefix, doh.ecsOverride)
}
// Check if there was already an active client before sending the request. // Check if there was already an active client before sending the request.
// We'll only attempt to re-connect if there was one. // We'll only attempt to re-connect if there was one.
client, isCached, err := doh.getClient(ctx) client, isCached, err := doh.getClient(ctx)
@@ -552,8 +523,8 @@ func (doh *dnsOverHTTPS) createTransportH3(
Dial: func( Dial: func(
ctx context.Context, ctx context.Context,
// Ignore the address and always connect to the one that we got // Ignore the address and always connect to the one that we got
// from the bootstrapper. // from the bootstrapper.
_ string, _ string,
tlsCfg *tlsC.Config, tlsCfg *tlsC.Config,
cfg *quic.Config, cfg *quic.Config,

View File

@@ -61,15 +61,16 @@ type dnsOverQUIC struct {
bytesPool *sync.Pool bytesPool *sync.Pool
bytesPoolGuard sync.Mutex bytesPoolGuard sync.Mutex
addr string addr string
dialer *dnsDialer dialer *dnsDialer
skipCertVerify bool
} }
// type check // type check
var _ dnsClient = (*dnsOverQUIC)(nil) var _ dnsClient = (*dnsOverQUIC)(nil)
// newDoQ returns the DNS-over-QUIC Upstream. // newDoQ returns the DNS-over-QUIC Upstream.
func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) { func newDoQ(addr string, resolver *Resolver, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) *dnsOverQUIC {
doq := &dnsOverQUIC{ doq := &dnsOverQUIC{
addr: addr, addr: addr,
dialer: newDNSDialer(resolver, proxyAdapter, proxyName), dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
@@ -79,8 +80,12 @@ func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyN
}, },
} }
if params["skip-cert-verify"] == "true" {
doq.skipCertVerify = true
}
runtime.SetFinalizer(doq, (*dnsOverQUIC).Close) runtime.SetFinalizer(doq, (*dnsOverQUIC).Close)
return doq, nil return doq
} }
// Address implements the Upstream interface for *dnsOverQUIC. // Address implements the Upstream interface for *dnsOverQUIC.
@@ -329,7 +334,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
tlsConfig := ca.GetGlobalTLSConfig( tlsConfig := ca.GetGlobalTLSConfig(
&tls.Config{ &tls.Config{
ServerName: host, ServerName: host,
InsecureSkipVerify: false, InsecureSkipVerify: doq.skipCertVerify,
NextProtos: []string{ NextProtos: []string{
NextProtoDQ, NextProtoDQ,
}, },

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 {

View File

@@ -2,10 +2,8 @@ package dns
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net"
"net/netip" "net/netip"
"strings" "strings"
"time" "time"
@@ -92,46 +90,95 @@ func isIPRequest(q D.Question) bool {
func transform(servers []NameServer, resolver *Resolver) []dnsClient { func transform(servers []NameServer, resolver *Resolver) []dnsClient {
ret := make([]dnsClient, 0, len(servers)) ret := make([]dnsClient, 0, len(servers))
for _, s := range servers { for _, s := range servers {
var c dnsClient
switch s.Net { switch s.Net {
case "https": case "https":
ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName)) c = newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName)
continue
case "dhcp": case "dhcp":
ret = append(ret, newDHCPClient(s.Addr)) c = newDHCPClient(s.Addr)
continue
case "system": case "system":
ret = append(ret, newSystemClient()) c = newSystemClient()
continue
case "rcode": case "rcode":
ret = append(ret, newRCodeClient(s.Addr)) c = newRCodeClient(s.Addr)
continue
case "quic": case "quic":
if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter, s.ProxyName); err == nil { c = newDoQ(s.Addr, resolver, s.Params, s.ProxyAdapter, s.ProxyName)
ret = append(ret, doq) default:
} else { c = newClient(s.Addr, resolver, s.Net, s.Params, s.ProxyAdapter, s.ProxyName)
log.Fatalln("DoQ format error: %v", err)
}
continue
} }
host, port, _ := net.SplitHostPort(s.Addr) c = warpClientWithEdns0Subnet(c, s.Params)
ret = append(ret, &client{
Client: &D.Client{ if s.Params["disable-ipv4"] == "true" {
Net: s.Net, c = warpClientWithDisableType(c, D.TypeA)
TLSConfig: &tls.Config{ }
ServerName: host,
}, if s.Params["disable-ipv6"] == "true" {
UDPSize: 4096, c = warpClientWithDisableType(c, D.TypeAAAA)
Timeout: 5 * time.Second, }
},
port: port, ret = append(ret, c)
host: host,
dialer: newDNSDialer(resolver, s.ProxyAdapter, s.ProxyName),
})
} }
return ret return ret
} }
type clientWithDisableType struct {
dnsClient
qType uint16
}
func (c clientWithDisableType) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if len(m.Question) > 0 {
q := m.Question[0]
if q.Qtype == c.qType {
return handleMsgWithEmptyAnswer(m), nil
}
}
return c.dnsClient.ExchangeContext(ctx, m)
}
func warpClientWithDisableType(c dnsClient, qType uint16) dnsClient {
return clientWithDisableType{c, qType}
}
type clientWithEdns0Subnet struct {
dnsClient
ecsPrefix netip.Prefix
ecsOverride bool
}
func (c clientWithEdns0Subnet) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
m = m.Copy()
setEdns0Subnet(m, c.ecsPrefix, c.ecsOverride)
return c.dnsClient.ExchangeContext(ctx, m)
}
func warpClientWithEdns0Subnet(c dnsClient, params map[string]string) dnsClient {
var ecsPrefix netip.Prefix
var ecsOverride bool
if ecs := params["ecs"]; ecs != "" {
prefix, err := netip.ParsePrefix(ecs)
if err != nil {
addr, err := netip.ParseAddr(ecs)
if err != nil {
log.Warnln("DNS [%s] config with invalid ecs: %s", c.Address(), ecs)
} else {
ecsPrefix = netip.PrefixFrom(addr, addr.BitLen())
}
} else {
ecsPrefix = prefix
}
}
if ecsPrefix.IsValid() {
log.Debugln("DNS [%s] config with ecs: %s", c.Address(), ecsPrefix)
if params["ecs-override"] == "true" {
ecsOverride = true
}
return clientWithEdns0Subnet{c, ecsPrefix, ecsOverride}
}
return c
}
func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg { func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
msg := &D.Msg{} msg := &D.Msg{}
msg.Answer = []D.RR{} msg.Answer = []D.RR{}

View File

@@ -1020,7 +1020,7 @@ proxy-providers:
type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5 type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5
url: "url" url: "url"
interval: 3600 interval: 3600
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到其他位置,请通过设置 SAFE_PATHS 环境变量指定额外的安全路径。该环境变量的语法同本操作系统的PATH环境变量解析规则即Windows下以分号分割其他系统下以冒号分割
proxy: DIRECT proxy: DIRECT
# size-limit: 10240 # 限制下载文件最大为10kb默认为0即不限制文件大小 # size-limit: 10240 # 限制下载文件最大为10kb默认为0即不限制文件大小
header: header:
@@ -1077,7 +1077,7 @@ rule-providers:
rule1: rule1:
behavior: classical # domain ipcidr behavior: classical # domain ipcidr
interval: 259200 interval: 259200
path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1 path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到其他位置,请通过设置 SAFE_PATHS 环境变量指定额外的安全路径。该环境变量的语法同本操作系统的PATH环境变量解析规则即Windows下以分号分割其他系统下以冒号分割
type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5 type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
url: "url" url: "url"
proxy: DIRECT proxy: DIRECT
@@ -1276,6 +1276,16 @@ listeners:
# - 0123456789abcdef # - 0123456789abcdef
# server-names: # server-names:
# - test.com # - test.com
# #下列两个 limit 为选填可对未通过验证的回落连接限速bytesPerSec 默认为 0 即不启用
# #回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
# limit-fallback-upload:
# after-bytes: 0 # 传输指定字节后开始限速
# bytes-per-sec: 0 # 基准速率(字节/秒)
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
# limit-fallback-download:
# after-bytes: 0 # 传输指定字节后开始限速
# bytes-per-sec: 0 # 基准速率(字节/秒)
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
- name: tuic-in-1 - name: tuic-in-1
type: tuic type: tuic
@@ -1343,6 +1353,16 @@ listeners:
- 0123456789abcdef - 0123456789abcdef
server-names: server-names:
- test.com - test.com
#下列两个 limit 为选填可对未通过验证的回落连接限速bytesPerSec 默认为 0 即不启用
#回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
limit-fallback-upload:
after-bytes: 0 # 传输指定字节后开始限速
bytes-per-sec: 0 # 基准速率(字节/秒)
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
limit-fallback-download:
after-bytes: 0 # 传输指定字节后开始限速
bytes-per-sec: 0 # 基准速率(字节/秒)
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
### 注意对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ### ### 注意对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ###
- name: anytls-in-1 - name: anytls-in-1
@@ -1393,6 +1413,16 @@ listeners:
# - 0123456789abcdef # - 0123456789abcdef
# server-names: # server-names:
# - test.com # - test.com
# #下列两个 limit 为选填可对未通过验证的回落连接限速bytesPerSec 默认为 0 即不启用
# #回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
# limit-fallback-upload:
# after-bytes: 0 # 传输指定字节后开始限速
# bytes-per-sec: 0 # 基准速率(字节/秒)
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
# limit-fallback-download:
# after-bytes: 0 # 传输指定字节后开始限速
# bytes-per-sec: 0 # 基准速率(字节/秒)
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
# ss-option: # like trojan-go's `shadowsocks` config # ss-option: # like trojan-go's `shadowsocks` config
# enabled: false # enabled: false
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305 # method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305

18
go.mod
View File

@@ -20,23 +20,23 @@ require (
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
github.com/metacubex/bart v0.20.5 github.com/metacubex/bart v0.20.5
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399
github.com/metacubex/chacha v0.1.2 github.com/metacubex/chacha v0.1.5
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.4-0.20250605054047-54dc6097da29
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.11-0.20250621023810-0e9ef9dd0c92
github.com/metacubex/sing-shadowsocks2 v0.2.3 github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621023950-93d605a2143d
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.7-0.20250611091011-60774779fdd8
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
github.com/metacubex/utls v1.7.3 github.com/metacubex/utls v1.7.4-0.20250610022031-808d767c8c73
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20 github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
github.com/mroth/weightedrand/v2 v2.1.0 github.com/mroth/weightedrand/v2 v2.1.0

40
go.sum
View File

@@ -101,8 +101,8 @@ github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI= github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc= github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU= github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI= github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
@@ -111,35 +111,39 @@ 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.4-0.20250605054047-54dc6097da29 h1:SD9q025FNTaepuFXFOKDhnGLVu6PQYChBvw2ZYPXeLo=
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing v0.5.4-0.20250605054047-54dc6097da29/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.11-0.20250621021503-4f85ef9bf4b3 h1:dtiRj7WaCAXp4UhCkmaIiFF6v886qXiuqeIDN4Z//9E=
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA= github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621021503-4f85ef9bf4b3/go.mod h1:/squZ38pXrYjqtg8qn+joVvwbpGNYQNp8yxKsMVbCto=
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0= github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621023810-0e9ef9dd0c92 h1:Y9ebcKya6ow7VHoESCN5+l4zZvg5eaL2IhI5LLCQxQA=
github.com/metacubex/sing-shadowsocks2 v0.2.3/go.mod h1:/WNy/Q8ahLCoPRriWuFZFD0Jy+JNp1MEQl28Zw6SaF8= github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621023810-0e9ef9dd0c92/go.mod h1:/squZ38pXrYjqtg8qn+joVvwbpGNYQNp8yxKsMVbCto=
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621021638-dcd503063651 h1:vwLj0DDjPYy4AHEZvfRVf8ih52o6wpBnJxXxqa+ztmE=
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621021638-dcd503063651/go.mod h1:+ukTd0OPFglT3bnKAYTJWYPbuox6HYNXE235r5tHdUk=
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621023950-93d605a2143d h1:Ey3A1tA8lVkRbK1FDmwuWj/57Nr8JMdpoVqe45mFzJg=
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621023950-93d605a2143d/go.mod h1:+ukTd0OPFglT3bnKAYTJWYPbuox6HYNXE235r5tHdUk=
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.7-0.20250611091011-60774779fdd8 h1:4zWKqxTx75TbfW2EmlQ3hxM6RTRg2PYOAVMCnU4I61I=
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U= github.com/metacubex/sing-tun v0.4.7-0.20250611091011-60774779fdd8/go.mod h1:2YywXPWW8Z97kTH7RffOeykKzU+l0aiKlglWV1PAS64=
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=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc= github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.7.3 h1:yDcMEWojFh+t8rU9X0HPcZDPAoFze/rIIyssqivzj8A= github.com/metacubex/utls v1.7.4-0.20250610022031-808d767c8c73 h1:HWKsf92BqLYqugATFIJ3hYiEBZ7JF6AoqyvqF39afuI=
github.com/metacubex/utls v1.7.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU= github.com/metacubex/utls v1.7.4-0.20250610022031-808d767c8c73/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=

View File

@@ -75,6 +75,7 @@ type tunSchema struct {
AutoRedirect *bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"` AutoRedirect *bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
AutoRedirectInputMark *uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"` AutoRedirectInputMark *uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
AutoRedirectOutputMark *uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"` AutoRedirectOutputMark *uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
LoopbackAddress *[]netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"` StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route-address,omitempty"` RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
RouteAddressSet *[]string `yaml:"route-address-set" json:"route-address-set,omitempty"` RouteAddressSet *[]string `yaml:"route-address-set" json:"route-address-set,omitempty"`
@@ -174,6 +175,9 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
if p.AutoRedirectOutputMark != nil { if p.AutoRedirectOutputMark != nil {
def.AutoRedirectOutputMark = *p.AutoRedirectOutputMark def.AutoRedirectOutputMark = *p.AutoRedirectOutputMark
} }
if p.LoopbackAddress != nil {
def.LoopbackAddress = *p.LoopbackAddress
}
if p.StrictRoute != nil { if p.StrictRoute != nil {
def.StrictRoute = *p.StrictRoute def.StrictRoute = *p.StrictRoute
} }

View File

@@ -9,18 +9,6 @@ import (
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
func StringSliceToNetipPrefixSlice(ss []string) ([]netip.Prefix, error) {
lps := make([]netip.Prefix, 0, len(ss))
for _, s := range ss {
prefix, err := netip.ParsePrefix(s)
if err != nil {
return nil, err
}
lps = append(lps, prefix)
}
return lps, nil
}
type Tun struct { type Tun struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
Device string `yaml:"device" json:"device"` Device string `yaml:"device" json:"device"`
@@ -39,6 +27,7 @@ type Tun struct {
AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"` AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"` AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"` AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
LoopbackAddress []netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"` RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"` RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"`
@@ -142,6 +131,9 @@ func (t *Tun) Equal(other Tun) bool {
if t.AutoRedirectOutputMark != other.AutoRedirectOutputMark { if t.AutoRedirectOutputMark != other.AutoRedirectOutputMark {
return false return false
} }
if !slices.Equal(t.RouteAddress, other.RouteAddress) {
return false
}
if t.StrictRoute != other.StrictRoute { if t.StrictRoute != other.StrictRoute {
return false return false
} }

View File

@@ -9,6 +9,15 @@ type RealityConfig struct {
ServerNames []string `inbound:"server-names"` ServerNames []string `inbound:"server-names"`
MaxTimeDifference int `inbound:"max-time-difference,omitempty"` MaxTimeDifference int `inbound:"max-time-difference,omitempty"`
Proxy string `inbound:"proxy,omitempty"` Proxy string `inbound:"proxy,omitempty"`
LimitFallbackUpload RealityLimitFallback `inbound:"limit-fallback-upload,omitempty"`
LimitFallbackDownload RealityLimitFallback `inbound:"limit-fallback-download,omitempty"`
}
type RealityLimitFallback struct {
AfterBytes uint64 `inbound:"after-bytes,omitempty"`
BytesPerSec uint64 `inbound:"bytes-per-sec,omitempty"`
BurstBytesPerSec uint64 `inbound:"burst-bytes-per-sec,omitempty"`
} }
func (c RealityConfig) Build() reality.Config { func (c RealityConfig) Build() reality.Config {
@@ -19,5 +28,16 @@ func (c RealityConfig) Build() reality.Config {
ServerNames: c.ServerNames, ServerNames: c.ServerNames,
MaxTimeDifference: c.MaxTimeDifference, MaxTimeDifference: c.MaxTimeDifference,
Proxy: c.Proxy, Proxy: c.Proxy,
LimitFallbackUpload: reality.LimitFallback{
AfterBytes: c.LimitFallbackUpload.AfterBytes,
BytesPerSec: c.LimitFallbackUpload.BytesPerSec,
BurstBytesPerSec: c.LimitFallbackUpload.BurstBytesPerSec,
},
LimitFallbackDownload: reality.LimitFallback{
AfterBytes: c.LimitFallbackDownload.AfterBytes,
BytesPerSec: c.LimitFallbackDownload.BytesPerSec,
BurstBytesPerSec: c.LimitFallbackDownload.BurstBytesPerSec,
},
} }
} }

View File

@@ -1,8 +1,8 @@
package inbound package inbound
import ( import (
"errors" "encoding"
"strings" "net/netip"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
@@ -12,50 +12,55 @@ import (
type TunOption struct { type TunOption struct {
BaseOption BaseOption
Device string `inbound:"device,omitempty"` Device string `inbound:"device,omitempty"`
Stack string `inbound:"stack,omitempty"` Stack C.TUNStack `inbound:"stack,omitempty"`
DNSHijack []string `inbound:"dns-hijack,omitempty"` DNSHijack []string `inbound:"dns-hijack,omitempty"`
AutoRoute bool `inbound:"auto-route,omitempty"` AutoRoute bool `inbound:"auto-route,omitempty"`
AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"` AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"`
MTU uint32 `inbound:"mtu,omitempty"` MTU uint32 `inbound:"mtu,omitempty"`
GSO bool `inbound:"gso,omitempty"` GSO bool `inbound:"gso,omitempty"`
GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"` GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"`
Inet4Address []string `inbound:"inet4-address,omitempty"` Inet4Address []netip.Prefix `inbound:"inet4-address,omitempty"`
Inet6Address []string `inbound:"inet6-address,omitempty"` Inet6Address []netip.Prefix `inbound:"inet6-address,omitempty"`
IPRoute2TableIndex int `inbound:"iproute2-table-index,omitempty"` IPRoute2TableIndex int `inbound:"iproute2-table-index,omitempty"`
IPRoute2RuleIndex int `inbound:"iproute2-rule-index,omitempty"` IPRoute2RuleIndex int `inbound:"iproute2-rule-index,omitempty"`
AutoRedirect bool `inbound:"auto-redirect,omitempty"` AutoRedirect bool `inbound:"auto-redirect,omitempty"`
AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark,omitempty"` AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark,omitempty"`
AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark,omitempty"` AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark,omitempty"`
StrictRoute bool `inbound:"strict-route,omitempty"` LoopbackAddress []netip.Addr `inbound:"loopback-address,omitempty"`
RouteAddress []string `inbound:"route-address,omitempty"` StrictRoute bool `inbound:"strict-route,omitempty"`
RouteAddressSet []string `inbound:"route-address-set,omitempty"` RouteAddress []netip.Prefix `inbound:"route-address,omitempty"`
RouteExcludeAddress []string `inbound:"route-exclude-address,omitempty"` RouteAddressSet []string `inbound:"route-address-set,omitempty"`
RouteExcludeAddressSet []string `inbound:"route-exclude-address-set,omitempty"` RouteExcludeAddress []netip.Prefix `inbound:"route-exclude-address,omitempty"`
IncludeInterface []string `inbound:"include-interface,omitempty"` RouteExcludeAddressSet []string `inbound:"route-exclude-address-set,omitempty"`
ExcludeInterface []string `inbound:"exclude-interface,omitempty"` IncludeInterface []string `inbound:"include-interface,omitempty"`
IncludeUID []uint32 `inbound:"include-uid,omitempty"` ExcludeInterface []string `inbound:"exclude-interface,omitempty"`
IncludeUIDRange []string `inbound:"include-uid-range,omitempty"` IncludeUID []uint32 `inbound:"include-uid,omitempty"`
ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"` IncludeUIDRange []string `inbound:"include-uid-range,omitempty"`
ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"` ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"`
ExcludeSrcPort []uint16 `inbound:"exclude-src-port,omitempty"` ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"`
ExcludeSrcPortRange []string `inbound:"exclude-src-port-range,omitempty"` ExcludeSrcPort []uint16 `inbound:"exclude-src-port,omitempty"`
ExcludeDstPort []uint16 `inbound:"exclude-dst-port,omitempty"` ExcludeSrcPortRange []string `inbound:"exclude-src-port-range,omitempty"`
ExcludeDstPortRange []string `inbound:"exclude-dst-port-range,omitempty"` ExcludeDstPort []uint16 `inbound:"exclude-dst-port,omitempty"`
IncludeAndroidUser []int `inbound:"include-android-user,omitempty"` ExcludeDstPortRange []string `inbound:"exclude-dst-port-range,omitempty"`
IncludePackage []string `inbound:"include-package,omitempty"` IncludeAndroidUser []int `inbound:"include-android-user,omitempty"`
ExcludePackage []string `inbound:"exclude-package,omitempty"` IncludePackage []string `inbound:"include-package,omitempty"`
EndpointIndependentNat bool `inbound:"endpoint-independent-nat,omitempty"` ExcludePackage []string `inbound:"exclude-package,omitempty"`
UDPTimeout int64 `inbound:"udp-timeout,omitempty"` EndpointIndependentNat bool `inbound:"endpoint-independent-nat,omitempty"`
FileDescriptor int `inbound:"file-descriptor,omitempty"` UDPTimeout int64 `inbound:"udp-timeout,omitempty"`
FileDescriptor int `inbound:"file-descriptor,omitempty"`
Inet4RouteAddress []string `inbound:"inet4-route-address,omitempty"` Inet4RouteAddress []netip.Prefix `inbound:"inet4-route-address,omitempty"`
Inet6RouteAddress []string `inbound:"inet6-route-address,omitempty"` Inet6RouteAddress []netip.Prefix `inbound:"inet6-route-address,omitempty"`
Inet4RouteExcludeAddress []string `inbound:"inet4-route-exclude-address,omitempty"` Inet4RouteExcludeAddress []netip.Prefix `inbound:"inet4-route-exclude-address,omitempty"`
Inet6RouteExcludeAddress []string `inbound:"inet6-route-exclude-address,omitempty"` Inet6RouteExcludeAddress []netip.Prefix `inbound:"inet6-route-exclude-address,omitempty"`
} }
var _ encoding.TextUnmarshaler = (*netip.Addr)(nil) // ensure netip.Addr can decode direct by structure package
var _ encoding.TextUnmarshaler = (*netip.Prefix)(nil) // ensure netip.Prefix can decode direct by structure package
var _ encoding.TextUnmarshaler = (*C.TUNStack)(nil) // ensure C.TUNStack can decode direct by structure package
func (o TunOption) Equal(config C.InboundConfig) bool { func (o TunOption) Equal(config C.InboundConfig) bool {
return optionToString(o) == optionToString(config) return optionToString(o) == optionToString(config)
} }
@@ -72,68 +77,31 @@ func NewTun(options *TunOption) (*Tun, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
stack, exist := C.StackTypeMapping[strings.ToLower(options.Stack)]
if !exist {
return nil, errors.New("invalid tun stack")
}
routeAddress, err := LC.StringSliceToNetipPrefixSlice(options.RouteAddress)
if err != nil {
return nil, err
}
routeExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.RouteExcludeAddress)
if err != nil {
return nil, err
}
inet4Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet4Address)
if err != nil {
return nil, err
}
inet6Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet6Address)
if err != nil {
return nil, err
}
inet4RouteAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteAddress)
if err != nil {
return nil, err
}
inet6RouteAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteAddress)
if err != nil {
return nil, err
}
inet4RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteExcludeAddress)
if err != nil {
return nil, err
}
inet6RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteExcludeAddress)
if err != nil {
return nil, err
}
return &Tun{ return &Tun{
Base: base, Base: base,
config: options, config: options,
tun: LC.Tun{ tun: LC.Tun{
Enable: true, Enable: true,
Device: options.Device, Device: options.Device,
Stack: stack, Stack: options.Stack,
DNSHijack: options.DNSHijack, DNSHijack: options.DNSHijack,
AutoRoute: options.AutoRoute, AutoRoute: options.AutoRoute,
AutoDetectInterface: options.AutoDetectInterface, AutoDetectInterface: options.AutoDetectInterface,
MTU: options.MTU, MTU: options.MTU,
GSO: options.GSO, GSO: options.GSO,
GSOMaxSize: options.GSOMaxSize, GSOMaxSize: options.GSOMaxSize,
Inet4Address: inet4Address, Inet4Address: options.Inet4Address,
Inet6Address: inet6Address, Inet6Address: options.Inet6Address,
IPRoute2TableIndex: options.IPRoute2TableIndex, IPRoute2TableIndex: options.IPRoute2TableIndex,
IPRoute2RuleIndex: options.IPRoute2RuleIndex, IPRoute2RuleIndex: options.IPRoute2RuleIndex,
AutoRedirect: options.AutoRedirect, AutoRedirect: options.AutoRedirect,
AutoRedirectInputMark: options.AutoRedirectInputMark, AutoRedirectInputMark: options.AutoRedirectInputMark,
AutoRedirectOutputMark: options.AutoRedirectOutputMark, AutoRedirectOutputMark: options.AutoRedirectOutputMark,
LoopbackAddress: options.LoopbackAddress,
StrictRoute: options.StrictRoute, StrictRoute: options.StrictRoute,
RouteAddress: routeAddress, RouteAddress: options.RouteAddress,
RouteAddressSet: options.RouteAddressSet, RouteAddressSet: options.RouteAddressSet,
RouteExcludeAddress: routeExcludeAddress, RouteExcludeAddress: options.RouteExcludeAddress,
RouteExcludeAddressSet: options.RouteExcludeAddressSet, RouteExcludeAddressSet: options.RouteExcludeAddressSet,
IncludeInterface: options.IncludeInterface, IncludeInterface: options.IncludeInterface,
ExcludeInterface: options.ExcludeInterface, ExcludeInterface: options.ExcludeInterface,
@@ -152,10 +120,10 @@ func NewTun(options *TunOption) (*Tun, error) {
UDPTimeout: options.UDPTimeout, UDPTimeout: options.UDPTimeout,
FileDescriptor: options.FileDescriptor, FileDescriptor: options.FileDescriptor,
Inet4RouteAddress: inet4RouteAddress, Inet4RouteAddress: options.Inet4RouteAddress,
Inet6RouteAddress: inet6RouteAddress, Inet6RouteAddress: options.Inet6RouteAddress,
Inet4RouteExcludeAddress: inet4RouteExcludeAddress, Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
Inet6RouteExcludeAddress: inet6RouteExcludeAddress, Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
}, },
}, nil }, nil
} }

View File

@@ -64,7 +64,7 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) {
listener, err = IN.NewTunnel(tunnelOption) listener, err = IN.NewTunnel(tunnelOption)
case "tun": case "tun":
tunOption := &IN.TunOption{ tunOption := &IN.TunOption{
Stack: C.TunGvisor.String(), Stack: C.TunGvisor,
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
} }
err = decoder.Decode(mapping, tunOption) err = decoder.Decode(mapping, tunOption)

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"
@@ -19,6 +20,7 @@ import (
) )
type Conn = utls.Conn type Conn = utls.Conn
type LimitFallback = utls.RealityLimitFallback
type Config struct { type Config struct {
Dest string Dest string
@@ -27,6 +29,9 @@ type Config struct {
ServerNames []string ServerNames []string
MaxTimeDifference int MaxTimeDifference int
Proxy string Proxy string
LimitFallbackUpload LimitFallback
LimitFallbackDownload LimitFallback
} }
func (c Config) Build(tunnel C.Tunnel) (*Builder, error) { func (c Config) Build(tunnel C.Tunnel) (*Builder, error) {
@@ -72,6 +77,9 @@ func (c Config) Build(tunnel C.Tunnel) (*Builder, error) {
return inner.HandleTcp(tunnel, address, c.Proxy) return inner.HandleTcp(tunnel, address, c.Proxy)
} }
realityConfig.LimitFallbackUpload = c.LimitFallbackUpload
realityConfig.LimitFallbackDownload = c.LimitFallbackDownload
return &Builder{realityConfig}, nil return &Builder{realityConfig}, nil
} }
@@ -89,7 +97,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

@@ -347,6 +347,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
IPRoute2RuleIndex: ruleIndex, IPRoute2RuleIndex: ruleIndex,
AutoRedirectInputMark: inputMark, AutoRedirectInputMark: inputMark,
AutoRedirectOutputMark: outputMark, AutoRedirectOutputMark: outputMark,
Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4),
Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6),
StrictRoute: options.StrictRoute, StrictRoute: options.StrictRoute,
Inet4RouteAddress: inet4RouteAddress, Inet4RouteAddress: inet4RouteAddress,
Inet6RouteAddress: inet6RouteAddress, Inet6RouteAddress: inet6RouteAddress,

View File

@@ -1,7 +1,6 @@
package log package log
import ( import (
"encoding/json"
"errors" "errors"
"strings" "strings"
) )
@@ -25,30 +24,6 @@ const (
type LogLevel int type LogLevel int
// UnmarshalYAML unserialize LogLevel with yaml
func (l *LogLevel) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
unmarshal(&tp)
level, exist := LogLevelMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid log-level")
}
*l = level
return nil
}
// UnmarshalJSON unserialize LogLevel with json
func (l *LogLevel) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
level, exist := LogLevelMapping[strings.ToLower(tp)]
if !exist {
return errors.New("invalid log-level")
}
*l = level
return nil
}
// UnmarshalText unserialize LogLevel // UnmarshalText unserialize LogLevel
func (l *LogLevel) UnmarshalText(data []byte) error { func (l *LogLevel) UnmarshalText(data []byte) error {
level, exist := LogLevelMapping[strings.ToLower(string(data))] level, exist := LogLevelMapping[strings.ToLower(string(data))]
@@ -59,16 +34,6 @@ func (l *LogLevel) UnmarshalText(data []byte) error {
return nil return nil
} }
// MarshalYAML serialize LogLevel with yaml
func (l LogLevel) MarshalYAML() (any, error) {
return l.String(), nil
}
// MarshalJSON serialize LogLevel with json
func (l LogLevel) MarshalJSON() ([]byte, error) {
return json.Marshal(l.String())
}
// MarshalText serialize LogLevel // MarshalText serialize LogLevel
func (l LogLevel) MarshalText() ([]byte, error) { func (l LogLevel) MarshalText() ([]byte, error) {
return []byte(l.String()), nil return []byte(l.String()), nil

View File

@@ -21,14 +21,6 @@ var (
type Base struct { type Base struct {
} }
func (b *Base) ShouldFindProcess() bool {
return false
}
func (b *Base) ShouldResolveIP() bool {
return false
}
func (b *Base) ProviderNames() []string { return nil } func (b *Base) ProviderNames() []string { return nil }
func ParseParams(params []string) (isSrc bool, noResolve bool) { func ParseParams(params []string) (isSrc bool, noResolve bool) {

View File

@@ -17,7 +17,7 @@ func (d *Domain) RuleType() C.RuleType {
return C.Domain return C.Domain
} }
func (d *Domain) Match(metadata *C.Metadata) (bool, string) { func (d *Domain) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
return metadata.RuleHost() == d.domain, d.adapter return metadata.RuleHost() == d.domain, d.adapter
} }

View File

@@ -17,7 +17,7 @@ func (dk *DomainKeyword) RuleType() C.RuleType {
return C.DomainKeyword return C.DomainKeyword
} }
func (dk *DomainKeyword) Match(metadata *C.Metadata) (bool, string) { func (dk *DomainKeyword) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
domain := metadata.RuleHost() domain := metadata.RuleHost()
return strings.Contains(domain, dk.keyword), dk.adapter return strings.Contains(domain, dk.keyword), dk.adapter
} }

View File

@@ -16,7 +16,7 @@ func (dr *DomainRegex) RuleType() C.RuleType {
return C.DomainRegex return C.DomainRegex
} }
func (dr *DomainRegex) Match(metadata *C.Metadata) (bool, string) { func (dr *DomainRegex) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
domain := metadata.RuleHost() domain := metadata.RuleHost()
match, _ := dr.regex.MatchString(domain) match, _ := dr.regex.MatchString(domain)
return match, dr.adapter return match, dr.adapter

View File

@@ -17,7 +17,7 @@ func (ds *DomainSuffix) RuleType() C.RuleType {
return C.DomainSuffix return C.DomainSuffix
} }
func (ds *DomainSuffix) Match(metadata *C.Metadata) (bool, string) { func (ds *DomainSuffix) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
domain := metadata.RuleHost() domain := metadata.RuleHost()
return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix, ds.adapter return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix, ds.adapter
} }

View File

@@ -18,7 +18,7 @@ func (d *DSCP) RuleType() C.RuleType {
return C.DSCP return C.DSCP
} }
func (d *DSCP) Match(metadata *C.Metadata) (bool, string) { func (d *DSCP) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
return d.ranges.Check(metadata.DSCP), d.adapter return d.ranges.Check(metadata.DSCP), d.adapter
} }

View File

@@ -13,7 +13,7 @@ func (f *Match) RuleType() C.RuleType {
return C.MATCH return C.MATCH
} }
func (f *Match) Match(metadata *C.Metadata) (bool, string) { func (f *Match) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
return true, f.adapter return true, f.adapter
} }

View File

@@ -33,7 +33,11 @@ func (g *GEOIP) RuleType() C.RuleType {
return C.GEOIP return C.GEOIP
} }
func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) { func (g *GEOIP) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
if !g.noResolveIP && !g.isSourceIP && helper.ResolveIP != nil {
helper.ResolveIP()
}
ip := metadata.DstIP ip := metadata.DstIP
if g.isSourceIP { if g.isSourceIP {
ip = metadata.SrcIP ip = metadata.SrcIP
@@ -161,10 +165,6 @@ func (g *GEOIP) Payload() string {
return g.country return g.country
} }
func (g *GEOIP) ShouldResolveIP() bool {
return !g.noResolveIP
}
func (g *GEOIP) GetCountry() string { func (g *GEOIP) GetCountry() string {
return g.country return g.country
} }

View File

@@ -22,7 +22,7 @@ func (gs *GEOSITE) RuleType() C.RuleType {
return C.GEOSITE return C.GEOSITE
} }
func (gs *GEOSITE) Match(metadata *C.Metadata) (bool, string) { func (gs *GEOSITE) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
return gs.MatchDomain(metadata.RuleHost()), gs.adapter return gs.MatchDomain(metadata.RuleHost()), gs.adapter
} }

View File

@@ -2,8 +2,9 @@ package common
import ( import (
"fmt" "fmt"
C "github.com/metacubex/mihomo/constant"
"strings" "strings"
C "github.com/metacubex/mihomo/constant"
) )
type InName struct { type InName struct {
@@ -13,7 +14,7 @@ type InName struct {
payload string payload string
} }
func (u *InName) Match(metadata *C.Metadata) (bool, string) { func (u *InName) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
for _, name := range u.names { for _, name := range u.names {
if metadata.InName == name { if metadata.InName == name {
return true, u.adapter return true, u.adapter
@@ -36,8 +37,12 @@ func (u *InName) Payload() string {
func NewInName(iNames, adapter string) (*InName, error) { func NewInName(iNames, adapter string) (*InName, error) {
names := strings.Split(iNames, "/") names := strings.Split(iNames, "/")
if len(names) == 0 { for i, name := range names {
return nil, fmt.Errorf("in name couldn't be empty") name = strings.TrimSpace(name)
if len(name) == 0 {
return nil, fmt.Errorf("in name couldn't be empty")
}
names[i] = name
} }
return &InName{ return &InName{

View File

@@ -2,8 +2,9 @@ package common
import ( import (
"fmt" "fmt"
C "github.com/metacubex/mihomo/constant"
"strings" "strings"
C "github.com/metacubex/mihomo/constant"
) )
type InType struct { type InType struct {
@@ -13,7 +14,7 @@ type InType struct {
payload string payload string
} }
func (u *InType) Match(metadata *C.Metadata) (bool, string) { func (u *InType) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
for _, tp := range u.types { for _, tp := range u.types {
if metadata.Type == tp { if metadata.Type == tp {
return true, u.adapter return true, u.adapter
@@ -36,8 +37,12 @@ func (u *InType) Payload() string {
func NewInType(iTypes, adapter string) (*InType, error) { func NewInType(iTypes, adapter string) (*InType, error) {
types := strings.Split(iTypes, "/") types := strings.Split(iTypes, "/")
if len(types) == 0 { for i, tp := range types {
return nil, fmt.Errorf("in type couldn't be empty") tp = strings.TrimSpace(tp)
if len(tp) == 0 {
return nil, fmt.Errorf("in type couldn't be empty")
}
types[i] = tp
} }
tps, err := parseInTypes(types) tps, err := parseInTypes(types)

View File

@@ -2,8 +2,9 @@ package common
import ( import (
"fmt" "fmt"
C "github.com/metacubex/mihomo/constant"
"strings" "strings"
C "github.com/metacubex/mihomo/constant"
) )
type InUser struct { type InUser struct {
@@ -13,7 +14,7 @@ type InUser struct {
payload string payload string
} }
func (u *InUser) Match(metadata *C.Metadata) (bool, string) { func (u *InUser) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
for _, user := range u.users { for _, user := range u.users {
if metadata.InUser == user { if metadata.InUser == user {
return true, u.adapter return true, u.adapter
@@ -36,8 +37,12 @@ func (u *InUser) Payload() string {
func NewInUser(iUsers, adapter string) (*InUser, error) { func NewInUser(iUsers, adapter string) (*InUser, error) {
users := strings.Split(iUsers, "/") users := strings.Split(iUsers, "/")
if len(users) == 0 { for i, user := range users {
return nil, fmt.Errorf("in user couldn't be empty") user = strings.TrimSpace(user)
if len(user) == 0 {
return nil, fmt.Errorf("in user couldn't be empty")
}
users[i] = user
} }
return &InUser{ return &InUser{

View File

@@ -15,7 +15,11 @@ type ASN struct {
isSourceIP bool isSourceIP bool
} }
func (a *ASN) Match(metadata *C.Metadata) (bool, string) { func (a *ASN) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
if !a.noResolveIP && !a.isSourceIP && helper.ResolveIP != nil {
helper.ResolveIP()
}
ip := metadata.DstIP ip := metadata.DstIP
if a.isSourceIP { if a.isSourceIP {
ip = metadata.SrcIP ip = metadata.SrcIP
@@ -49,10 +53,6 @@ func (a *ASN) Payload() string {
return a.asn return a.asn
} }
func (a *ASN) ShouldResolveIP() bool {
return !a.noResolveIP
}
func (a *ASN) GetASN() string { func (a *ASN) GetASN() string {
return a.asn return a.asn
} }

View File

@@ -35,7 +35,11 @@ func (i *IPCIDR) RuleType() C.RuleType {
return C.IPCIDR return C.IPCIDR
} }
func (i *IPCIDR) Match(metadata *C.Metadata) (bool, string) { func (i *IPCIDR) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
if !i.noResolveIP && !i.isSourceIP && helper.ResolveIP != nil {
helper.ResolveIP()
}
ip := metadata.DstIP ip := metadata.DstIP
if i.isSourceIP { if i.isSourceIP {
ip = metadata.SrcIP ip = metadata.SrcIP
@@ -51,10 +55,6 @@ func (i *IPCIDR) Payload() string {
return i.ipnet.String() return i.ipnet.String()
} }
func (i *IPCIDR) ShouldResolveIP() bool {
return !i.noResolveIP
}
func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) { func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) {
ipnet, err := netip.ParsePrefix(s) ipnet, err := netip.ParsePrefix(s)
if err != nil { if err != nil {

View File

@@ -22,7 +22,11 @@ func (is *IPSuffix) RuleType() C.RuleType {
return C.IPSuffix return C.IPSuffix
} }
func (is *IPSuffix) Match(metadata *C.Metadata) (bool, string) { func (is *IPSuffix) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
if !is.noResolveIP && !is.isSourceIP && helper.ResolveIP != nil {
helper.ResolveIP()
}
ip := metadata.DstIP ip := metadata.DstIP
if is.isSourceIP { if is.isSourceIP {
ip = metadata.SrcIP ip = metadata.SrcIP
@@ -57,10 +61,6 @@ func (is *IPSuffix) Payload() string {
return is.payload return is.payload
} }
func (is *IPSuffix) ShouldResolveIP() bool {
return !is.noResolveIP
}
func NewIPSuffix(payload, adapter string, isSrc, noResolveIP bool) (*IPSuffix, error) { func NewIPSuffix(payload, adapter string, isSrc, noResolveIP bool) (*IPSuffix, error) {
ipnet, err := netip.ParsePrefix(payload) ipnet, err := netip.ParsePrefix(payload)
if err != nil { if err != nil {

Some files were not shown because too many files have changed in this diff Show More