Compare commits

...

54 Commits

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

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

For reject and DNS outbound, no resolution is required.

For other outbound, resolution will still be performed when the connection is initiated, and the domain name will not be sent directly to the remote server at present.
2025-05-27 10:45:26 +08:00
wwqgtxx
12e3952b74 chore: code cleanup 2025-05-26 12:33:24 +08:00
wwqgtxx
88419cbd12 chore: better parse remote dst 2025-05-26 01:12:35 +08:00
wwqgtxx
4ed830330e chore: remove confused code 2025-05-25 22:22:23 +08:00
wwqgtxx
3ed6ff9402 chore: export pipeDeadline 2025-05-25 22:07:29 +08:00
wwqgtxx
34de62d21d chore: better get localAddr 2025-05-24 23:19:38 +08:00
wwqgtxx
d2e255f257 fix: some error in tun 2025-05-24 22:23:10 +08:00
wwqgtxx
a0c46bb4b7 chore: remove the redundant layer of udpnat in sing-tun to reduce resource usage when processing udp 2025-05-24 15:57:49 +08:00
wwqgtxx
9e3bf14b1a chore: handle two interfaces have the same prefix but different address 2025-05-24 11:32:36 +08:00
wwqgtxx
28c387a9b6 chore: restore break change in sing-tun 2025-05-23 20:19:18 +08:00
wwqgtxx
15eda703b4 fix: hysteria2 panic 2025-05-23 20:12:38 +08:00
wwqgtxx
b1d12a15db chore: proxy's ech should fetch from proxy-nameserver 2025-05-22 17:42:40 +08:00
wwqgtxx
5a21bf3642 fix: listener close panic 2025-05-22 17:01:24 +08:00
wwqgtxx
199fb8fd5d chore: update quic-go to 0.52.0 2025-05-22 10:28:10 +08:00
wwqgtxx
fd959feff2 chore: update dependencies 2025-05-21 21:37:20 +08:00
wwqgtxx
d5a03901d2 fix: race in close grpc transport 2025-05-20 16:15:04 +08:00
wwqgtxx
257fead538 docs: update config.yaml follow 5cf0f18c 2025-05-20 11:08:42 +08:00
wwqgtxx
c489c5260b fix: hysteria2 hop ports init
https://github.com/MetaCubeX/mihomo/issues/2056
2025-05-20 10:56:14 +08:00
wwqgtxx
8f92b1de13 chore: simplify the single root decompression process 2025-05-20 09:48:05 +08:00
wwqgtxx
9f7a2a36c1 chore: unpack externalUI in a separate temporary directory to avoid malicious compressed packages from polluting workdir 2025-05-20 01:58:25 +08:00
wwqgtxx
a93479124c chore: stricter path checking when unpacking zip/tgz 2025-05-20 00:00:30 +08:00
wwqgtxx
ed42c4feb8 chore: disallow symlink in unzip 2025-05-19 23:42:39 +08:00
wwqgtxx
608ddb1b44 fix: external-ui-name must in local 2025-05-19 23:11:52 +08:00
wwqgtxx
d036d98128 fix: http server does not handle http2 logic correctly 2025-05-18 23:05:00 +08:00
wwqgtxx
d900c71214 fix: shadowtls v2 not work with X25519MLKEM768 2025-05-18 23:03:07 +08:00
wwqgtxx
1672750c47 chore: simplifying the old fingerprint processing method 2025-05-18 23:03:07 +08:00
wwqgtxx
41b57afb3f fix: grpc deadline implement 2025-05-18 23:03:07 +08:00
wwqgtxx
188372cb04 feat: add tls.ech-key for external-controller-tls 2025-05-17 21:21:02 +08:00
wwqgtxx
a1350d4985 feat: add ech-key for listeners 2025-05-17 20:50:21 +08:00
wwqgtxx
dc958e6a39 feat: add ech-opts for hysteria/hysteria2/tuic outbound 2025-05-17 18:41:39 +08:00
wwqgtxx
8a5f3b8909 chore: simplify port hop costs 2025-05-17 17:06:38 +08:00
wwqgtxx
c6d7ef8cb8 feat: add ech-opts for anytls/shadowsocks/trojan/vmess/vless outbound 2025-05-17 13:53:21 +08:00
wwqgtxx
bb8c47d83d fix: error typo 2025-05-15 18:07:55 +08:00
wwqgtxx
5cf0f18c29 feat: reality add support-x25519mlkem768, it only works with new version server 2025-05-15 14:54:43 +08:00
wwqgtxx
83213d493e chore: adjust min backoff from 1s to 10s 2025-05-14 21:51:18 +08:00
wwqgtxx
90ed01ed53 fix: backoff not reset when the file unchanged 2025-05-14 21:45:12 +08:00
wwqgtxx
f91a586da8 fix: inline proxy provider's healthcheck not work 2025-05-13 19:00:32 +08:00
wwqgtxx
266fb03838 chore: update dependencies 2025-05-13 12:09:38 +08:00
wwqgtxx
76e9607fd7 chore: move start healthcheck.process() from New to Initial in provider
avoid panic cause by build-in proxy have not set to tunnel
2025-05-13 01:12:06 +08:00
wwqgtxx
23e2d3a132 chore: rebuild provider load 2025-05-12 22:19:49 +08:00
wwqgtxx
6e35cf9399 fix: truncated UDP response in system dns
https://github.com/MetaCubeX/mihomo/issues/2031
2025-05-12 12:34:22 +08:00
wwqgtxx
2116640886 chore: the updateConfigs api also adds a check for SAFE_PATHS 2025-05-12 11:28:15 +08:00
wwqgtxx
a4fcd3af07 chore: rollback incompatible changes to updateConfigs api 2025-05-12 10:00:01 +08:00
wwqgtxx
d22a893060 fix: hysteria server port hopping compatibility issues 2025-05-11 11:44:42 +08:00
Anya Lin
00cceba890 docs: update config.yaml follow 7e7016b (#2022) 2025-05-10 13:12:45 +08:00
115 changed files with 2068 additions and 1049 deletions

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ package outbound
import ( import (
"context" "context"
"errors"
"net" "net"
"strconv" "strconv"
"time" "time"
@@ -10,7 +9,6 @@ import (
CN "github.com/metacubex/mihomo/common/net" CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls" "github.com/metacubex/mihomo/transport/anytls"
"github.com/metacubex/mihomo/transport/vmess" "github.com/metacubex/mihomo/transport/vmess"
@@ -28,19 +26,20 @@ type AnyTLS struct {
type AnyTLSOption struct { type AnyTLSOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Password string `proxy:"password"` Password string `proxy:"password"`
ALPN []string `proxy:"alpn,omitempty"` ALPN []string `proxy:"alpn,omitempty"`
SNI string `proxy:"sni,omitempty"` SNI string `proxy:"sni,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"` ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
UDP bool `proxy:"udp,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"` UDP bool `proxy:"udp,omitempty"`
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"` IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
MinIdleSession int `proxy:"min-idle-session,omitempty"` IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
MinIdleSession int `proxy:"min-idle-session,omitempty"`
} }
func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
@@ -52,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 {
@@ -59,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
} }
@@ -115,12 +111,17 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second, IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second,
MinIdleSession: option.MinIdleSession, MinIdleSession: option.MinIdleSession,
} }
echConfig, err := option.ECHOpts.Parse()
if err != nil {
return nil, err
}
tlsConfig := &vmess.TLSConfig{ tlsConfig := &vmess.TLSConfig{
Host: option.SNI, Host: option.SNI,
SkipCertVerify: option.SkipCertVerify, SkipCertVerify: option.SkipCertVerify,
NextProtos: option.ALPN, NextProtos: option.ALPN,
FingerPrint: option.Fingerprint, FingerPrint: option.Fingerprint,
ClientFingerprint: option.ClientFingerprint, ClientFingerprint: option.ClientFingerprint,
ECH: echConfig,
} }
if tlsConfig.Host == "" { if tlsConfig.Host == "" {
tlsConfig.Host = option.Server tlsConfig.Host = option.Server

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()

36
adapter/outbound/ech.go Normal file
View File

@@ -0,0 +1,36 @@
package outbound
import (
"context"
"encoding/base64"
"fmt"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/resolver"
)
type ECHOptions struct {
Enable bool `proxy:"enable,omitempty" obfs:"enable,omitempty"`
Config string `proxy:"config,omitempty" obfs:"config,omitempty"`
}
func (o ECHOptions) Parse() (*ech.Config, error) {
if !o.Enable {
return nil, nil
}
echConfig := &ech.Config{}
if o.Config != "" {
list, err := base64.StdEncoding.DecodeString(o.Config)
if err != nil {
return nil, fmt.Errorf("base64 decode ech config string failed: %v", err)
}
echConfig.GetEncryptedClientHelloConfigList = func(ctx context.Context, serverName string) ([]byte, error) {
return list, nil
}
} else {
echConfig.GetEncryptedClientHelloConfigList = func(ctx context.Context, serverName string) ([]byte, error) {
return resolver.ResolveECHWithResolver(ctx, serverName, resolver.ProxyServerHostResolver)
}
}
return echConfig, nil
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
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"
@@ -44,6 +45,9 @@ type Hysteria struct {
option *HysteriaOption option *HysteriaOption
client *core.Client client *core.Client
tlsConfig *tlsC.Config
echConfig *ech.Config
} }
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
@@ -56,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
@@ -79,7 +86,15 @@ func (h *Hysteria) genHdc(ctx context.Context) utils.PacketDialer {
return cDialer.ListenPacket(ctx, network, "", rAddrPort) return cDialer.ListenPacket(ctx, network, "", rAddrPort)
}, },
remoteAddr: func(addr string) (net.Addr, error) { remoteAddr: func(addr string) (net.Addr, error) {
return resolveUDPAddr(ctx, "udp", addr, h.prefer) udpAddr, err := resolveUDPAddr(ctx, "udp", addr, h.prefer)
if err != nil {
return nil, err
}
err = h.echConfig.ClientHandle(ctx, h.tlsConfig)
if err != nil {
return nil, err
}
return udpAddr, nil
}, },
} }
} }
@@ -93,30 +108,31 @@ func (h *Hysteria) ProxyInfo() C.ProxyInfo {
type HysteriaOption struct { type HysteriaOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port,omitempty"` Port int `proxy:"port,omitempty"`
Ports string `proxy:"ports,omitempty"` Ports string `proxy:"ports,omitempty"`
Protocol string `proxy:"protocol,omitempty"` Protocol string `proxy:"protocol,omitempty"`
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
Up string `proxy:"up"` Up string `proxy:"up"`
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
Down string `proxy:"down"` Down string `proxy:"down"`
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
Auth string `proxy:"auth,omitempty"` Auth string `proxy:"auth,omitempty"`
AuthString string `proxy:"auth-str,omitempty"` AuthString string `proxy:"auth-str,omitempty"`
Obfs string `proxy:"obfs,omitempty"` Obfs string `proxy:"obfs,omitempty"`
SNI string `proxy:"sni,omitempty"` SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
ALPN []string `proxy:"alpn,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
CustomCA string `proxy:"ca,omitempty"` ALPN []string `proxy:"alpn,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"` CustomCA string `proxy:"ca,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"` CustomCAString string `proxy:"ca-str,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"` ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"` ReceiveWindow int `proxy:"recv-window,omitempty"`
FastOpen bool `proxy:"fast-open,omitempty"` DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
HopInterval int `proxy:"hop-interval,omitempty"` FastOpen bool `proxy:"fast-open,omitempty"`
HopInterval int `proxy:"hop-interval,omitempty"`
} }
func (c *HysteriaOption) Speed() (uint64, uint64, error) { func (c *HysteriaOption) Speed() (uint64, uint64, error) {
@@ -161,6 +177,13 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
} else { } else {
tlsConfig.NextProtos = []string{DefaultALPN} tlsConfig.NextProtos = []string{DefaultALPN}
} }
echConfig, err := option.ECHOpts.Parse()
if err != nil {
return nil, err
}
tlsClientConfig := tlsC.UConfig(tlsConfig)
quicConfig := &quic.Config{ quicConfig := &quic.Config{
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn), InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn), MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
@@ -215,7 +238,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
down = uint64(option.DownSpeed * mbpsToBps) down = uint64(option.DownSpeed * mbpsToBps)
} }
client, err := core.NewClient( client, err := core.NewClient(
addr, ports, option.Protocol, auth, tlsC.UConfig(tlsConfig), quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl { addr, ports, option.Protocol, auth, tlsClientConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
}, obfuscator, hopInterval, option.FastOpen, }, obfuscator, hopInterval, option.FastOpen,
) )
@@ -233,8 +256,10 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
option: &option, option: &option,
client: client, client: client,
tlsConfig: tlsClientConfig,
echConfig: echConfig,
} }
return outbound, nil return outbound, nil

View File

@@ -20,7 +20,6 @@ import (
tuicCommon "github.com/metacubex/mihomo/transport/tuic/common" tuicCommon "github.com/metacubex/mihomo/transport/tuic/common"
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"
"github.com/metacubex/randv2"
"github.com/metacubex/sing-quic/hysteria2" "github.com/metacubex/sing-quic/hysteria2"
M "github.com/metacubex/sing/common/metadata" M "github.com/metacubex/sing/common/metadata"
) )
@@ -42,24 +41,25 @@ type Hysteria2 struct {
type Hysteria2Option struct { type Hysteria2Option struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port,omitempty"` Port int `proxy:"port,omitempty"`
Ports string `proxy:"ports,omitempty"` Ports string `proxy:"ports,omitempty"`
HopInterval int `proxy:"hop-interval,omitempty"` HopInterval int `proxy:"hop-interval,omitempty"`
Up string `proxy:"up,omitempty"` Up string `proxy:"up,omitempty"`
Down string `proxy:"down,omitempty"` Down string `proxy:"down,omitempty"`
Password string `proxy:"password,omitempty"` Password string `proxy:"password,omitempty"`
Obfs string `proxy:"obfs,omitempty"` Obfs string `proxy:"obfs,omitempty"`
ObfsPassword string `proxy:"obfs-password,omitempty"` ObfsPassword string `proxy:"obfs-password,omitempty"`
SNI string `proxy:"sni,omitempty"` SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
ALPN []string `proxy:"alpn,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
CustomCA string `proxy:"ca,omitempty"` ALPN []string `proxy:"alpn,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"` CustomCA string `proxy:"ca,omitempty"`
CWND int `proxy:"cwnd,omitempty"` CustomCAString string `proxy:"ca-str,omitempty"`
UdpMTU int `proxy:"udp-mtu,omitempty"` CWND int `proxy:"cwnd,omitempty"`
UdpMTU int `proxy:"udp-mtu,omitempty"`
// quic-go special config // quic-go special config
InitialStreamReceiveWindow uint64 `proxy:"initial-stream-receive-window,omitempty"` InitialStreamReceiveWindow uint64 `proxy:"initial-stream-receive-window,omitempty"`
@@ -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
@@ -154,6 +157,12 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
tlsConfig.NextProtos = option.ALPN tlsConfig.NextProtos = option.ALPN
} }
tlsClientConfig := tlsC.UConfig(tlsConfig)
echConfig, err := option.ECHOpts.Parse()
if err != nil {
return nil, err
}
if option.UdpMTU == 0 { if option.UdpMTU == 0 {
// "1200" from quic-go's MaxDatagramSize // "1200" from quic-go's MaxDatagramSize
// "-3" from quic-go's DatagramFrame.MaxDataLen // "-3" from quic-go's DatagramFrame.MaxDataLen
@@ -175,41 +184,46 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
ReceiveBPS: StringToBps(option.Down), ReceiveBPS: StringToBps(option.Down),
SalamanderPassword: salamanderPassword, SalamanderPassword: salamanderPassword,
Password: option.Password, Password: option.Password,
TLSConfig: tlsC.UConfig(tlsConfig), TLSConfig: tlsClientConfig,
QUICConfig: quicConfig, QUICConfig: quicConfig,
UDPDisabled: false, UDPDisabled: false,
CWND: option.CWND, CWND: option.CWND,
UdpMTU: option.UdpMTU, UdpMTU: option.UdpMTU,
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) { ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
return resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion)) udpAddr, err := resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
if err != nil {
return nil, err
}
err = echConfig.ClientHandle(ctx, tlsClientConfig)
if err != nil {
return nil, err
}
return udpAddr, nil
}, },
} }
var ranges utils.IntRanges[uint16] var ranges utils.IntRanges[uint16]
var serverAddress []string var serverPorts []uint16
if option.Ports != "" { if option.Ports != "" {
ranges, err = utils.NewUnsignedRanges[uint16](option.Ports) ranges, err = utils.NewUnsignedRanges[uint16](option.Ports)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ranges.Range(func(port uint16) bool { ranges.Range(func(port uint16) bool {
serverAddress = append(serverAddress, net.JoinHostPort(option.Server, strconv.Itoa(int(port)))) serverPorts = append(serverPorts, port)
return true return true
}) })
if len(serverAddress) > 0 { if len(serverPorts) > 0 {
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
return resolveUDPAddr(ctx, "udp", serverAddress[randv2.IntN(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
}
if option.HopInterval == 0 { if option.HopInterval == 0 {
option.HopInterval = defaultHopInterval option.HopInterval = defaultHopInterval
} else if option.HopInterval < minHopInterval { } else if option.HopInterval < minHopInterval {
option.HopInterval = minHopInterval option.HopInterval = minHopInterval
} }
clientOptions.HopInterval = time.Duration(option.HopInterval) * time.Second clientOptions.HopInterval = time.Duration(option.HopInterval) * time.Second
clientOptions.ServerPorts = serverPorts
} }
} }
if option.Port == 0 && len(serverAddress) == 0 { if option.Port == 0 && len(serverPorts) == 0 {
return nil, errors.New("invalid port") return nil, errors.New("invalid port")
} }

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

@@ -13,11 +13,14 @@ import (
type RealityOptions struct { type RealityOptions struct {
PublicKey string `proxy:"public-key"` PublicKey string `proxy:"public-key"`
ShortID string `proxy:"short-id"` ShortID string `proxy:"short-id"`
SupportX25519MLKEM768 bool `proxy:"support-x25519mlkem768"`
} }
func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) { func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
if o.PublicKey != "" { if o.PublicKey != "" {
config := new(tlsC.RealityConfig) config := new(tlsC.RealityConfig)
config.SupportX25519MLKEM768 = o.SupportX25519MLKEM768
const x25519ScalarSize = 32 const x25519ScalarSize = 32
publicKey, err := base64.RawURLEncoding.DecodeString(o.PublicKey) publicKey, err := base64.RawURLEncoding.DecodeString(o.PublicKey)

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"
@@ -64,6 +62,7 @@ type v2rayObfsOption struct {
Host string `obfs:"host,omitempty"` Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"` Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"` TLS bool `obfs:"tls,omitempty"`
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"` Fingerprint string `obfs:"fingerprint,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"` Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
@@ -77,6 +76,7 @@ type gostObfsOption struct {
Host string `obfs:"host,omitempty"` Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"` Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"` TLS bool `obfs:"tls,omitempty"`
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"` Fingerprint string `obfs:"fingerprint,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"` Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
@@ -200,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
@@ -228,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
@@ -303,6 +300,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
v2rayOption.TLS = true v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.Fingerprint = opts.Fingerprint v2rayOption.Fingerprint = opts.Fingerprint
echConfig, err := opts.ECHOpts.Parse()
if err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", addr, err)
}
v2rayOption.ECHConfig = echConfig
} }
} else if option.Plugin == "gost-plugin" { } else if option.Plugin == "gost-plugin" {
opts := gostObfsOption{Host: "bing.com", Mux: true} opts := gostObfsOption{Host: "bing.com", Mux: true}
@@ -325,6 +328,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
gostOption.TLS = true gostOption.TLS = true
gostOption.SkipCertVerify = opts.SkipCertVerify gostOption.SkipCertVerify = opts.SkipCertVerify
gostOption.Fingerprint = opts.Fingerprint gostOption.Fingerprint = opts.Fingerprint
echConfig, err := opts.ECHOpts.Parse()
if err != nil {
return nil, fmt.Errorf("ss %s initialize gost-plugin error: %w", addr, err)
}
gostOption.ECHConfig = echConfig
} }
} else if option.Plugin == shadowtls.Mode { } else if option.Plugin == shadowtls.Mode {
obfsMode = shadowtls.Mode obfsMode = shadowtls.Mode

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

@@ -138,7 +138,7 @@ func NewSsh(option SshOption) (*Ssh, error) {
} else { } else {
path := C.Path.Resolve(option.PrivateKey) path := C.Path.Resolve(option.PrivateKey)
if !C.Path.IsSafePath(path) { if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("path is not subpath of home directory: %s", path) return nil, C.Path.ErrNotSafePath(path)
} }
b, err = os.ReadFile(path) b, err = os.ReadFile(path)
if err != nil { if err != nil {

View File

@@ -12,6 +12,7 @@ import (
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
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"
@@ -32,6 +33,7 @@ type Trojan struct {
transport *gun.TransportWrap transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig realityConfig *tlsC.RealityConfig
echConfig *ech.Config
ssCipher core.Cipher ssCipher core.Cipher
} }
@@ -48,6 +50,7 @@ type TrojanOption struct {
Fingerprint string `proxy:"fingerprint,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"` RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"`
@@ -77,6 +80,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade, V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen, V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
ClientFingerprint: t.option.ClientFingerprint, ClientFingerprint: t.option.ClientFingerprint,
ECHConfig: t.echConfig,
Headers: http.Header{}, Headers: http.Header{},
} }
@@ -110,7 +114,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts) c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
case "grpc": case "grpc":
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig) c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.echConfig, t.realityConfig)
default: default:
// default tcp network // default tcp network
// handle TLS // handle TLS
@@ -124,6 +128,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
FingerPrint: t.option.Fingerprint, FingerPrint: t.option.Fingerprint,
ClientFingerprint: t.option.ClientFingerprint, ClientFingerprint: t.option.ClientFingerprint,
NextProtos: alpn, NextProtos: alpn,
ECH: t.echConfig,
Reality: t.realityConfig, Reality: t.realityConfig,
}) })
} }
@@ -214,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
@@ -245,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)
@@ -266,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
@@ -321,6 +327,11 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
return nil, err return nil, err
} }
t.echConfig, err = option.ECHOpts.Parse()
if err != nil {
return nil, err
}
if option.SSOpts.Enabled { if option.SSOpts.Enabled {
if option.SSOpts.Password == "" { if option.SSOpts.Password == "" {
return nil, errors.New("empty password") return nil, errors.New("empty password")
@@ -365,7 +376,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
return nil, err return nil, err
} }
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.ClientFingerprint, t.realityConfig) t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.ClientFingerprint, t.echConfig, t.realityConfig)
t.gunTLSConfig = tlsConfig t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{ t.gunConfig = &gun.Config{

View File

@@ -3,7 +3,6 @@ package outbound
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"math" "math"
"net" "net"
@@ -12,8 +11,8 @@ import (
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"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"
@@ -28,6 +27,9 @@ type Tuic struct {
*Base *Base
option *TuicOption option *TuicOption
client *tuic.PoolClient client *tuic.PoolClient
tlsConfig *tlsC.Config
echConfig *ech.Config
} }
type TuicOption struct { type TuicOption struct {
@@ -48,18 +50,19 @@ type TuicOption struct {
DisableSni bool `proxy:"disable-sni,omitempty"` DisableSni bool `proxy:"disable-sni,omitempty"`
MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"` MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"`
FastOpen bool `proxy:"fast-open,omitempty"` FastOpen bool `proxy:"fast-open,omitempty"`
MaxOpenStreams int `proxy:"max-open-streams,omitempty"` MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
CWND int `proxy:"cwnd,omitempty"` CWND int `proxy:"cwnd,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
CustomCA string `proxy:"ca,omitempty"` CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"` CustomCAString string `proxy:"ca-str,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"` ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"` ReceiveWindow int `proxy:"recv-window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"` DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"` MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
SNI string `proxy:"sni,omitempty"` SNI string `proxy:"sni,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
UDPOverStream bool `proxy:"udp-over-stream,omitempty"` UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"` UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"`
@@ -86,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
@@ -97,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 {
@@ -135,6 +135,10 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
err = t.echConfig.ClientHandle(ctx, t.tlsConfig)
if err != nil {
return nil, nil, err
}
addr = udpAddr addr = udpAddr
var pc net.PacketConn var pc net.PacketConn
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort()) pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
@@ -249,6 +253,12 @@ func NewTuic(option TuicOption) (*Tuic, error) {
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
} }
tlsClientConfig := tlsC.UConfig(tlsConfig)
echConfig, err := option.ECHOpts.Parse()
if err != nil {
return nil, err
}
switch option.UDPOverStreamVersion { switch option.UDPOverStreamVersion {
case uot.Version, uot.LegacyVersion: case uot.Version, uot.LegacyVersion:
case 0: case 0:
@@ -268,7 +278,9 @@ func NewTuic(option TuicOption) (*Tuic, error) {
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
option: &option, option: &option,
tlsConfig: tlsClientConfig,
echConfig: echConfig,
} }
clientMaxOpenStreams := int64(option.MaxOpenStreams) clientMaxOpenStreams := int64(option.MaxOpenStreams)
@@ -285,7 +297,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
if len(option.Token) > 0 { if len(option.Token) > 0 {
tkn := tuic.GenTKN(option.Token) tkn := tuic.GenTKN(option.Token)
clientOption := &tuic.ClientOptionV4{ clientOption := &tuic.ClientOptionV4{
TlsConfig: tlsC.UConfig(tlsConfig), TlsConfig: tlsClientConfig,
QuicConfig: quicConfig, QuicConfig: quicConfig,
Token: tkn, Token: tkn,
UdpRelayMode: udpRelayMode, UdpRelayMode: udpRelayMode,
@@ -305,7 +317,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
maxUdpRelayPacketSize = tuic.MaxFragSizeV5 maxUdpRelayPacketSize = tuic.MaxFragSizeV5
} }
clientOption := &tuic.ClientOptionV5{ clientOption := &tuic.ClientOptionV5{
TlsConfig: tlsC.UConfig(tlsConfig), TlsConfig: tlsClientConfig,
QuicConfig: quicConfig, QuicConfig: quicConfig,
Uuid: uuid.FromStringOrNil(option.UUID), Uuid: uuid.FromStringOrNil(option.UUID),
Password: option.Password, Password: option.Password,

View File

@@ -17,8 +17,8 @@ import (
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"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"
@@ -46,6 +46,7 @@ type Vless struct {
transport *gun.TransportWrap transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig realityConfig *tlsC.RealityConfig
echConfig *ech.Config
} }
type VlessOption struct { type VlessOption struct {
@@ -62,6 +63,7 @@ type VlessOption struct {
XUDP bool `proxy:"xudp,omitempty"` XUDP bool `proxy:"xudp,omitempty"`
PacketEncoding string `proxy:"packet-encoding,omitempty"` PacketEncoding string `proxy:"packet-encoding,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"` RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
@@ -88,6 +90,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade, V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen, V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
ECHConfig: v.echConfig,
Headers: http.Header{}, Headers: http.Header{},
} }
@@ -151,7 +154,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c, err = vmess.StreamH2Conn(ctx, c, h2Opts) c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
case "grpc": case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig) c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.echConfig, v.realityConfig)
default: default:
// default tcp network // default tcp network
// handle TLS // handle TLS
@@ -206,6 +209,7 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint, FingerPrint: v.option.Fingerprint,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
ECH: v.echConfig,
Reality: v.realityConfig, Reality: v.realityConfig,
NextProtos: v.option.ALPN, NextProtos: v.option.ALPN,
} }
@@ -272,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
@@ -310,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)
@@ -342,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 {
@@ -563,6 +552,11 @@ func NewVless(option VlessOption) (*Vless, error) {
return nil, err return nil, err
} }
v.echConfig, err = v.option.ECHOpts.Parse()
if err != nil {
return nil, err
}
switch option.Network { switch option.Network {
case "h2": case "h2":
if len(option.HTTP2Opts.Host) == 0 { if len(option.HTTP2Opts.Host) == 0 {
@@ -611,7 +605,7 @@ func NewVless(option VlessOption) (*Vless, error) {
v.gunTLSConfig = tlsConfig v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig v.gunConfig = gunConfig
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig) v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.echConfig, v.realityConfig)
} }
return v, nil return v, nil

View File

@@ -15,8 +15,8 @@ import (
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"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"
@@ -41,6 +41,7 @@ type Vmess struct {
transport *gun.TransportWrap transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig realityConfig *tlsC.RealityConfig
echConfig *ech.Config
} }
type VmessOption struct { type VmessOption struct {
@@ -58,6 +59,7 @@ type VmessOption struct {
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
ServerName string `proxy:"servername,omitempty"` ServerName string `proxy:"servername,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"` RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
@@ -109,6 +111,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade, V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen, V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
ECHConfig: v.echConfig,
Headers: http.Header{}, Headers: http.Header{},
} }
@@ -146,6 +149,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
ECH: v.echConfig,
Reality: v.realityConfig, Reality: v.realityConfig,
NextProtos: v.option.ALPN, NextProtos: v.option.ALPN,
} }
@@ -195,7 +199,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts) c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts)
case "grpc": case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig) c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.echConfig, v.realityConfig)
default: default:
// handle TLS // handle TLS
if v.option.TLS { if v.option.TLS {
@@ -205,6 +209,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint, FingerPrint: v.option.Fingerprint,
ClientFingerprint: v.option.ClientFingerprint, ClientFingerprint: v.option.ClientFingerprint,
ECH: v.echConfig,
Reality: v.realityConfig, Reality: v.realityConfig,
NextProtos: v.option.ALPN, NextProtos: v.option.ALPN,
} }
@@ -324,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
@@ -361,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)
@@ -407,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 {
@@ -474,6 +464,11 @@ func NewVmess(option VmessOption) (*Vmess, error) {
return nil, err return nil, err
} }
v.echConfig, err = v.option.ECHOpts.Parse()
if err != nil {
return nil, err
}
switch option.Network { switch option.Network {
case "h2": case "h2":
if len(option.HTTP2Opts.Host) == 0 { if len(option.HTTP2Opts.Host) == 0 {
@@ -522,7 +517,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
v.gunTLSConfig = tlsConfig v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig v.gunConfig = gunConfig
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig) v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.echConfig, v.realityConfig)
} }
return v, nil return v, nil

View File

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

View File

@@ -32,7 +32,6 @@ type HealthCheck struct {
url string url string
extra map[string]*extraOption extra map[string]*extraOption
mu sync.Mutex mu sync.Mutex
started atomic.Bool
proxies []C.Proxy proxies []C.Proxy
interval time.Duration interval time.Duration
lazy bool lazy bool
@@ -43,13 +42,8 @@ type HealthCheck struct {
} }
func (hc *HealthCheck) process() { func (hc *HealthCheck) process() {
if hc.started.Load() {
log.Warnln("Skip start health check timer due to it's started")
return
}
ticker := time.NewTicker(hc.interval) ticker := time.NewTicker(hc.interval)
hc.start() go hc.check()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
@@ -62,13 +56,12 @@ func (hc *HealthCheck) process() {
} }
case <-hc.ctx.Done(): case <-hc.ctx.Done():
ticker.Stop() ticker.Stop()
hc.stop()
return return
} }
} }
} }
func (hc *HealthCheck) setProxy(proxies []C.Proxy) { func (hc *HealthCheck) setProxies(proxies []C.Proxy) {
hc.proxies = proxies hc.proxies = proxies
} }
@@ -105,10 +98,6 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.
option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus} option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus}
splitAndAddFiltersToExtra(filter, option) splitAndAddFiltersToExtra(filter, option)
hc.extra[url] = option hc.extra[url] = option
if hc.auto() && !hc.started.Load() {
go hc.process()
}
} }
func splitAndAddFiltersToExtra(filter string, option *extraOption) { func splitAndAddFiltersToExtra(filter string, option *extraOption) {
@@ -131,14 +120,6 @@ func (hc *HealthCheck) touch() {
hc.lastTouch.Store(time.Now()) hc.lastTouch.Store(time.Now())
} }
func (hc *HealthCheck) start() {
hc.started.Store(true)
}
func (hc *HealthCheck) stop() {
hc.started.Store(false)
}
func (hc *HealthCheck) check() { func (hc *HealthCheck) check() {
if len(hc.proxies) == 0 { if len(hc.proxies) == 0 {
return return

View File

@@ -17,7 +17,6 @@ import (
var ( var (
errVehicleType = errors.New("unsupport vehicle type") errVehicleType = errors.New("unsupport vehicle type")
errSubPath = errors.New("path is not subpath of home directory")
) )
type healthCheckSchema struct { type healthCheckSchema struct {
@@ -115,7 +114,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
if schema.Path != "" { if schema.Path != "" {
path = C.Path.Resolve(schema.Path) path = C.Path.Resolve(schema.Path)
if !C.Path.IsSafePath(path) { if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("%w: %s", errSubPath, path) return nil, C.Path.ErrNotSafePath(path)
} }
} }
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout, schema.SizeLimit) vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout, schema.SizeLimit)

View File

@@ -57,6 +57,13 @@ func (bp *baseProvider) Version() uint32 {
return bp.version return bp.version
} }
func (bp *baseProvider) Initial() error {
if bp.healthCheck.auto() {
go bp.healthCheck.process()
}
return nil
}
func (bp *baseProvider) HealthCheck() { func (bp *baseProvider) HealthCheck() {
bp.healthCheck.check() bp.healthCheck.check()
} }
@@ -88,7 +95,7 @@ func (bp *baseProvider) RegisterHealthCheckTask(url string, expectedStatus utils
func (bp *baseProvider) setProxies(proxies []C.Proxy) { func (bp *baseProvider) setProxies(proxies []C.Proxy) {
bp.proxies = proxies bp.proxies = proxies
bp.version += 1 bp.version += 1
bp.healthCheck.setProxy(proxies) bp.healthCheck.setProxies(proxies)
if bp.healthCheck.auto() { if bp.healthCheck.auto() {
go bp.healthCheck.check() go bp.healthCheck.check()
} }
@@ -133,6 +140,9 @@ func (pp *proxySetProvider) Update() error {
} }
func (pp *proxySetProvider) Initial() error { func (pp *proxySetProvider) Initial() error {
if err := pp.baseProvider.Initial(); err != nil {
return err
}
_, err := pp.Fetcher.Initial() _, err := pp.Fetcher.Initial()
if err != nil { if err != nil {
return err return err
@@ -162,10 +172,6 @@ func (pp *proxySetProvider) Close() error {
} }
func NewProxySetProvider(name string, interval time.Duration, payload []map[string]any, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { func NewProxySetProvider(name string, interval time.Duration, payload []map[string]any, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
if hc.auto() {
go hc.process()
}
pd := &proxySetProvider{ pd := &proxySetProvider{
baseProvider: baseProvider{ baseProvider: baseProvider{
name: name, name: name,
@@ -185,6 +191,8 @@ func NewProxySetProvider(name string, interval time.Duration, payload []map[stri
return nil, err return nil, err
} }
pd.proxies = proxies pd.proxies = proxies
// direct call setProxies on hc to avoid starting a health check process immediately, it should be done by Initial()
hc.setProxies(proxies)
} }
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, parser, pd.setProxies) fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, parser, pd.setProxies)
@@ -234,10 +242,6 @@ func (ip *inlineProvider) VehicleType() types.VehicleType {
return types.Inline return types.Inline
} }
func (ip *inlineProvider) Initial() error {
return nil
}
func (ip *inlineProvider) Update() error { func (ip *inlineProvider) Update() error {
// make api update happy // make api update happy
ip.updateAt = time.Now() ip.updateAt = time.Now()
@@ -245,10 +249,6 @@ func (ip *inlineProvider) Update() error {
} }
func NewInlineProvider(name string, payload []map[string]any, parser resource.Parser[[]C.Proxy], hc *HealthCheck) (*InlineProvider, error) { func NewInlineProvider(name string, payload []map[string]any, parser resource.Parser[[]C.Proxy], hc *HealthCheck) (*InlineProvider, error) {
if hc.auto() {
go hc.process()
}
ps := ProxySchema{Proxies: payload} ps := ProxySchema{Proxies: payload}
buf, err := yaml.Marshal(ps) buf, err := yaml.Marshal(ps)
if err != nil { if err != nil {
@@ -258,6 +258,8 @@ func NewInlineProvider(name string, payload []map[string]any, parser resource.Pa
if err != nil { if err != nil {
return nil, err return nil, err
} }
// direct call setProxies on hc to avoid starting a health check process immediately, it should be done by Initial()
hc.setProxies(proxies)
ip := &inlineProvider{ ip := &inlineProvider{
baseProvider: baseProvider{ baseProvider: baseProvider{
@@ -301,13 +303,6 @@ func (cp *compatibleProvider) Update() error {
return nil return nil
} }
func (cp *compatibleProvider) Initial() error {
if cp.healthCheck.interval != 0 && cp.healthCheck.url != "" {
cp.HealthCheck()
}
return nil
}
func (cp *compatibleProvider) VehicleType() types.VehicleType { func (cp *compatibleProvider) VehicleType() types.VehicleType {
return types.Compatible return types.Compatible
} }
@@ -317,10 +312,6 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
return nil, errors.New("provider need one proxy at least") return nil, errors.New("provider need one proxy at least")
} }
if hc.auto() {
go hc.process()
}
pd := &compatibleProvider{ pd := &compatibleProvider{
baseProvider: baseProvider{ baseProvider: baseProvider{
name: name, name: name,

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

@@ -83,7 +83,7 @@ func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error)
if len(customCA) > 0 { if len(customCA) > 0 {
path := C.Path.Resolve(customCA) path := C.Path.Resolve(customCA)
if !C.Path.IsSafePath(path) { if !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("path is not subpath of home directory: %s", path) return nil, C.Path.ErrNotSafePath(path)
} }
certificate, err = os.ReadFile(path) certificate, err = os.ReadFile(path)
if err != nil { if err != nil {

View File

@@ -17,6 +17,7 @@ import (
type Path interface { type Path interface {
Resolve(path string) string Resolve(path string) string
IsSafePath(path string) bool IsSafePath(path string) bool
ErrNotSafePath(path string) error
} }
// LoadTLSKeyPair loads a TLS key pair from the provided certificate and private key data or file paths, supporting fallback resolution. // LoadTLSKeyPair loads a TLS key pair from the provided certificate and private key data or file paths, supporting fallback resolution.
@@ -42,10 +43,12 @@ func LoadTLSKeyPair(certificate, privateKey string, path Path) (tls.Certificate,
certificate = path.Resolve(certificate) certificate = path.Resolve(certificate)
privateKey = path.Resolve(privateKey) privateKey = path.Resolve(privateKey)
var loadErr error var loadErr error
if path.IsSafePath(certificate) && path.IsSafePath(privateKey) { if !path.IsSafePath(certificate) {
cert, loadErr = tls.LoadX509KeyPair(certificate, privateKey) loadErr = path.ErrNotSafePath(certificate)
} else if !path.IsSafePath(privateKey) {
loadErr = path.ErrNotSafePath(privateKey)
} else { } else {
loadErr = fmt.Errorf("path is not subpath of home directory") cert, loadErr = tls.LoadX509KeyPair(certificate, privateKey)
} }
if loadErr != nil { if loadErr != nil {
return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())

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() {

31
component/ech/ech.go Normal file
View File

@@ -0,0 +1,31 @@
package ech
import (
"context"
"fmt"
tlsC "github.com/metacubex/mihomo/component/tls"
)
type Config struct {
GetEncryptedClientHelloConfigList func(ctx context.Context, serverName string) ([]byte, error)
}
func (cfg *Config) ClientHandle(ctx context.Context, tlsConfig *tlsC.Config) (err error) {
if cfg == nil {
return nil
}
echConfigList, err := cfg.GetEncryptedClientHelloConfigList(ctx, tlsConfig.ServerName)
if err != nil {
return fmt.Errorf("resolve ECH config error: %w", err)
}
tlsConfig.EncryptedClientHelloConfigList = echConfigList
if tlsConfig.MinVersion != 0 && tlsConfig.MinVersion < tlsC.VersionTLS13 {
tlsConfig.MinVersion = tlsC.VersionTLS13
}
if tlsConfig.MaxVersion != 0 && tlsConfig.MaxVersion < tlsC.VersionTLS13 {
tlsConfig.MaxVersion = tlsC.VersionTLS13
}
return nil
}

143
component/ech/key.go Normal file
View File

@@ -0,0 +1,143 @@
package ech
import (
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"os"
"github.com/metacubex/mihomo/component/ca"
tlsC "github.com/metacubex/mihomo/component/tls"
"golang.org/x/crypto/cryptobyte"
)
const (
AEAD_AES_128_GCM = 0x0001
AEAD_AES_256_GCM = 0x0002
AEAD_ChaCha20Poly1305 = 0x0003
)
const extensionEncryptedClientHello = 0xfe0d
const DHKEM_X25519_HKDF_SHA256 = 0x0020
const KDF_HKDF_SHA256 = 0x0001
// sortedSupportedAEADs is just a sorted version of hpke.SupportedAEADS.
// We need this so that when we insert them into ECHConfigs the ordering
// is stable.
var sortedSupportedAEADs = []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305}
func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) []byte {
builder := cryptobyte.NewBuilder(nil)
builder.AddUint16(extensionEncryptedClientHello)
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddUint8(id)
builder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddBytes(pubKey)
})
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
for _, aeadID := range sortedSupportedAEADs {
builder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support
builder.AddUint16(aeadID)
}
})
builder.AddUint8(maxNameLen)
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddBytes([]byte(publicName))
})
builder.AddUint16(0) // extensions
})
return builder.BytesOrPanic()
}
func GenECHConfig(publicName string) (configBase64 string, keyPem string, err error) {
echKey, err := ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
return
}
echConfig := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)
builder := cryptobyte.NewBuilder(nil)
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddBytes(echConfig)
})
echConfigList := builder.BytesOrPanic()
builder2 := cryptobyte.NewBuilder(nil)
builder2.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddBytes(echKey.Bytes())
})
builder2.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
builder.AddBytes(echConfig)
})
echConfigKeys := builder2.BytesOrPanic()
configBase64 = base64.StdEncoding.EncodeToString(echConfigList)
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: echConfigKeys}))
return
}
func UnmarshalECHKeys(raw []byte) ([]tlsC.EncryptedClientHelloKey, error) {
var keys []tlsC.EncryptedClientHelloKey
rawString := cryptobyte.String(raw)
for !rawString.Empty() {
var key tlsC.EncryptedClientHelloKey
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) {
return nil, errors.New("error parsing private key")
}
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.Config)) {
return nil, errors.New("error parsing config")
}
keys = append(keys, key)
}
if len(keys) == 0 {
return nil, errors.New("empty ECH keys")
}
return keys, nil
}
func LoadECHKey(key string, tlsConfig *tlsC.Config, path ca.Path) error {
if key == "" {
return nil
}
painTextErr := loadECHKey([]byte(key), tlsConfig)
if painTextErr == nil {
return nil
}
key = path.Resolve(key)
var loadErr error
if !path.IsSafePath(key) {
loadErr = path.ErrNotSafePath(key)
} else {
var echKey []byte
echKey, loadErr = os.ReadFile(key)
if loadErr == nil {
loadErr = loadECHKey(echKey, tlsConfig)
}
}
if loadErr != nil {
return fmt.Errorf("parse ECH keys failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
}
return nil
}
func loadECHKey(echKey []byte, tlsConfig *tlsC.Config) error {
block, rest := pem.Decode(echKey)
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
return errors.New("invalid ECH keys pem")
}
echKeys, err := UnmarshalECHKeys(block.Bytes)
if err != nil {
return fmt.Errorf("parse ECH keys: %w", err)
}
tlsConfig.EncryptedClientHelloKeys = echKeys
return nil
}

View File

@@ -4,12 +4,14 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/metacubex/mihomo/component/ech"
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid/v5"
) )
func Main(args []string) { func Main(args []string) {
if len(args) < 1 { if len(args) < 1 {
panic("Using: generate uuid/reality-keypair/wg-keypair") panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair")
} }
switch args[0] { switch args[0] {
case "uuid": case "uuid":
@@ -33,5 +35,15 @@ func Main(args []string) {
} }
fmt.Println("PrivateKey: " + privateKey.String()) fmt.Println("PrivateKey: " + privateKey.String())
fmt.Println("PublicKey: " + privateKey.PublicKey().String()) fmt.Println("PublicKey: " + privateKey.PublicKey().String())
case "ech-keypair":
if len(args) < 2 {
panic("Using: generate ech-keypair <plain_server_name>")
}
configBase64, keyPem, err := ech.GenECHConfig(args[1])
if err != nil {
panic(err)
}
fmt.Println("Config:", configBase64)
fmt.Println("Key:", keyPem)
} }
} }

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/netip" "net/netip"
"strings"
"time" "time"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
@@ -49,6 +48,7 @@ type Resolver interface {
LookupIP(ctx context.Context, host string) (ips []netip.Addr, err error) LookupIP(ctx context.Context, host string) (ips []netip.Addr, err error)
LookupIPv4(ctx context.Context, host string) (ips []netip.Addr, err error) LookupIPv4(ctx context.Context, host string) (ips []netip.Addr, err error)
LookupIPv6(ctx context.Context, host string) (ips []netip.Addr, err error) LookupIPv6(ctx context.Context, host string) (ips []netip.Addr, err error)
ResolveECH(ctx context.Context, host string) ([]byte, error)
ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error) ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error)
Invalid() bool Invalid() bool
ClearCache() ClearCache()
@@ -67,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
@@ -116,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
@@ -165,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
} }
@@ -216,6 +219,17 @@ func ResolveIPPrefer6(ctx context.Context, host string) (netip.Addr, error) {
return ResolveIPPrefer6WithResolver(ctx, host, DefaultResolver) return ResolveIPPrefer6WithResolver(ctx, host, DefaultResolver)
} }
func ResolveECHWithResolver(ctx context.Context, host string, r Resolver) ([]byte, error) {
if r != nil && r.Invalid() {
return r.ResolveECH(ctx, host)
}
return SystemResolver.ResolveECH(ctx, host)
}
func ResolveECH(ctx context.Context, host string) ([]byte, error) {
return ResolveECHWithResolver(ctx, host, DefaultResolver)
}
func ResetConnection() { func ResetConnection() {
if DefaultResolver != nil { if DefaultResolver != nil {
go DefaultResolver.ResetConnection() go DefaultResolver.ResetConnection()

View File

@@ -105,6 +105,7 @@ func (f *Fetcher[V]) loadBuf(buf []byte, hash utils.HashType, updateFile bool) (
_ = os.Chtimes(f.vehicle.Path(), now, now) _ = os.Chtimes(f.vehicle.Path(), now, now)
} }
f.updatedAt = now f.updatedAt = now
f.backoff.Reset() // no error, reset backoff
return lo.Empty[V](), true, nil return lo.Empty[V](), true, nil
} }
@@ -220,6 +221,10 @@ func (f *Fetcher[V]) updateWithLog() {
func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] { func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
minBackoff := 10 * time.Second
if interval < minBackoff {
minBackoff = interval
}
return &Fetcher[V]{ return &Fetcher[V]{
ctx: ctx, ctx: ctx,
ctxCancel: cancel, ctxCancel: cancel,
@@ -231,7 +236,7 @@ func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicl
backoff: slowdown.Backoff{ backoff: slowdown.Backoff{
Factor: 2, Factor: 2,
Jitter: false, Jitter: false,
Min: time.Second, Min: minBackoff,
Max: interval, Max: interval,
}, },
} }

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,70 @@
package tls
import (
"context"
"net"
"net/http"
"runtime/debug"
"time"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/log"
"golang.org/x/net/http2"
)
func extractTlsHandshakeTimeoutFromServer(s *http.Server) time.Duration {
var ret time.Duration
for _, v := range [...]time.Duration{
s.ReadHeaderTimeout,
s.ReadTimeout,
s.WriteTimeout,
} {
if v <= 0 {
continue
}
if ret == 0 || v < ret {
ret = v
}
}
return ret
}
// NewListenerForHttps returns a net.Listener for (*http.Server).Serve()
// the "func (c *conn) serve(ctx context.Context)" in http\server.go
// only do tls handshake and check NegotiatedProtocol with std's *tls.Conn
// so we do the same logic to let http2 (not h2c) work fine
func NewListenerForHttps(l net.Listener, httpServer *http.Server, tlsConfig *Config) net.Listener {
http2Server := &http2.Server{}
_ = http2.ConfigureServer(httpServer, http2Server)
return N.NewHandleContextListener(context.Background(), l, func(ctx context.Context, conn net.Conn) (net.Conn, error) {
c := Server(conn, tlsConfig)
tlsTO := extractTlsHandshakeTimeoutFromServer(httpServer)
if tlsTO > 0 {
dl := time.Now().Add(tlsTO)
_ = conn.SetReadDeadline(dl)
_ = conn.SetWriteDeadline(dl)
}
err := c.HandshakeContext(ctx)
if err != nil {
return nil, err
}
// Restore Conn-level deadlines.
if tlsTO > 0 {
_ = conn.SetReadDeadline(time.Time{})
_ = conn.SetWriteDeadline(time.Time{})
}
if c.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS {
http2Server.ServeConn(c, &http2.ServeConnOpts{BaseConfig: httpServer})
return nil, net.ErrClosed
}
return c, nil
}, func(a any) {
stack := debug.Stack()
log.Errorln("https server panic: %s\n%s", a, stack)
})
}

View File

@@ -26,7 +26,6 @@ import (
utls "github.com/metacubex/utls" utls "github.com/metacubex/utls"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
"golang.org/x/exp/slices"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@@ -35,9 +34,11 @@ const RealityMaxShortIDLen = 8
type RealityConfig struct { type RealityConfig struct {
PublicKey *ecdh.PublicKey PublicKey *ecdh.PublicKey
ShortID [RealityMaxShortIDLen]byte ShortID [RealityMaxShortIDLen]byte
SupportX25519MLKEM768 bool
} }
func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHelloID, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) { func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHelloID, tlsConfig *Config, realityConfig *RealityConfig) (net.Conn, error) {
for retry := 0; ; retry++ { for retry := 0; ; retry++ {
verifier := &realityVerifier{ verifier := &realityVerifier{
serverName: tlsConfig.ServerName, serverName: tlsConfig.ServerName,
@@ -48,39 +49,18 @@ func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHello
SessionTicketsDisabled: true, SessionTicketsDisabled: true,
VerifyPeerCertificate: verifier.VerifyPeerCertificate, VerifyPeerCertificate: verifier.VerifyPeerCertificate,
} }
clientID := utls.ClientHelloID{
Client: fingerprint.Client, if !realityConfig.SupportX25519MLKEM768 && fingerprint == HelloChrome_Auto {
Version: fingerprint.Version, fingerprint = HelloChrome_120 // old reality server doesn't work with X25519MLKEM768
Seed: fingerprint.Seed,
} }
uConn := utls.UClient(conn, uConfig, clientID)
uConn := utls.UClient(conn, uConfig, fingerprint)
verifier.UConn = uConn verifier.UConn = uConn
err := uConn.BuildHandshakeState() err := uConn.BuildHandshakeState()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// ------for X25519MLKEM768 does not work properly with reality-------
// Iterate over extensions and check
for _, extension := range uConn.Extensions {
if ce, ok := extension.(*utls.SupportedCurvesExtension); ok {
ce.Curves = slices.DeleteFunc(ce.Curves, func(curveID utls.CurveID) bool {
return curveID == utls.X25519MLKEM768
})
}
if ks, ok := extension.(*utls.KeyShareExtension); ok {
ks.KeyShares = slices.DeleteFunc(ks.KeyShares, func(share utls.KeyShare) bool {
return share.Group == utls.X25519MLKEM768
})
}
}
// Rebuild the client hello
err = uConn.BuildHandshakeState()
if err != nil {
return nil, err
}
// --------------------------------------------------------------------
hello := uConn.HandshakeState.Hello hello := uConn.HandshakeState.Hello
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID rawSessionID := hello.Raw[39 : 39+32] // the location of session ID
for i := range rawSessionID { // https://github.com/golang/go/issues/5373 for i := range rawSessionID { // https://github.com/golang/go/issues/5373
@@ -144,7 +124,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHello
log.Debugln("REALITY Authentication: %v, AEAD: %T", verifier.verified, aeadCipher) log.Debugln("REALITY Authentication: %v, AEAD: %T", verifier.verified, aeadCipher)
if !verifier.verified { if !verifier.verified {
go realityClientFallback(uConn, uConfig.ServerName, clientID) go realityClientFallback(uConn, uConfig.ServerName, fingerprint)
return nil, errors.New("REALITY authentication failed") return nil, errors.New("REALITY authentication failed")
} }

View File

@@ -16,6 +16,7 @@ type Conn = utls.Conn
type UConn = utls.UConn type UConn = utls.UConn
type UClientHelloID = utls.ClientHelloID type UClientHelloID = utls.ClientHelloID
const VersionTLS12 = utls.VersionTLS12
const VersionTLS13 = utls.VersionTLS13 const VersionTLS13 = utls.VersionTLS13
func Client(c net.Conn, config *utls.Config) *Conn { func Client(c net.Conn, config *utls.Config) *Conn {
@@ -26,6 +27,14 @@ func UClient(c net.Conn, config *utls.Config, fingerprint UClientHelloID) *UConn
return utls.UClient(c, config, fingerprint) return utls.UClient(c, config, fingerprint)
} }
func Server(c net.Conn, config *utls.Config) *Conn {
return utls.Server(c, config)
}
func NewListener(inner net.Listener, config *Config) net.Listener {
return utls.NewListener(inner, config)
}
func GetFingerprint(clientFingerprint string) (UClientHelloID, bool) { func GetFingerprint(clientFingerprint string) (UClientHelloID, bool) {
if len(clientFingerprint) == 0 { if len(clientFingerprint) == 0 {
clientFingerprint = globalFingerprint clientFingerprint = globalFingerprint
@@ -65,21 +74,26 @@ var randomFingerprint = once.OnceValue(func() UClientHelloID {
return fingerprint return fingerprint
}) })
var HelloChrome_Auto = utls.HelloChrome_Auto
var HelloChrome_120 = utls.HelloChrome_120 // special fingerprint for some old protocols doesn't work with HelloChrome_Auto
var fingerprints = map[string]UClientHelloID{ var fingerprints = map[string]UClientHelloID{
"chrome": utls.HelloChrome_Auto, "chrome": utls.HelloChrome_Auto,
"firefox": utls.HelloFirefox_Auto,
"safari": utls.HelloSafari_Auto,
"ios": utls.HelloIOS_Auto,
"android": utls.HelloAndroid_11_OkHttp,
"edge": utls.HelloEdge_Auto,
"360": utls.Hello360_Auto,
"qq": utls.HelloQQ_Auto,
"random": {},
// deprecated fingerprints should not be used
"chrome_psk": utls.HelloChrome_100_PSK, "chrome_psk": utls.HelloChrome_100_PSK,
"chrome_psk_shuffle": utls.HelloChrome_106_Shuffle, "chrome_psk_shuffle": utls.HelloChrome_106_Shuffle,
"chrome_padding_psk_shuffle": utls.HelloChrome_114_Padding_PSK_Shuf, "chrome_padding_psk_shuffle": utls.HelloChrome_114_Padding_PSK_Shuf,
"chrome_pq": utls.HelloChrome_115_PQ, "chrome_pq": utls.HelloChrome_115_PQ,
"chrome_pq_psk": utls.HelloChrome_115_PQ_PSK, "chrome_pq_psk": utls.HelloChrome_115_PQ_PSK,
"firefox": utls.HelloFirefox_Auto,
"safari": utls.HelloSafari_Auto,
"ios": utls.HelloIOS_Auto,
"android": utls.HelloAndroid_11_OkHttp,
"edge": utls.HelloEdge_Auto,
"360": utls.Hello360_Auto,
"qq": utls.HelloQQ_Auto,
"random": {},
"randomized": utls.HelloRandomized, "randomized": utls.HelloRandomized,
} }
@@ -93,7 +107,9 @@ func init() {
fingerprints["randomized"] = randomized fingerprints["randomized"] = randomized
} }
func UCertificates(it tls.Certificate) utls.Certificate { type Certificate = utls.Certificate
func UCertificate(it tls.Certificate) utls.Certificate {
return utls.Certificate{ return utls.Certificate{
Certificate: it.Certificate, Certificate: it.Certificate,
PrivateKey: it.PrivateKey, PrivateKey: it.PrivateKey,
@@ -106,13 +122,15 @@ func UCertificates(it tls.Certificate) utls.Certificate {
} }
} }
type EncryptedClientHelloKey = utls.EncryptedClientHelloKey
type Config = utls.Config type Config = utls.Config
func UConfig(config *tls.Config) *utls.Config { func UConfig(config *tls.Config) *utls.Config {
return &utls.Config{ return &utls.Config{
Rand: config.Rand, Rand: config.Rand,
Time: config.Time, Time: config.Time,
Certificates: utils.Map(config.Certificates, UCertificates), Certificates: utils.Map(config.Certificates, UCertificate),
VerifyPeerCertificate: config.VerifyPeerCertificate, VerifyPeerCertificate: config.VerifyPeerCertificate,
RootCAs: config.RootCAs, RootCAs: config.RootCAs,
NextProtos: config.NextProtos, NextProtos: config.NextProtos,

View File

@@ -3,6 +3,7 @@ package updater
import ( import (
"archive/tar" "archive/tar"
"archive/zip" "archive/zip"
"bytes"
"compress/gzip" "compress/gzip"
"fmt" "fmt"
"io" "io"
@@ -32,6 +33,17 @@ const (
typeTarGzip typeTarGzip
) )
func (t compressionType) String() string {
switch t {
case typeZip:
return "zip"
case typeTarGzip:
return "tar.gz"
default:
return "unknown"
}
}
var DefaultUiUpdater = &UIUpdater{} var DefaultUiUpdater = &UIUpdater{}
func NewUiUpdater(externalUI, externalUIURL, externalUIName string) *UIUpdater { func NewUiUpdater(externalUI, externalUIURL, externalUIName string) *UIUpdater {
@@ -99,48 +111,38 @@ func detectFileType(data []byte) compressionType {
} }
func (u *UIUpdater) downloadUI() error { func (u *UIUpdater) downloadUI() error {
err := u.prepareUIPath()
if err != nil {
return fmt.Errorf("prepare UI path failed: %w", err)
}
data, err := downloadForBytes(u.externalUIURL) data, err := downloadForBytes(u.externalUIURL)
if err != nil { if err != nil {
return fmt.Errorf("can't download file: %w", err) return fmt.Errorf("can't download file: %w", err)
} }
fileType := detectFileType(data) tmpDir := C.Path.Resolve("downloadUI.tmp")
if fileType == typeUnknown { defer os.RemoveAll(tmpDir)
return fmt.Errorf("unknown or unsupported file type")
os.RemoveAll(tmpDir) // cleanup tmp dir before extract
log.Debugln("extractedFolder: %s", tmpDir)
err = extract(data, tmpDir)
if err != nil {
return fmt.Errorf("can't extract compressed file: %w", err)
} }
ext := ".zip" log.Debugln("cleanupFolder: %s", u.externalUIPath)
if fileType == typeTarGzip { err = cleanup(u.externalUIPath) // cleanup files in dir don't remove dir itself
ext = ".tgz"
}
saved := path.Join(C.Path.HomeDir(), "download"+ext)
log.Debugln("compression Type: %s", ext)
if err = saveFile(data, saved); err != nil {
return fmt.Errorf("can't save compressed file: %w", err)
}
defer os.Remove(saved)
err = cleanup(u.externalUIPath)
if err != nil { if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return fmt.Errorf("cleanup exist file error: %w", err) return fmt.Errorf("cleanup exist file error: %w", err)
} }
} }
extractedFolder, err := extract(saved, C.Path.HomeDir()) err = u.prepareUIPath()
if err != nil { if err != nil {
return fmt.Errorf("can't extract compressed file: %w", err) return fmt.Errorf("prepare UI path failed: %w", err)
} }
err = os.Rename(extractedFolder, u.externalUIPath) log.Debugln("moveFolder from %s to %s", tmpDir, u.externalUIPath)
err = moveDir(tmpDir, u.externalUIPath) // move files from tmp to target
if err != nil { if err != nil {
return fmt.Errorf("rename UI folder failed: %w", err) return fmt.Errorf("move UI folder failed: %w", err)
} }
return nil return nil
} }
@@ -155,228 +157,109 @@ func (u *UIUpdater) prepareUIPath() error {
return nil return nil
} }
func unzip(src, dest string) (string, error) { func unzip(data []byte, dest string) error {
r, err := zip.OpenReader(src) r, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
if err != nil { if err != nil {
return "", err return err
} }
defer r.Close()
// check whether or not only exists singleRoot dir // check whether or not only exists singleRoot dir
rootDir := ""
isSingleRoot := true
rootItemCount := 0
for _, f := range r.File {
parts := strings.Split(strings.Trim(f.Name, "/"), "/")
if len(parts) == 0 {
continue
}
if len(parts) == 1 {
isDir := strings.HasSuffix(f.Name, "/")
if !isDir {
isSingleRoot = false
break
}
if rootDir == "" {
rootDir = parts[0]
}
rootItemCount++
}
}
if rootItemCount != 1 {
isSingleRoot = false
}
// build the dir of extraction
var extractedFolder string
if isSingleRoot && rootDir != "" {
// if the singleRoot, use it directly
log.Debugln("Match the singleRoot")
extractedFolder = filepath.Join(dest, rootDir)
log.Debugln("extractedFolder: %s", extractedFolder)
} else {
log.Debugln("Match the multiRoot")
// or put the files/dirs into new dir
baseName := filepath.Base(src)
baseName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
extractedFolder = filepath.Join(dest, baseName)
for i := 1; ; i++ {
if _, err := os.Stat(extractedFolder); os.IsNotExist(err) {
break
}
extractedFolder = filepath.Join(dest, fmt.Sprintf("%s_%d", baseName, i))
}
log.Debugln("extractedFolder: %s", extractedFolder)
}
for _, f := range r.File { for _, f := range r.File {
var fpath string fpath := filepath.Join(dest, f.Name)
if isSingleRoot && rootDir != "" {
fpath = filepath.Join(dest, f.Name)
} else {
fpath = filepath.Join(extractedFolder, f.Name)
}
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { if !inDest(fpath, dest) {
return "", fmt.Errorf("invalid file path: %s", fpath) return fmt.Errorf("invalid file path: %s", fpath)
} }
if f.FileInfo().IsDir() { info := f.FileInfo()
if info.IsDir() {
os.MkdirAll(fpath, os.ModePerm) os.MkdirAll(fpath, os.ModePerm)
continue continue
} }
if info.Mode()&os.ModeSymlink != 0 {
continue // disallow symlink
}
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return "", err return err
} }
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil { if err != nil {
return "", err return err
} }
rc, err := f.Open() rc, err := f.Open()
if err != nil { if err != nil {
return "", err return err
} }
_, err = io.Copy(outFile, rc) _, err = io.Copy(outFile, rc)
outFile.Close() outFile.Close()
rc.Close() rc.Close()
if err != nil { if err != nil {
return "", err return err
} }
} }
return extractedFolder, nil return nil
} }
func untgz(src, dest string) (string, error) { func untgz(data []byte, dest string) error {
file, err := os.Open(src) gzr, err := gzip.NewReader(bytes.NewReader(data))
if err != nil { if err != nil {
return "", err return err
}
defer file.Close()
gzr, err := gzip.NewReader(file)
if err != nil {
return "", err
} }
defer gzr.Close() defer gzr.Close()
tr := tar.NewReader(gzr) tr := tar.NewReader(gzr)
rootDir := "" _ = gzr.Reset(bytes.NewReader(data))
isSingleRoot := true
rootItemCount := 0
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return "", err
}
parts := strings.Split(cleanTarPath(header.Name), string(os.PathSeparator))
if len(parts) == 0 {
continue
}
if len(parts) == 1 {
isDir := header.Typeflag == tar.TypeDir
if !isDir {
isSingleRoot = false
break
}
if rootDir == "" {
rootDir = parts[0]
}
rootItemCount++
}
}
if rootItemCount != 1 {
isSingleRoot = false
}
file.Seek(0, 0)
gzr, _ = gzip.NewReader(file)
tr = tar.NewReader(gzr) tr = tar.NewReader(gzr)
var extractedFolder string
if isSingleRoot && rootDir != "" {
log.Debugln("Match the singleRoot")
extractedFolder = filepath.Join(dest, rootDir)
log.Debugln("extractedFolder: %s", extractedFolder)
} else {
log.Debugln("Match the multiRoot")
baseName := filepath.Base(src)
baseName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
baseName = strings.TrimSuffix(baseName, ".tar")
extractedFolder = filepath.Join(dest, baseName)
for i := 1; ; i++ {
if _, err := os.Stat(extractedFolder); os.IsNotExist(err) {
break
}
extractedFolder = filepath.Join(dest, fmt.Sprintf("%s_%d", baseName, i))
}
log.Debugln("extractedFolder: %s", extractedFolder)
}
for { for {
header, err := tr.Next() header, err := tr.Next()
if err == io.EOF { if err == io.EOF {
break break
} }
if err != nil { if err != nil {
return "", err return err
} }
var fpath string fpath := filepath.Join(dest, header.Name)
if isSingleRoot && rootDir != "" {
fpath = filepath.Join(dest, cleanTarPath(header.Name))
} else {
fpath = filepath.Join(extractedFolder, cleanTarPath(header.Name))
}
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { if !inDest(fpath, dest) {
return "", fmt.Errorf("invalid file path: %s", fpath) return fmt.Errorf("invalid file path: %s", fpath)
} }
switch header.Typeflag { switch header.Typeflag {
case tar.TypeDir: case tar.TypeDir:
if err = os.MkdirAll(fpath, os.FileMode(header.Mode)); err != nil { if err = os.MkdirAll(fpath, os.FileMode(header.Mode)); err != nil {
return "", err return err
} }
case tar.TypeReg: case tar.TypeReg:
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return "", err return err
} }
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode)) outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode))
if err != nil { if err != nil {
return "", err return err
} }
if _, err := io.Copy(outFile, tr); err != nil { if _, err := io.Copy(outFile, tr); err != nil {
outFile.Close() outFile.Close()
return "", err return err
} }
outFile.Close() outFile.Close()
} }
} }
return extractedFolder, nil return nil
} }
func extract(src, dest string) (string, error) { func extract(data []byte, dest string) error {
srcLower := strings.ToLower(src) fileType := detectFileType(data)
switch { log.Debugln("compression Type: %s", fileType)
case strings.HasSuffix(srcLower, ".tar.gz") || switch fileType {
strings.HasSuffix(srcLower, ".tgz"): case typeZip:
return untgz(src, dest) return unzip(data, dest)
case strings.HasSuffix(srcLower, ".zip"): case typeTarGzip:
return unzip(src, dest) return untgz(data, dest)
default: default:
return "", fmt.Errorf("unsupported file format: %s", src) return fmt.Errorf("unknown or unsupported file type")
} }
} }
@@ -398,22 +281,49 @@ func cleanTarPath(path string) string {
} }
func cleanup(root string) error { func cleanup(root string) error {
if _, err := os.Stat(root); os.IsNotExist(err) { dirEntryList, err := os.ReadDir(root)
return nil if err != nil {
return err
} }
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
for _, dirEntry := range dirEntryList {
err = os.RemoveAll(filepath.Join(root, dirEntry.Name()))
if err != nil { if err != nil {
return err return err
} }
if info.IsDir() { }
if err := os.RemoveAll(path); err != nil { return nil
return err }
}
} else { func moveDir(src string, dst string) error {
if err := os.Remove(path); err != nil { dirEntryList, err := os.ReadDir(src)
return err if err != nil {
} return err
} }
return nil
}) if len(dirEntryList) == 1 && dirEntryList[0].IsDir() {
src = filepath.Join(src, dirEntryList[0].Name())
log.Debugln("match the singleRoot: %s", src)
dirEntryList, err = os.ReadDir(src)
if err != nil {
return err
}
}
for _, dirEntry := range dirEntryList {
err = os.Rename(filepath.Join(src, dirEntry.Name()), filepath.Join(dst, dirEntry.Name()))
if err != nil {
return err
}
}
return nil
}
func inDest(fpath, dest string) bool {
if rel, err := filepath.Rel(dest, fpath); err == nil {
if filepath.IsLocal(rel) {
return true
}
}
return false
} }

View File

@@ -7,6 +7,7 @@ import (
"net" "net"
"net/netip" "net/netip"
"net/url" "net/url"
"path/filepath"
"strings" "strings"
"time" "time"
_ "unsafe" _ "unsafe"
@@ -174,6 +175,7 @@ type Profile struct {
type TLS struct { type TLS struct {
Certificate string Certificate string
PrivateKey string PrivateKey string
EchKey string
CustomTrustCert []string CustomTrustCert []string
} }
@@ -360,6 +362,7 @@ type RawSniffingConfig struct {
type RawTLS struct { type RawTLS struct {
Certificate string `yaml:"certificate" json:"certificate"` Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"` PrivateKey string `yaml:"private-key" json:"private-key"`
EchKey string `yaml:"ech-key" json:"ech-key"`
CustomTrustCert []string `yaml:"custom-certifactes" json:"custom-certifactes"` CustomTrustCert []string `yaml:"custom-certifactes" json:"custom-certifactes"`
} }
@@ -755,7 +758,10 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
func parseController(cfg *RawConfig) (*Controller, error) { func parseController(cfg *RawConfig) (*Controller, error) {
if path := cfg.ExternalUI; path != "" && !C.Path.IsSafePath(path) { if path := cfg.ExternalUI; path != "" && !C.Path.IsSafePath(path) {
return nil, fmt.Errorf("path is not subpath of home directory: %s", path) return nil, C.Path.ErrNotSafePath(path)
}
if uiName := cfg.ExternalUIName; uiName != "" && !filepath.IsLocal(uiName) {
return nil, fmt.Errorf("external UI name is not local: %s", uiName)
} }
return &Controller{ return &Controller{
ExternalController: cfg.ExternalController, ExternalController: cfg.ExternalController,
@@ -814,6 +820,7 @@ func parseTLS(cfg *RawConfig) (*TLS, error) {
return &TLS{ return &TLS{
Certificate: cfg.TLS.Certificate, Certificate: cfg.TLS.Certificate,
PrivateKey: cfg.TLS.PrivateKey, PrivateKey: cfg.TLS.PrivateKey,
EchKey: cfg.TLS.EchKey,
CustomTrustCert: cfg.TLS.CustomTrustCert, CustomTrustCert: cfg.TLS.CustomTrustCert,
}, nil }, 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

@@ -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

@@ -1,6 +1,7 @@
package constant package constant
import ( import (
"fmt"
"os" "os"
P "path" P "path"
"path/filepath" "path/filepath"
@@ -87,10 +88,8 @@ func (p *path) IsSafePath(path string) bool {
if p.allowUnsafePath || features.CMFA { if p.allowUnsafePath || features.CMFA {
return true return true
} }
homedir := p.HomeDir()
path = p.Resolve(path) path = p.Resolve(path)
safePaths := append([]string{homedir}, p.safePaths...) // add homedir to safePaths for _, safePath := range p.SafePaths() {
for _, safePath := range safePaths {
if rel, err := filepath.Rel(safePath, path); err == nil { if rel, err := filepath.Rel(safePath, path); err == nil {
if filepath.IsLocal(rel) { if filepath.IsLocal(rel) {
return true return true
@@ -100,6 +99,23 @@ func (p *path) IsSafePath(path string) bool {
return false return false
} }
func (p *path) SafePaths() []string {
return append([]string{p.homeDir}, p.safePaths...) // add homedir to safePaths
}
func (p *path) ErrNotSafePath(path string) error {
return ErrNotSafePath{Path: path, SafePaths: p.SafePaths()}
}
type ErrNotSafePath struct {
Path string
SafePaths []string
}
func (e ErrNotSafePath) Error() string {
return fmt.Sprintf("path is not subpath of home directory or SAFE_PATHS: %s \n allowed paths: %s", e.Path, e.SafePaths)
}
func (p *path) GetPathByHash(prefix, name string) string { func (p *path) GetPathByHash(prefix, name string) string {
hash := utils.MakeHash([]byte(name)) hash := utils.MakeHash([]byte(name))
filename := hash.String() filename := hash.String()

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

@@ -77,7 +77,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
msg, _, err := c.Client.ExchangeWithConn(m, dConn) msg, _, err := c.Client.ExchangeWithConn(m, dConn)
// Resolvers MUST resend queries over TCP if they receive a truncated UDP response (with TC=1 set)! // Resolvers MUST resend queries over TCP if they receive a truncated UDP response (with TC=1 set)!
if msg != nil && msg.Truncated && c.Client.Net == "" { if msg != nil && msg.Truncated && network == "udp" {
tcpClient := *c.Client // copy a client tcpClient := *c.Client // copy a client
tcpClient.Net = "tcp" tcpClient.Net = "tcp"
network = "tcp" network = "tcp"

View File

@@ -127,6 +127,28 @@ func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
return false return false
} }
func (r *Resolver) ResolveECH(ctx context.Context, host string) ([]byte, error) {
query := &D.Msg{}
query.SetQuestion(D.Fqdn(host), D.TypeHTTPS)
msg, err := r.ExchangeContext(ctx, query)
if err != nil {
return nil, err
}
for _, rr := range msg.Answer {
switch resource := rr.(type) {
case *D.HTTPS:
for _, value := range resource.Value {
if echConfig, ok := value.(*D.SVCBECHConfig); ok {
return echConfig.ECH, nil
}
}
}
}
return nil, errors.New("no ECH config found in DNS records")
}
// ExchangeContext a batch of dns request with context.Context, and it use cache // ExchangeContext a batch of dns request with context.Context, and it use cache
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
if len(m.Question) == 0 { if len(m.Question) == 0 {
@@ -326,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

@@ -48,6 +48,13 @@ ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS
tls: tls:
certificate: string # 证书 PEM 格式,或者 证书的路径 certificate: string # 证书 PEM 格式,或者 证书的路径
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径 private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
# dC5jb20AAA==
# -----END ECH KEYS-----
custom-certifactes: custom-certifactes:
- | - |
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
@@ -427,6 +434,10 @@ proxies: # socks5
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取 # 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 配置指纹将实现 SSL Pining 效果 # 配置指纹将实现 SSL Pining 效果
# fingerprint: xxxx # fingerprint: xxxx
# ech-opts:
# enable: true # 必须手动开启
# # 如果config为空则通过dns解析不为空则通过该值指定格式为经过base64编码的ech参数dig +short TYPE65 tls-ech.dev
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
# skip-cert-verify: true # skip-cert-verify: true
# host: bing.com # host: bing.com
# path: "/" # path: "/"
@@ -527,6 +538,10 @@ proxies: # socks5
# skip-cert-verify: true # skip-cert-verify: true
# servername: example.com # priority over wss host # servername: example.com # priority over wss host
# network: ws # network: ws
# ech-opts:
# enable: true # 必须手动开启
# # 如果config为空则通过dns解析不为空则通过该值指定格式为经过base64编码的ech参数dig +short TYPE65 tls-ech.dev
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
# ws-opts: # ws-opts:
# path: /path # path: /path
# headers: # headers:
@@ -599,6 +614,10 @@ proxies: # socks5
# skip-cert-verify: true # skip-cert-verify: true
# fingerprint: xxxx # fingerprint: xxxx
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none" # client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
# ech-opts:
# enable: true # 必须手动开启
# # 如果config为空则通过dns解析不为空则通过该值指定格式为经过base64编码的ech参数dig +short TYPE65 tls-ech.dev
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
- name: "vless-vision" - name: "vless-vision"
type: vless type: vless
@@ -626,6 +645,7 @@ proxies: # socks5
reality-opts: reality-opts:
public-key: xxx public-key: xxx
short-id: xxx # optional short-id: xxx # optional
support-x25519mlkem768: false # 如果服务端支持可手动设置为true
client-fingerprint: chrome # cannot be empty client-fingerprint: chrome # cannot be empty
- name: "vless-reality-grpc" - name: "vless-reality-grpc"
@@ -645,6 +665,7 @@ proxies: # socks5
reality-opts: reality-opts:
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
short-id: 10f897e26c4b9478 short-id: 10f897e26c4b9478
support-x25519mlkem768: false # 如果服务端支持可手动设置为true
- name: "vless-ws" - name: "vless-ws"
type: vless type: vless
@@ -683,6 +704,10 @@ proxies: # socks5
# 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
# password: "example" # password: "example"
# ech-opts:
# enable: true # 必须手动开启
# # 如果config为空则通过dns解析不为空则通过该值指定格式为经过base64编码的ech参数dig +short TYPE65 tls-ech.dev
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
- name: trojan-grpc - name: trojan-grpc
server: server server: server
@@ -740,6 +765,10 @@ proxies: # socks5
up: "30 Mbps" # 若不写单位,默认为 Mbps up: "30 Mbps" # 若不写单位,默认为 Mbps
down: "200 Mbps" # 若不写单位,默认为 Mbps down: "200 Mbps" # 若不写单位,默认为 Mbps
# sni: server.com # sni: server.com
# ech-opts:
# enable: true # 必须手动开启
# # 如果config为空则通过dns解析不为空则通过该值指定格式为经过base64编码的ech参数dig +short TYPE65 tls-ech.dev
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
# skip-cert-verify: false # skip-cert-verify: false
# recv-window-conn: 12582912 # recv-window-conn: 12582912
# recv-window: 52428800 # recv-window: 52428800
@@ -763,6 +792,10 @@ proxies: # socks5
# obfs: salamander # 默认为空,如果填写则开启 obfs目前仅支持 salamander # obfs: salamander # 默认为空,如果填写则开启 obfs目前仅支持 salamander
# obfs-password: yourpassword # obfs-password: yourpassword
# sni: server.com # sni: server.com
# ech-opts:
# enable: true # 必须手动开启
# # 如果config为空则通过dns解析不为空则通过该值指定格式为经过base64编码的ech参数dig +short TYPE65 tls-ech.dev
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
# skip-cert-verify: false # skip-cert-verify: false
# fingerprint: xxxx # fingerprint: xxxx
# alpn: # alpn:
@@ -838,6 +871,10 @@ proxies: # socks5
# skip-cert-verify: true # skip-cert-verify: true
# max-open-streams: 20 # default 100, too many open streams may hurt performance # max-open-streams: 20 # default 100, too many open streams may hurt performance
# sni: example.com # sni: example.com
# ech-opts:
# enable: true # 必须手动开启
# # 如果config为空则通过dns解析不为空则通过该值指定格式为经过base64编码的ech参数dig +short TYPE65 tls-ech.dev
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
# #
# meta 和 sing-box 私有扩展,将 ss-uot 用于 udp 中继,开启此选项后 udp-relay-mode 将失效 # meta 和 sing-box 私有扩展,将 ss-uot 用于 udp 中继,开启此选项后 udp-relay-mode 将失效
# 警告,与原版 tuic 不兼容!!! # 警告,与原版 tuic 不兼容!!!
@@ -906,6 +943,12 @@ proxies: # socks5
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理 # dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
- name: "dns-out" - name: "dns-out"
type: dns type: dns
# 配置指定 interface-name 和 fwmark 的 DIRECT
- name: en1-direct
type: direct
interface-name: en1
routing-mark: 6667
proxy-groups: proxy-groups:
# 代理链,目前 relay 可以支持 udp 的只有 vmess/vless/trojan/ss/ssr/tuic # 代理链,目前 relay 可以支持 udp 的只有 vmess/vless/trojan/ss/ssr/tuic
# wireguard 目前不支持在 relay 中使用,请使用 proxy 中的 dialer-proxy 配置项 # wireguard 目前不支持在 relay 中使用,请使用 proxy 中的 dialer-proxy 配置项
@@ -962,14 +1005,6 @@ proxy-groups:
- vmess1 - vmess1
- auto - auto
# 配置指定 interface-name 和 fwmark 的 DIRECT
- name: en1
type: select
interface-name: en1
routing-mark: 6667
proxies:
- DIRECT
- name: UseProvider - name: UseProvider
type: select type: select
filter: "HK|TW" # 正则表达式,过滤 provider1 中节点名包含 HK 或 TW filter: "HK|TW" # 正则表达式,过滤 provider1 中节点名包含 HK 或 TW
@@ -1129,6 +1164,13 @@ listeners:
# 下面两项如果填写则开启 tls需要同时填写 # 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt # certificate: ./server.crt
# private-key: ./server.key # private-key: ./server.key
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
# dC5jb20AAA==
# -----END ECH KEYS-----
- name: http-in-1 - name: http-in-1
type: http type: http
@@ -1142,6 +1184,13 @@ listeners:
# 下面两项如果填写则开启 tls需要同时填写 # 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt # certificate: ./server.crt
# private-key: ./server.key # private-key: ./server.key
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
# dC5jb20AAA==
# -----END ECH KEYS-----
- name: mixed-in-1 - name: mixed-in-1
type: mixed # HTTP(S) 和 SOCKS 代理混合 type: mixed # HTTP(S) 和 SOCKS 代理混合
@@ -1156,6 +1205,13 @@ listeners:
# 下面两项如果填写则开启 tls需要同时填写 # 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt # certificate: ./server.crt
# private-key: ./server.key # private-key: ./server.key
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
# dC5jb20AAA==
# -----END ECH KEYS-----
- name: reidr-in-1 - name: reidr-in-1
type: redir type: redir
@@ -1205,6 +1261,13 @@ listeners:
# 下面两项如果填写则开启 tls需要同时填写 # 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt # certificate: ./server.crt
# private-key: ./server.key # private-key: ./server.key
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
# dC5jb20AAA==
# -----END ECH KEYS-----
# 如果填写reality-config则开启reality注意不可与certificate和private-key同时填写 # 如果填写reality-config则开启reality注意不可与certificate和private-key同时填写
# reality-config: # reality-config:
# dest: test.com:443 # dest: test.com:443
@@ -1227,6 +1290,13 @@ listeners:
# 00000000-0000-0000-0000-000000000001: PASSWORD_1 # 00000000-0000-0000-0000-000000000001: PASSWORD_1
# certificate: ./server.crt # certificate: ./server.crt
# private-key: ./server.key # private-key: ./server.key
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
# dC5jb20AAA==
# -----END ECH KEYS-----
# congestion-controller: bbr # congestion-controller: bbr
# max-idle-time: 15000 # max-idle-time: 15000
# authentication-timeout: 1000 # authentication-timeout: 1000
@@ -1258,6 +1328,13 @@ listeners:
# 下面两项如果填写则开启 tls需要同时填写 # 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt # certificate: ./server.crt
# private-key: ./server.key # private-key: ./server.key
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
# dC5jb20AAA==
# -----END ECH KEYS-----
# 如果填写reality-config则开启reality注意不可与certificate和private-key同时填写 # 如果填写reality-config则开启reality注意不可与certificate和private-key同时填写
reality-config: reality-config:
dest: test.com:443 dest: test.com:443
@@ -1278,6 +1355,13 @@ listeners:
# "certificate" and "private-key" are required # "certificate" and "private-key" are required
certificate: ./server.crt certificate: ./server.crt
private-key: ./server.key private-key: ./server.key
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
# dC5jb20AAA==
# -----END ECH KEYS-----
# padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme # padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme
- name: trojan-in-1 - name: trojan-in-1
@@ -1294,6 +1378,13 @@ listeners:
# 下面两项如果填写则开启 tls需要同时填写 # 下面两项如果填写则开启 tls需要同时填写
certificate: ./server.crt certificate: ./server.crt
private-key: ./server.key private-key: ./server.key
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
# dC5jb20AAA==
# -----END ECH KEYS-----
# 如果填写reality-config则开启reality注意不可与certificate和private-key同时填写 # 如果填写reality-config则开启reality注意不可与certificate和private-key同时填写
# reality-config: # reality-config:
# dest: test.com:443 # dest: test.com:443
@@ -1319,6 +1410,13 @@ listeners:
00000000-0000-0000-0000-000000000001: PASSWORD_1 00000000-0000-0000-0000-000000000001: PASSWORD_1
# certificate: ./server.crt # certificate: ./server.crt
# private-key: ./server.key # private-key: ./server.key
# 如果填写则开启ech可由 mihomo generate ech-keypair <明文域名> 生成)
# ech-key: |
# -----BEGIN ECH KEYS-----
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
# dC5jb20AAA==
# -----END ECH KEYS-----
## up 和 down 均不写或为 0 则使用 BBR 流控 ## up 和 down 均不写或为 0 则使用 BBR 流控
# up: "30 Mbps" # 若不写单位,默认为 Mbps # up: "30 Mbps" # 若不写单位,默认为 Mbps
# down: "200 Mbps" # 若不写单位,默认为 Mbps # down: "200 Mbps" # 若不写单位,默认为 Mbps

22
go.mod
View File

@@ -18,25 +18,25 @@ require (
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.2 github.com/mdlayher/netlink v1.7.2
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.19.0 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.2
github.com/metacubex/fswatch v0.1.1 github.com/metacubex/fswatch v0.1.1
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.51.1-0.20250423035655-e3948b36ce14 github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639
github.com/metacubex/randv2 v0.2.0 github.com/metacubex/randv2 v0.2.0
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 github.com/metacubex/sing v0.5.3
github.com/metacubex/sing-mux v0.3.2 github.com/metacubex/sing-mux v0.3.2
github.com/metacubex/sing-quic v0.0.0-20250504030450-1e678cb3d50b github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f
github.com/metacubex/sing-shadowsocks v0.2.9 github.com/metacubex/sing-shadowsocks v0.2.10
github.com/metacubex/sing-shadowsocks2 v0.2.3 github.com/metacubex/sing-shadowsocks2 v0.2.4
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c
github.com/metacubex/sing-vmess v0.2.1 github.com/metacubex/sing-vmess v0.2.2
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4
github.com/metacubex/utls v1.7.0-alpha.2 github.com/metacubex/utls v1.7.3
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
@@ -72,7 +72,7 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect github.com/cloudflare/circl v1.3.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615 // indirect github.com/ebitengine/purego v0.8.3 // indirect
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect

44
go.sum
View File

@@ -26,8 +26,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615 h1:W7mpP4uiOAbBOdDnRXT9EUdauFv7bz+ERT5rPIord00= github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98= github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98=
github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
@@ -97,8 +97,8 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4= github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI= github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY= github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM=
github.com/metacubex/bart v0.19.0/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.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
@@ -111,35 +111,35 @@ 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.20250423035655-e3948b36ce14 h1:vhB4KEgiN89xXtLlyYWczu3AxgN2T1lp0kIDYT4Faag= github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639 h1:L+1brQNzBhCCxWlicwfK1TlceemCRmrDE4HmcVHc29w=
github.com/metacubex/quic-go v0.51.1-0.20250423035655-e3948b36ce14/go.mod h1:9R1NOzCgTcWsdWvOMlmtMuF0uKzuOpsfvEf7U3I8zM0= github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639/go.mod h1:Kc6h++Q/zf3AxcUCevJhJwgrskJumv+pZdR8g/E/10k=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxMzzmnXams+ebwabcry4Ydep/zNiesQ= github.com/metacubex/sing v0.5.3 h1:QWdN16WFKMk06x4nzkc8SvZ7y2x+TLQrpkPoHs+WSVM=
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= github.com/metacubex/sing v0.5.3/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw= github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw= github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
github.com/metacubex/sing-quic v0.0.0-20250504030450-1e678cb3d50b h1:JKx0yY/eXU7U5tHiAxANytFtkfEjzOte19qLlc+pFeY= github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I=
github.com/metacubex/sing-quic v0.0.0-20250504030450-1e678cb3d50b/go.mod h1:mqtr9bgM9eLvLKQqiLOi5I6AJHkvqAw2a61ABZcLuoE= github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00= github.com/metacubex/sing-shadowsocks v0.2.10 h1:Pr7LDbjMANIQHl07zWgl1vDuhpsfDQUpZ8cX6DPabfg=
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA= github.com/metacubex/sing-shadowsocks v0.2.10/go.mod h1:MtRM0ZZjR0kaDOzy9zWSt6/4/UlrnsNBq+1FNAF4vBk=
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0= github.com/metacubex/sing-shadowsocks2 v0.2.4 h1:Ec0x3hHR7xkld5Z09IGh16wtUUpBb2HgqZ9DExd8Q7s=
github.com/metacubex/sing-shadowsocks2 v0.2.3/go.mod h1:/WNy/Q8ahLCoPRriWuFZFD0Jy+JNp1MEQl28Zw6SaF8= github.com/metacubex/sing-shadowsocks2 v0.2.4/go.mod h1:WP8+S0kqtnSbX1vlIpo5i8Irm/ijZITEPBcZ26B5unY=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 h1:TAwL91XPa6x1QK55CRm+VTzPvLPUfEr/uFDnOZArqEU= github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c h1:Y6jk7AH5BEg9Dsvczrf/KokYsvxeKSZZlCLHg+hC4ro=
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U= github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
github.com/metacubex/sing-vmess v0.2.1 h1:I6gM3VUjtvJ15D805EUbNH+SRBuqzJeFnuIbKYUsWZ0= github.com/metacubex/sing-vmess v0.2.2 h1:nG6GIKF1UOGmlzs+BIetdGHkFZ20YqFVIYp5Htqzp+4=
github.com/metacubex/sing-vmess v0.2.1/go.mod h1:DsODWItJtOMZNna8Qhheg8r3tUivrcO3vWgaTYKnfTo= github.com/metacubex/sing-vmess v0.2.2/go.mod h1:CVDNcdSLVYFgTHQlubr88d8CdqupAUDqLjROos+H9xk=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo= github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
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-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY= github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.7.0-alpha.2 h1:kLRg6zDV12R1uclL5qW9Tx4RD6ztGIIrTZWY5zrJXCg= github.com/metacubex/utls v1.7.3 h1:yDcMEWojFh+t8rU9X0HPcZDPAoFze/rIIyssqivzj8A=
github.com/metacubex/utls v1.7.0-alpha.2/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU= github.com/metacubex/utls v1.7.3/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

@@ -113,12 +113,11 @@ func ApplyConfig(cfg *config.Config, force bool) {
tunnel.OnInnerLoading() tunnel.OnInnerLoading()
initInnerTcp() initInnerTcp()
loadProxyProvider(cfg.Providers) loadProvider(cfg.Providers)
updateProfile(cfg) updateProfile(cfg)
loadRuleProvider(cfg.RuleProviders) loadProvider(cfg.RuleProviders)
runtime.GC() runtime.GC()
tunnel.OnRunning() tunnel.OnRunning()
hcCompatibleProvider(cfg.Providers)
updateUpdater(cfg) updateUpdater(cfg)
resolver.ResetConnection() resolver.ResetConnection()
@@ -303,79 +302,40 @@ func updateRules(rules []C.Rule, subRules map[string][]C.Rule, ruleProviders map
tunnel.UpdateRules(rules, subRules, ruleProviders) tunnel.UpdateRules(rules, subRules, ruleProviders)
} }
func loadProvider(pv provider.Provider) { func loadProvider[P provider.Provider](providers map[string]P) {
if pv.VehicleType() == provider.Compatible { load := func(pv P) {
return name := pv.Name()
} else { if pv.VehicleType() == provider.Compatible {
log.Infoln("Start initial provider %s", (pv).Name()) log.Infoln("Start initial compatible provider %s", name)
} } else {
log.Infoln("Start initial provider %s", name)
if err := pv.Initial(); err != nil {
switch pv.Type() {
case provider.Proxy:
{
log.Errorln("initial proxy provider %s error: %v", (pv).Name(), err)
}
case provider.Rule:
{
log.Errorln("initial rule provider %s error: %v", (pv).Name(), err)
}
} }
}
}
func loadRuleProvider(ruleProviders map[string]provider.RuleProvider) { if err := pv.Initial(); err != nil {
wg := sync.WaitGroup{} switch pv.Type() {
ch := make(chan struct{}, concurrentCount) case provider.Proxy:
for _, ruleProvider := range ruleProviders { {
ruleProvider := ruleProvider log.Errorln("initial proxy provider %s error: %v", name, err)
wg.Add(1)
ch <- struct{}{}
go func() {
defer func() { <-ch; wg.Done() }()
loadProvider(ruleProvider)
}()
}
wg.Wait()
}
func loadProxyProvider(proxyProviders map[string]provider.ProxyProvider) {
// limit concurrent size
wg := sync.WaitGroup{}
ch := make(chan struct{}, concurrentCount)
for _, proxyProvider := range proxyProviders {
proxyProvider := proxyProvider
wg.Add(1)
ch <- struct{}{}
go func() {
defer func() { <-ch; wg.Done() }()
loadProvider(proxyProvider)
}()
}
wg.Wait()
}
func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) {
// limit concurrent size
wg := sync.WaitGroup{}
ch := make(chan struct{}, concurrentCount)
for _, proxyProvider := range proxyProviders {
proxyProvider := proxyProvider
if proxyProvider.VehicleType() == provider.Compatible {
log.Infoln("Start initial Compatible provider %s", proxyProvider.Name())
wg.Add(1)
ch <- struct{}{}
go func() {
defer func() { <-ch; wg.Done() }()
if err := proxyProvider.Initial(); err != nil {
log.Errorln("initial Compatible provider %s error: %v", proxyProvider.Name(), err)
} }
}() case provider.Rule:
{
log.Errorln("initial rule provider %s error: %v", name, err)
}
}
} }
} }
wg := sync.WaitGroup{}
ch := make(chan struct{}, concurrentCount)
for _, pv := range providers {
pv := pv
wg.Add(1)
ch <- struct{}{}
go func() {
defer func() { <-ch; wg.Done() }()
load(pv)
}()
}
wg.Wait() wg.Wait()
} }

View File

@@ -57,6 +57,7 @@ func applyRoute(cfg *config.Config) {
Secret: cfg.Controller.Secret, Secret: cfg.Controller.Secret,
Certificate: cfg.TLS.Certificate, Certificate: cfg.TLS.Certificate,
PrivateKey: cfg.TLS.PrivateKey, PrivateKey: cfg.TLS.PrivateKey,
EchKey: cfg.TLS.EchKey,
DohServer: cfg.Controller.ExternalDohServer, DohServer: cfg.Controller.ExternalDohServer,
IsDebug: cfg.General.LogLevel == log.DEBUG, IsDebug: cfg.General.LogLevel == log.DEBUG,
Cors: route.Cors{ Cors: route.Cors{

View File

@@ -371,12 +371,20 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
return return
} }
} else { } else {
if req.Path == "" { if req.Path == "" { // default path unneeded any safe check
req.Path = C.Path.Config() req.Path = C.Path.Config()
} else if !filepath.IsLocal(req.Path) { } else {
render.Status(r, http.StatusBadRequest) if !filepath.IsAbs(req.Path) {
render.JSON(w, r, newError("path is not a valid absolute path")) render.Status(r, http.StatusBadRequest)
return render.JSON(w, r, newError("path is not a absolute path"))
return
}
if !C.Path.IsSafePath(req.Path) {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError(C.Path.ErrNotSafePath(req.Path).Error()))
return
}
} }
cfg, err = executor.ParseWithPath(req.Path) cfg, err = executor.ParseWithPath(req.Path)

View File

@@ -3,7 +3,6 @@ package route
import ( import (
"bytes" "bytes"
"crypto/subtle" "crypto/subtle"
"crypto/tls"
"encoding/json" "encoding/json"
"net" "net"
"net/http" "net/http"
@@ -17,6 +16,8 @@ import (
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel/statistic" "github.com/metacubex/mihomo/tunnel/statistic"
@@ -62,6 +63,7 @@ type Config struct {
Secret string Secret string
Certificate string Certificate string
PrivateKey string PrivateKey string
EchKey string
DohServer string DohServer string
IsDebug bool IsDebug bool
Cors Cors Cors Cors
@@ -186,7 +188,7 @@ func startTLS(cfg *Config) {
// handle tlsAddr // handle tlsAddr
if len(cfg.TLSAddr) > 0 { if len(cfg.TLSAddr) > 0 {
c, err := ca.LoadTLSKeyPair(cfg.Certificate, cfg.PrivateKey, C.Path) cert, err := ca.LoadTLSKeyPair(cfg.Certificate, cfg.PrivateKey, C.Path)
if err != nil { if err != nil {
log.Errorln("External controller tls listen error: %s", err) log.Errorln("External controller tls listen error: %s", err)
return return
@@ -199,14 +201,22 @@ func startTLS(cfg *Config) {
} }
log.Infoln("RESTful API tls listening at: %s", l.Addr().String()) log.Infoln("RESTful API tls listening at: %s", l.Addr().String())
tlsConfig := &tlsC.Config{}
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
if cfg.EchKey != "" {
err = ech.LoadECHKey(cfg.EchKey, tlsConfig, C.Path)
if err != nil {
log.Errorln("External controller tls serve error: %s", err)
return
}
}
server := &http.Server{ server := &http.Server{
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors), Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors),
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{c},
},
} }
tlsServer = server tlsServer = server
if err = server.ServeTLS(l, "", ""); err != nil { if err = server.Serve(tlsC.NewListenerForHttps(l, server, tlsConfig)); err != nil {
log.Errorln("External controller tls serve error: %s", err) log.Errorln("External controller tls serve error: %s", err)
} }
} }

View File

@@ -3,7 +3,6 @@ package anytls
import ( import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"crypto/tls"
"encoding/binary" "encoding/binary"
"errors" "errors"
"net" "net"
@@ -13,6 +12,8 @@ import (
"github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/buf" "github.com/metacubex/mihomo/common/buf"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech"
tlsC "github.com/metacubex/mihomo/component/tls"
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"
"github.com/metacubex/mihomo/listener/sing" "github.com/metacubex/mihomo/listener/sing"
@@ -28,7 +29,7 @@ type Listener struct {
closed bool closed bool
config LC.AnyTLSServer config LC.AnyTLSServer
listeners []net.Listener listeners []net.Listener
tlsConfig *tls.Config tlsConfig *tlsC.Config
userMap map[[32]byte]string userMap map[[32]byte]string
padding atomic.TypedValue[*padding.PaddingFactory] padding atomic.TypedValue[*padding.PaddingFactory]
} }
@@ -41,13 +42,20 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
} }
} }
tlsConfig := &tls.Config{} tlsConfig := &tlsC.Config{}
if config.Certificate != "" && config.PrivateKey != "" { if config.Certificate != "" && config.PrivateKey != "" {
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.Certificates = []tls.Certificate{cert} tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
if err != nil {
return nil, err
}
}
} }
sl = &Listener{ sl = &Listener{
@@ -87,7 +95,7 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
return nil, err return nil, err
} }
if len(tlsConfig.Certificates) > 0 { if len(tlsConfig.Certificates) > 0 {
l = tls.NewListener(l, tlsConfig) l = tlsC.NewListener(l, tlsConfig)
} else { } else {
return nil, errors.New("disallow using AnyTLS without certificates config") return nil, errors.New("disallow using AnyTLS without certificates config")
} }

View File

@@ -10,6 +10,7 @@ type AnyTLSServer struct {
Users map[string]string `yaml:"users" json:"users,omitempty"` Users map[string]string `yaml:"users" json:"users,omitempty"`
Certificate string `yaml:"certificate" json:"certificate"` Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"` PrivateKey string `yaml:"private-key" json:"private-key"`
EchKey string `yaml:"ech-key" json:"ech-key"`
PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"` PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"`
} }

View File

@@ -12,5 +12,6 @@ type AuthServer struct {
AuthStore auth.AuthStore AuthStore auth.AuthStore
Certificate string Certificate string
PrivateKey string PrivateKey string
EchKey string
RealityConfig reality.Config RealityConfig reality.Config
} }

View File

@@ -14,6 +14,7 @@ type Hysteria2Server struct {
ObfsPassword string `yaml:"obfs-password" json:"obfs-password,omitempty"` ObfsPassword string `yaml:"obfs-password" json:"obfs-password,omitempty"`
Certificate string `yaml:"certificate" json:"certificate"` Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"` PrivateKey string `yaml:"private-key" json:"private-key"`
EchKey string `yaml:"ech-key" json:"ech-key,omitempty"`
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"` MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
ALPN []string `yaml:"alpn" json:"alpn,omitempty"` ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
Up string `yaml:"up" json:"up,omitempty"` Up string `yaml:"up" json:"up,omitempty"`

View File

@@ -20,6 +20,7 @@ type TrojanServer struct {
GrpcServiceName string GrpcServiceName string
Certificate string Certificate string
PrivateKey string PrivateKey string
EchKey string
RealityConfig reality.Config RealityConfig reality.Config
MuxOption sing.MuxOption MuxOption sing.MuxOption
TrojanSSOption TrojanSSOption TrojanSSOption TrojanSSOption

View File

@@ -13,6 +13,7 @@ type TuicServer struct {
Users map[string]string `yaml:"users" json:"users,omitempty"` Users map[string]string `yaml:"users" json:"users,omitempty"`
Certificate string `yaml:"certificate" json:"certificate"` Certificate string `yaml:"certificate" json:"certificate"`
PrivateKey string `yaml:"private-key" json:"private-key"` PrivateKey string `yaml:"private-key" json:"private-key"`
EchKey string `yaml:"ech-key" json:"ech-key"`
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"` CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"` MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"` AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`

View File

@@ -21,6 +21,7 @@ type VlessServer struct {
GrpcServiceName string GrpcServiceName string
Certificate string Certificate string
PrivateKey string PrivateKey string
EchKey string
RealityConfig reality.Config RealityConfig reality.Config
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
} }

View File

@@ -21,6 +21,7 @@ type VmessServer struct {
GrpcServiceName string GrpcServiceName string
Certificate string Certificate string
PrivateKey string PrivateKey string
EchKey string
RealityConfig reality.Config RealityConfig reality.Config
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
} }

View File

@@ -1,12 +1,13 @@
package http package http
import ( import (
"crypto/tls"
"errors" "errors"
"net" "net"
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth" authStore "github.com/metacubex/mihomo/listener/auth"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
@@ -64,7 +65,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
return nil, err return nil, err
} }
tlsConfig := &tls.Config{} tlsConfig := &tlsC.Config{}
var realityBuilder *reality.Builder var realityBuilder *reality.Builder
if config.Certificate != "" && config.PrivateKey != "" { if config.Certificate != "" && config.PrivateKey != "" {
@@ -72,7 +73,14 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.Certificates = []tls.Certificate{cert} tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
if err != nil {
return nil, err
}
}
} }
if config.RealityConfig.PrivateKey != "" { if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
@@ -87,7 +95,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
if realityBuilder != nil { if realityBuilder != nil {
l = realityBuilder.NewListener(l) l = realityBuilder.NewListener(l)
} else if len(tlsConfig.Certificates) > 0 { } else if len(tlsConfig.Certificates) > 0 {
l = tls.NewListener(l, tlsConfig) l = tlsC.NewListener(l, tlsConfig)
} }
hl := &Listener{ hl := &Listener{

View File

@@ -14,6 +14,7 @@ type AnyTLSOption struct {
Users map[string]string `inbound:"users,omitempty"` Users map[string]string `inbound:"users,omitempty"`
Certificate string `inbound:"certificate"` Certificate string `inbound:"certificate"`
PrivateKey string `inbound:"private-key"` PrivateKey string `inbound:"private-key"`
EchKey string `inbound:"ech-key,omitempty"`
PaddingScheme string `inbound:"padding-scheme,omitempty"` PaddingScheme string `inbound:"padding-scheme,omitempty"`
} }
@@ -42,6 +43,7 @@ func NewAnyTLS(options *AnyTLSOption) (*AnyTLS, error) {
Users: options.Users, Users: options.Users,
Certificate: options.Certificate, Certificate: options.Certificate,
PrivateKey: options.PrivateKey, PrivateKey: options.PrivateKey,
EchKey: options.EchKey,
PaddingScheme: options.PaddingScheme, PaddingScheme: options.PaddingScheme,
}, },
}, nil }, nil

View File

@@ -60,4 +60,14 @@ func TestInboundAnyTLS_TLS(t *testing.T) {
Fingerprint: tlsFingerprint, Fingerprint: tlsFingerprint,
} }
testInboundAnyTLS(t, inboundOptions, outboundOptions) testInboundAnyTLS(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundAnyTLS(t, inboundOptions, outboundOptions)
})
} }

View File

@@ -18,12 +18,15 @@ import (
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/ech"
"github.com/metacubex/mihomo/component/generater" "github.com/metacubex/mihomo/component/generater"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/render" "github.com/go-chi/render"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/net/http2"
) )
var httpPath = "/inbound_test" var httpPath = "/inbound_test"
@@ -38,6 +41,8 @@ var realityPrivateKey, realityPublickey string
var realityDest = "itunes.apple.com" var realityDest = "itunes.apple.com"
var realityShortid = "10f897e26c4b9478" var realityShortid = "10f897e26c4b9478"
var realityRealDial = false var realityRealDial = false
var echPublicSni = "public.sni"
var echConfigBase64, echKeyPem, _ = ech.GenECHConfig(echPublicSni)
func init() { func init() {
rand.Read(httpData) rand.Read(httpData)
@@ -131,7 +136,10 @@ func NewHttpTestTunnel() *TestTunnel {
r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) { r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) {
render.Data(w, r, httpData) render.Data(w, r, httpData)
}) })
go http.Serve(ln, r) h2Server := &http2.Server{}
server := http.Server{Handler: r}
_ = http2.ConfigureServer(&server, h2Server)
go server.Serve(ln)
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) { testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil) req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
@@ -205,23 +213,27 @@ func NewHttpTestTunnel() *TestTunnel {
ch: make(chan struct{}), ch: make(chan struct{}),
} }
if metadata.DstPort == 443 { if metadata.DstPort == 443 {
tlsConn := tls.Server(c, tlsConfig.Clone()) tlsConn := tlsC.Server(c, tlsC.UConfig(tlsConfig))
if metadata.Host == realityDest { // ignore the tls handshake error for realityDest if metadata.Host == realityDest { // ignore the tls handshake error for realityDest
if realityRealDial { if realityRealDial {
rconn, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress()) rconn, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress())
if err != nil { if err != nil {
panic(err) panic(err)
} }
N.Relay(rconn, tlsConn) N.Relay(rconn, conn)
return
}
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
defer cancel()
if err := tlsConn.HandshakeContext(ctx); err != nil {
return return
} }
} }
ln.ch <- tlsConn ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
defer cancel()
if err := tlsConn.HandshakeContext(ctx); err != nil {
return
}
if tlsConn.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS {
h2Server.ServeConn(tlsConn, &http2.ServeConnOpts{BaseConfig: &server})
} else {
ln.ch <- tlsConn
}
} else { } else {
ln.ch <- c ln.ch <- c
} }

View File

@@ -16,6 +16,7 @@ type HTTPOption struct {
Users AuthUsers `inbound:"users,omitempty"` Users AuthUsers `inbound:"users,omitempty"`
Certificate string `inbound:"certificate,omitempty"` Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"` PrivateKey string `inbound:"private-key,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"` RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
} }
@@ -64,6 +65,7 @@ func (h *HTTP) Listen(tunnel C.Tunnel) error {
AuthStore: h.config.Users.GetAuthStore(), AuthStore: h.config.Users.GetAuthStore(),
Certificate: h.config.Certificate, Certificate: h.config.Certificate,
PrivateKey: h.config.PrivateKey, PrivateKey: h.config.PrivateKey,
EchKey: h.config.EchKey,
RealityConfig: h.config.RealityConfig.Build(), RealityConfig: h.config.RealityConfig.Build(),
}, },
tunnel, tunnel,

View File

@@ -16,6 +16,7 @@ type Hysteria2Option struct {
ObfsPassword string `inbound:"obfs-password,omitempty"` ObfsPassword string `inbound:"obfs-password,omitempty"`
Certificate string `inbound:"certificate"` Certificate string `inbound:"certificate"`
PrivateKey string `inbound:"private-key"` PrivateKey string `inbound:"private-key"`
EchKey string `inbound:"ech-key,omitempty"`
MaxIdleTime int `inbound:"max-idle-time,omitempty"` MaxIdleTime int `inbound:"max-idle-time,omitempty"`
ALPN []string `inbound:"alpn,omitempty"` ALPN []string `inbound:"alpn,omitempty"`
Up string `inbound:"up,omitempty"` Up string `inbound:"up,omitempty"`
@@ -60,6 +61,7 @@ func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) {
ObfsPassword: options.ObfsPassword, ObfsPassword: options.ObfsPassword,
Certificate: options.Certificate, Certificate: options.Certificate,
PrivateKey: options.PrivateKey, PrivateKey: options.PrivateKey,
EchKey: options.EchKey,
MaxIdleTime: options.MaxIdleTime, MaxIdleTime: options.MaxIdleTime,
ALPN: options.ALPN, ALPN: options.ALPN,
Up: options.Up, Up: options.Up,

View File

@@ -60,6 +60,16 @@ func TestInboundHysteria2_TLS(t *testing.T) {
Fingerprint: tlsFingerprint, Fingerprint: tlsFingerprint,
} }
testInboundHysteria2(t, inboundOptions, outboundOptions) testInboundHysteria2(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundHysteria2(t, inboundOptions, outboundOptions)
})
} }
func TestInboundHysteria2_Salamander(t *testing.T) { func TestInboundHysteria2_Salamander(t *testing.T) {
@@ -75,6 +85,16 @@ func TestInboundHysteria2_Salamander(t *testing.T) {
ObfsPassword: userUUID, ObfsPassword: userUUID,
} }
testInboundHysteria2(t, inboundOptions, outboundOptions) testInboundHysteria2(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundHysteria2(t, inboundOptions, outboundOptions)
})
} }
func TestInboundHysteria2_Brutal(t *testing.T) { func TestInboundHysteria2_Brutal(t *testing.T) {
@@ -90,4 +110,14 @@ func TestInboundHysteria2_Brutal(t *testing.T) {
Down: "200 Mbps", Down: "200 Mbps",
} }
testInboundHysteria2(t, inboundOptions, outboundOptions) testInboundHysteria2(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundHysteria2(t, inboundOptions, outboundOptions)
})
} }

View File

@@ -18,6 +18,7 @@ type MixedOption struct {
UDP bool `inbound:"udp,omitempty"` UDP bool `inbound:"udp,omitempty"`
Certificate string `inbound:"certificate,omitempty"` Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"` PrivateKey string `inbound:"private-key,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"` RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
} }
@@ -69,6 +70,7 @@ func (m *Mixed) Listen(tunnel C.Tunnel) error {
AuthStore: m.config.Users.GetAuthStore(), AuthStore: m.config.Users.GetAuthStore(),
Certificate: m.config.Certificate, Certificate: m.config.Certificate,
PrivateKey: m.config.PrivateKey, PrivateKey: m.config.PrivateKey,
EchKey: m.config.EchKey,
RealityConfig: m.config.RealityConfig.Build(), RealityConfig: m.config.RealityConfig.Build(),
}, },
tunnel, tunnel,

View File

@@ -17,6 +17,7 @@ type SocksOption struct {
UDP bool `inbound:"udp,omitempty"` UDP bool `inbound:"udp,omitempty"`
Certificate string `inbound:"certificate,omitempty"` Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"` PrivateKey string `inbound:"private-key,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"` RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
} }
@@ -89,6 +90,7 @@ func (s *Socks) Listen(tunnel C.Tunnel) error {
AuthStore: s.config.Users.GetAuthStore(), AuthStore: s.config.Users.GetAuthStore(),
Certificate: s.config.Certificate, Certificate: s.config.Certificate,
PrivateKey: s.config.PrivateKey, PrivateKey: s.config.PrivateKey,
EchKey: s.config.EchKey,
RealityConfig: s.config.RealityConfig.Build(), RealityConfig: s.config.RealityConfig.Build(),
}, },
tunnel, tunnel,

View File

@@ -16,6 +16,7 @@ type TrojanOption struct {
GrpcServiceName string `inbound:"grpc-service-name,omitempty"` GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"` Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"` PrivateKey string `inbound:"private-key,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"` RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"` MuxOption MuxOption `inbound:"mux-option,omitempty"`
SSOption TrojanSSOption `inbound:"ss-option,omitempty"` SSOption TrojanSSOption `inbound:"ss-option,omitempty"`
@@ -67,6 +68,7 @@ func NewTrojan(options *TrojanOption) (*Trojan, error) {
GrpcServiceName: options.GrpcServiceName, GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate, Certificate: options.Certificate,
PrivateKey: options.PrivateKey, PrivateKey: options.PrivateKey,
EchKey: options.EchKey,
RealityConfig: options.RealityConfig.Build(), RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(), MuxOption: options.MuxOption.Build(),
TrojanSSOption: LC.TrojanSSOption{ TrojanSSOption: LC.TrojanSSOption{

View File

@@ -64,6 +64,16 @@ func TestInboundTrojan_TLS(t *testing.T) {
Fingerprint: tlsFingerprint, Fingerprint: tlsFingerprint,
} }
testInboundTrojan(t, inboundOptions, outboundOptions) testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
} }
func TestInboundTrojan_Wss1(t *testing.T) { func TestInboundTrojan_Wss1(t *testing.T) {
@@ -80,6 +90,16 @@ func TestInboundTrojan_Wss1(t *testing.T) {
}, },
} }
testInboundTrojan(t, inboundOptions, outboundOptions) testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
} }
func TestInboundTrojan_Wss2(t *testing.T) { func TestInboundTrojan_Wss2(t *testing.T) {
@@ -97,6 +117,16 @@ func TestInboundTrojan_Wss2(t *testing.T) {
}, },
} }
testInboundTrojan(t, inboundOptions, outboundOptions) testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
} }
func TestInboundTrojan_Grpc1(t *testing.T) { func TestInboundTrojan_Grpc1(t *testing.T) {
@@ -111,6 +141,16 @@ func TestInboundTrojan_Grpc1(t *testing.T) {
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
} }
testInboundTrojan(t, inboundOptions, outboundOptions) testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
} }
func TestInboundTrojan_Grpc2(t *testing.T) { func TestInboundTrojan_Grpc2(t *testing.T) {
@@ -126,6 +166,16 @@ func TestInboundTrojan_Grpc2(t *testing.T) {
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
} }
testInboundTrojan(t, inboundOptions, outboundOptions) testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
} }
func TestInboundTrojan_Reality(t *testing.T) { func TestInboundTrojan_Reality(t *testing.T) {
@@ -190,6 +240,16 @@ func TestInboundTrojan_TLS_TrojanSS(t *testing.T) {
}, },
} }
testInboundTrojan(t, inboundOptions, outboundOptions) testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
} }
func TestInboundTrojan_Wss_TrojanSS(t *testing.T) { func TestInboundTrojan_Wss_TrojanSS(t *testing.T) {
@@ -216,4 +276,14 @@ func TestInboundTrojan_Wss_TrojanSS(t *testing.T) {
}, },
} }
testInboundTrojan(t, inboundOptions, outboundOptions) testInboundTrojan(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTrojan(t, inboundOptions, outboundOptions)
})
} }

View File

@@ -15,6 +15,7 @@ type TuicOption struct {
Users map[string]string `inbound:"users,omitempty"` Users map[string]string `inbound:"users,omitempty"`
Certificate string `inbound:"certificate"` Certificate string `inbound:"certificate"`
PrivateKey string `inbound:"private-key"` PrivateKey string `inbound:"private-key"`
EchKey string `inbound:"ech-key,omitempty"`
CongestionController string `inbound:"congestion-controller,omitempty"` CongestionController string `inbound:"congestion-controller,omitempty"`
MaxIdleTime int `inbound:"max-idle-time,omitempty"` MaxIdleTime int `inbound:"max-idle-time,omitempty"`
AuthenticationTimeout int `inbound:"authentication-timeout,omitempty"` AuthenticationTimeout int `inbound:"authentication-timeout,omitempty"`
@@ -50,6 +51,7 @@ func NewTuic(options *TuicOption) (*Tuic, error) {
Users: options.Users, Users: options.Users,
Certificate: options.Certificate, Certificate: options.Certificate,
PrivateKey: options.PrivateKey, PrivateKey: options.PrivateKey,
EchKey: options.EchKey,
CongestionController: options.CongestionController, CongestionController: options.CongestionController,
MaxIdleTime: options.MaxIdleTime, MaxIdleTime: options.MaxIdleTime,
AuthenticationTimeout: options.AuthenticationTimeout, AuthenticationTimeout: options.AuthenticationTimeout,

View File

@@ -89,4 +89,14 @@ func TestInboundTuic_TLS(t *testing.T) {
Fingerprint: tlsFingerprint, Fingerprint: tlsFingerprint,
} }
testInboundTuic(t, inboundOptions, outboundOptions) testInboundTuic(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundTuic(t, inboundOptions, outboundOptions)
})
} }

View File

@@ -16,6 +16,7 @@ type VlessOption struct {
GrpcServiceName string `inbound:"grpc-service-name,omitempty"` GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"` Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"` PrivateKey string `inbound:"private-key,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"` RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"` MuxOption MuxOption `inbound:"mux-option,omitempty"`
} }
@@ -61,6 +62,7 @@ func NewVless(options *VlessOption) (*Vless, error) {
GrpcServiceName: options.GrpcServiceName, GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate, Certificate: options.Certificate,
PrivateKey: options.PrivateKey, PrivateKey: options.PrivateKey,
EchKey: options.EchKey,
RealityConfig: options.RealityConfig.Build(), RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(), MuxOption: options.MuxOption.Build(),
}, },

View File

@@ -66,9 +66,25 @@ func TestInboundVless_TLS(t *testing.T) {
} }
testInboundVless(t, inboundOptions, outboundOptions) testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) { t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision" outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions) testInboundVless(t, inboundOptions, outboundOptions)
}) })
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
})
} }
func TestInboundVless_Wss1(t *testing.T) { func TestInboundVless_Wss1(t *testing.T) {
@@ -87,9 +103,25 @@ func TestInboundVless_Wss1(t *testing.T) {
} }
testInboundVless(t, inboundOptions, outboundOptions) testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) { t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision" outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions) testInboundVless(t, inboundOptions, outboundOptions)
}) })
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
})
} }
func TestInboundVless_Wss2(t *testing.T) { func TestInboundVless_Wss2(t *testing.T) {
@@ -109,9 +141,25 @@ func TestInboundVless_Wss2(t *testing.T) {
} }
testInboundVless(t, inboundOptions, outboundOptions) testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) { t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision" outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions) testInboundVless(t, inboundOptions, outboundOptions)
}) })
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
})
} }
func TestInboundVless_Grpc1(t *testing.T) { func TestInboundVless_Grpc1(t *testing.T) {
@@ -127,6 +175,16 @@ func TestInboundVless_Grpc1(t *testing.T) {
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
} }
testInboundVless(t, inboundOptions, outboundOptions) testInboundVless(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
})
} }
func TestInboundVless_Grpc2(t *testing.T) { func TestInboundVless_Grpc2(t *testing.T) {
@@ -143,6 +201,16 @@ func TestInboundVless_Grpc2(t *testing.T) {
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
} }
testInboundVless(t, inboundOptions, outboundOptions) testInboundVless(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVless(t, inboundOptions, outboundOptions)
})
} }
func TestInboundVless_Reality(t *testing.T) { func TestInboundVless_Reality(t *testing.T) {
@@ -165,9 +233,20 @@ func TestInboundVless_Reality(t *testing.T) {
} }
testInboundVless(t, inboundOptions, outboundOptions) testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) { t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision" outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions) testInboundVless(t, inboundOptions, outboundOptions)
}) })
t.Run("X25519MLKEM768", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.RealityOpts.SupportX25519MLKEM768 = true
testInboundVless(t, inboundOptions, outboundOptions)
t.Run("xtls-rprx-vision", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.Flow = "xtls-rprx-vision"
testInboundVless(t, inboundOptions, outboundOptions)
})
})
} }
func TestInboundVless_Reality_Grpc(t *testing.T) { func TestInboundVless_Reality_Grpc(t *testing.T) {
@@ -192,4 +271,9 @@ func TestInboundVless_Reality_Grpc(t *testing.T) {
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
} }
testInboundVless(t, inboundOptions, outboundOptions) testInboundVless(t, inboundOptions, outboundOptions)
t.Run("X25519MLKEM768", func(t *testing.T) {
outboundOptions := outboundOptions
outboundOptions.RealityOpts.SupportX25519MLKEM768 = true
testInboundVless(t, inboundOptions, outboundOptions)
})
} }

View File

@@ -16,6 +16,7 @@ type VmessOption struct {
GrpcServiceName string `inbound:"grpc-service-name,omitempty"` GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"` Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"` PrivateKey string `inbound:"private-key,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"` RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"` MuxOption MuxOption `inbound:"mux-option,omitempty"`
} }
@@ -61,6 +62,7 @@ func NewVmess(options *VmessOption) (*Vmess, error) {
GrpcServiceName: options.GrpcServiceName, GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate, Certificate: options.Certificate,
PrivateKey: options.PrivateKey, PrivateKey: options.PrivateKey,
EchKey: options.EchKey,
RealityConfig: options.RealityConfig.Build(), RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(), MuxOption: options.MuxOption.Build(),
}, },

View File

@@ -73,6 +73,16 @@ func TestInboundVMess_TLS(t *testing.T) {
Fingerprint: tlsFingerprint, Fingerprint: tlsFingerprint,
} }
testInboundVMess(t, inboundOptions, outboundOptions) testInboundVMess(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVMess(t, inboundOptions, outboundOptions)
})
} }
func TestInboundVMess_Ws(t *testing.T) { func TestInboundVMess_Ws(t *testing.T) {
@@ -160,6 +170,16 @@ func TestInboundVMess_Wss1(t *testing.T) {
}, },
} }
testInboundVMess(t, inboundOptions, outboundOptions) testInboundVMess(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVMess(t, inboundOptions, outboundOptions)
})
} }
func TestInboundVMess_Wss2(t *testing.T) { func TestInboundVMess_Wss2(t *testing.T) {
@@ -178,6 +198,16 @@ func TestInboundVMess_Wss2(t *testing.T) {
}, },
} }
testInboundVMess(t, inboundOptions, outboundOptions) testInboundVMess(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVMess(t, inboundOptions, outboundOptions)
})
} }
func TestInboundVMess_Grpc1(t *testing.T) { func TestInboundVMess_Grpc1(t *testing.T) {
@@ -193,6 +223,16 @@ func TestInboundVMess_Grpc1(t *testing.T) {
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
} }
testInboundVMess(t, inboundOptions, outboundOptions) testInboundVMess(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVMess(t, inboundOptions, outboundOptions)
})
} }
func TestInboundVMess_Grpc2(t *testing.T) { func TestInboundVMess_Grpc2(t *testing.T) {
@@ -209,6 +249,16 @@ func TestInboundVMess_Grpc2(t *testing.T) {
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
} }
testInboundVMess(t, inboundOptions, outboundOptions) testInboundVMess(t, inboundOptions, outboundOptions)
t.Run("ECH", func(t *testing.T) {
inboundOptions := inboundOptions
outboundOptions := outboundOptions
inboundOptions.EchKey = echKeyPem
outboundOptions.ECHOpts = outbound.ECHOptions{
Enable: true,
Config: echConfigBase64,
}
testInboundVMess(t, inboundOptions, outboundOptions)
})
} }
func TestInboundVMess_Reality(t *testing.T) { func TestInboundVMess_Reality(t *testing.T) {

View File

@@ -1,7 +1,6 @@
package mixed package mixed
import ( import (
"crypto/tls"
"errors" "errors"
"net" "net"
@@ -9,6 +8,8 @@ import (
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/auth" "github.com/metacubex/mihomo/component/auth"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth" authStore "github.com/metacubex/mihomo/listener/auth"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
@@ -60,7 +61,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
return nil, err return nil, err
} }
tlsConfig := &tls.Config{} tlsConfig := &tlsC.Config{}
var realityBuilder *reality.Builder var realityBuilder *reality.Builder
if config.Certificate != "" && config.PrivateKey != "" { if config.Certificate != "" && config.PrivateKey != "" {
@@ -68,7 +69,14 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.Certificates = []tls.Certificate{cert} tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
if err != nil {
return nil, err
}
}
} }
if config.RealityConfig.PrivateKey != "" { if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
@@ -83,7 +91,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
if realityBuilder != nil { if realityBuilder != nil {
l = realityBuilder.NewListener(l) l = realityBuilder.NewListener(l)
} else if len(tlsConfig.Certificates) > 0 { } else if len(tlsConfig.Certificates) > 0 {
l = tls.NewListener(l, tlsConfig) l = tlsC.NewListener(l, tlsConfig)
} }
ml := &Listener{ ml := &Listener{

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ package sing_hysteria2
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@@ -15,6 +14,7 @@ import (
"github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/common/sockopt" "github.com/metacubex/mihomo/common/sockopt"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech"
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"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
@@ -60,9 +60,16 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig := &tls.Config{ tlsConfig := &tlsC.Config{
MinVersion: tls.VersionTLS13, MinVersion: tlsC.VersionTLS13,
Certificates: []tls.Certificate{cert}, }
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
if err != nil {
return nil, err
}
} }
if len(config.ALPN) > 0 { if len(config.ALPN) > 0 {
tlsConfig.NextProtos = config.ALPN tlsConfig.NextProtos = config.ALPN
@@ -125,7 +132,7 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
SendBPS: outbound.StringToBps(config.Up), SendBPS: outbound.StringToBps(config.Up),
ReceiveBPS: outbound.StringToBps(config.Down), ReceiveBPS: outbound.StringToBps(config.Down),
SalamanderPassword: salamanderPassword, SalamanderPassword: salamanderPassword,
TLSConfig: tlsC.UConfig(tlsConfig), TLSConfig: tlsConfig,
QUICConfig: quicConfig, QUICConfig: quicConfig,
IgnoreClientBandwidth: config.IgnoreClientBandwidth, IgnoreClientBandwidth: config.IgnoreClientBandwidth,
UDPTimeout: sing.UDPTimeout, UDPTimeout: sing.UDPTimeout,

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

@@ -2,7 +2,6 @@ package sing_vless
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"net" "net"
"net/http" "net/http"
@@ -12,6 +11,7 @@ import (
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech"
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"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
@@ -82,16 +82,23 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
sl = &Listener{false, config, nil, service} sl = &Listener{false, config, nil, service}
tlsConfig := &tls.Config{} tlsConfig := &tlsC.Config{}
var realityBuilder *reality.Builder var realityBuilder *reality.Builder
var httpHandler http.Handler var httpServer http.Server
if config.Certificate != "" && config.PrivateKey != "" { if config.Certificate != "" && config.PrivateKey != "" {
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.Certificates = []tls.Certificate{cert} tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
if err != nil {
return nil, err
}
}
} }
if config.RealityConfig.PrivateKey != "" { if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
@@ -112,16 +119,16 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
} }
sl.HandleConn(conn, tunnel, additions...) sl.HandleConn(conn, tunnel, additions...)
}) })
httpHandler = httpMux httpServer.Handler = httpMux
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1") tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1")
} }
if config.GrpcServiceName != "" { if config.GrpcServiceName != "" {
httpHandler = gun.NewServerHandler(gun.ServerOption{ httpServer.Handler = gun.NewServerHandler(gun.ServerOption{
ServiceName: config.GrpcServiceName, ServiceName: config.GrpcServiceName,
ConnHandler: func(conn net.Conn) { ConnHandler: func(conn net.Conn) {
sl.HandleConn(conn, tunnel, additions...) sl.HandleConn(conn, tunnel, additions...)
}, },
HttpHandler: httpHandler, HttpHandler: httpServer.Handler,
}) })
tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1 tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1
} }
@@ -137,15 +144,19 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
if realityBuilder != nil { if realityBuilder != nil {
l = realityBuilder.NewListener(l) l = realityBuilder.NewListener(l)
} else if len(tlsConfig.Certificates) > 0 { } else if len(tlsConfig.Certificates) > 0 {
l = tls.NewListener(l, tlsConfig) if httpServer.Handler != nil {
l = tlsC.NewListenerForHttps(l, &httpServer, tlsConfig)
} else {
l = tlsC.NewListener(l, tlsConfig)
}
} else { } else {
return nil, errors.New("disallow using Vless without both certificates/reality config") return nil, errors.New("disallow using Vless without both certificates/reality config")
} }
sl.listeners = append(sl.listeners, l) sl.listeners = append(sl.listeners, l)
go func() { go func() {
if httpHandler != nil { if httpServer.Handler != nil {
_ = http.Serve(l, httpHandler) _ = httpServer.Serve(l)
return return
} }
for { for {

View File

@@ -2,7 +2,6 @@ package sing_vmess
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"net" "net"
"net/http" "net/http"
@@ -11,6 +10,8 @@ import (
"github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech"
tlsC "github.com/metacubex/mihomo/component/tls"
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"
"github.com/metacubex/mihomo/listener/reality" "github.com/metacubex/mihomo/listener/reality"
@@ -75,16 +76,23 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
sl = &Listener{false, config, nil, service} sl = &Listener{false, config, nil, service}
tlsConfig := &tls.Config{} tlsConfig := &tlsC.Config{}
var realityBuilder *reality.Builder var realityBuilder *reality.Builder
var httpHandler http.Handler var httpServer http.Server
if config.Certificate != "" && config.PrivateKey != "" { if config.Certificate != "" && config.PrivateKey != "" {
cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.Certificates = []tls.Certificate{cert} tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
if err != nil {
return nil, err
}
}
} }
if config.RealityConfig.PrivateKey != "" { if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
@@ -105,16 +113,16 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
} }
sl.HandleConn(conn, tunnel, additions...) sl.HandleConn(conn, tunnel, additions...)
}) })
httpHandler = httpMux httpServer.Handler = httpMux
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1") tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1")
} }
if config.GrpcServiceName != "" { if config.GrpcServiceName != "" {
httpHandler = gun.NewServerHandler(gun.ServerOption{ httpServer.Handler = gun.NewServerHandler(gun.ServerOption{
ServiceName: config.GrpcServiceName, ServiceName: config.GrpcServiceName,
ConnHandler: func(conn net.Conn) { ConnHandler: func(conn net.Conn) {
sl.HandleConn(conn, tunnel, additions...) sl.HandleConn(conn, tunnel, additions...)
}, },
HttpHandler: httpHandler, HttpHandler: httpServer.Handler,
}) })
tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1 tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1
} }
@@ -130,13 +138,17 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
if realityBuilder != nil { if realityBuilder != nil {
l = realityBuilder.NewListener(l) l = realityBuilder.NewListener(l)
} else if len(tlsConfig.Certificates) > 0 { } else if len(tlsConfig.Certificates) > 0 {
l = tls.NewListener(l, tlsConfig) if httpServer.Handler != nil {
l = tlsC.NewListenerForHttps(l, &httpServer, tlsConfig)
} else {
l = tlsC.NewListener(l, tlsConfig)
}
} }
sl.listeners = append(sl.listeners, l) sl.listeners = append(sl.listeners, l)
go func() { go func() {
if httpHandler != nil { if httpServer.Handler != nil {
_ = http.Serve(l, httpHandler) _ = httpServer.Serve(l)
return return
} }
for { for {

View File

@@ -1,7 +1,6 @@
package socks package socks
import ( import (
"crypto/tls"
"errors" "errors"
"io" "io"
"net" "net"
@@ -10,6 +9,8 @@ import (
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/auth" "github.com/metacubex/mihomo/component/auth"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/ech"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth" authStore "github.com/metacubex/mihomo/listener/auth"
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
@@ -59,7 +60,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
return nil, err return nil, err
} }
tlsConfig := &tls.Config{} tlsConfig := &tlsC.Config{}
var realityBuilder *reality.Builder var realityBuilder *reality.Builder
if config.Certificate != "" && config.PrivateKey != "" { if config.Certificate != "" && config.PrivateKey != "" {
@@ -67,7 +68,14 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig.Certificates = []tls.Certificate{cert} tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
if config.EchKey != "" {
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
if err != nil {
return nil, err
}
}
} }
if config.RealityConfig.PrivateKey != "" { if config.RealityConfig.PrivateKey != "" {
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
@@ -82,7 +90,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
if realityBuilder != nil { if realityBuilder != nil {
l = realityBuilder.NewListener(l) l = realityBuilder.NewListener(l)
} else if len(tlsConfig.Certificates) > 0 { } else if len(tlsConfig.Certificates) > 0 {
l = tls.NewListener(l, tlsConfig) l = tlsC.NewListener(l, tlsConfig)
} }
sl := &Listener{ sl := &Listener{

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