mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-03-03 20:27:31 +00:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0f452b540 | ||
|
|
6c9abe16cc | ||
|
|
213d80c1e2 | ||
|
|
1db89da122 | ||
|
|
689c58f661 | ||
|
|
33590c4066 | ||
|
|
60ae9dce56 | ||
|
|
4741ac6702 | ||
|
|
ef3d7e4dd7 | ||
|
|
a1c7881229 | ||
|
|
12e3952b74 | ||
|
|
88419cbd12 | ||
|
|
4ed830330e | ||
|
|
3ed6ff9402 | ||
|
|
34de62d21d | ||
|
|
d2e255f257 | ||
|
|
a0c46bb4b7 | ||
|
|
9e3bf14b1a | ||
|
|
28c387a9b6 | ||
|
|
15eda703b4 | ||
|
|
b1d12a15db | ||
|
|
5a21bf3642 | ||
|
|
199fb8fd5d | ||
|
|
fd959feff2 | ||
|
|
d5a03901d2 | ||
|
|
257fead538 | ||
|
|
c489c5260b | ||
|
|
8f92b1de13 | ||
|
|
9f7a2a36c1 | ||
|
|
a93479124c | ||
|
|
ed42c4feb8 | ||
|
|
608ddb1b44 | ||
|
|
d036d98128 | ||
|
|
d900c71214 | ||
|
|
1672750c47 | ||
|
|
41b57afb3f | ||
|
|
188372cb04 | ||
|
|
a1350d4985 | ||
|
|
dc958e6a39 | ||
|
|
8a5f3b8909 | ||
|
|
c6d7ef8cb8 | ||
|
|
bb8c47d83d | ||
|
|
5cf0f18c29 | ||
|
|
83213d493e | ||
|
|
90ed01ed53 | ||
|
|
f91a586da8 | ||
|
|
266fb03838 | ||
|
|
76e9607fd7 | ||
|
|
23e2d3a132 | ||
|
|
6e35cf9399 | ||
|
|
2116640886 | ||
|
|
a4fcd3af07 | ||
|
|
d22a893060 | ||
|
|
00cceba890 |
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
36
adapter/outbound/ech.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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
31
component/ech/ech.go
Normal 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
143
component/ech/key.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
70
component/tls/httpserver.go
Normal file
70
component/tls/httpserver.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -261,6 +261,11 @@ func (m *Metadata) Pure() *Metadata {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) Clone() *Metadata {
|
||||||
|
copyM := *m
|
||||||
|
return ©M
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
114
docs/config.yaml
114
docs/config.yaml
@@ -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
22
go.mod
@@ -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
44
go.sum
@@ -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=
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user