mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-03-03 12:17:31 +00:00
Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b975275f5 | ||
|
|
166392fe17 | ||
|
|
5c6aa433ca | ||
|
|
2c55dc2557 | ||
|
|
56c0b088e8 | ||
|
|
5344e869a8 | ||
|
|
6cfaf15cbf | ||
|
|
31f0060b30 | ||
|
|
c60750d549 | ||
|
|
ebf5918e94 | ||
|
|
93ca18517c | ||
|
|
32d447ce99 | ||
|
|
617fef84ae | ||
|
|
d19199322d | ||
|
|
87795e3a07 | ||
|
|
85bb40aaf8 | ||
|
|
082bcec281 | ||
|
|
9283cb0f5f | ||
|
|
ae7967f662 | ||
|
|
01f8f2db2f | ||
|
|
255ff5e977 | ||
|
|
939e4109d7 | ||
|
|
40587b62b8 | ||
|
|
85e6d25de5 | ||
|
|
29a37f4f4b | ||
|
|
2f9a3b3469 | ||
|
|
40ea0ba098 | ||
|
|
8d7f947a80 | ||
|
|
71a8705636 | ||
|
|
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 |
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@@ -198,10 +198,11 @@ jobs:
|
|||||||
- name: Set variables
|
- name: Set variables
|
||||||
run: |
|
run: |
|
||||||
VERSION="${GITHUB_REF_NAME,,}-$(git rev-parse --short HEAD)"
|
VERSION="${GITHUB_REF_NAME,,}-$(git rev-parse --short HEAD)"
|
||||||
|
VERSION="${VERSION//\//-}"
|
||||||
PackageVersion="$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | jq -r '.tag_name' | sed 's/v//g' | awk -F '.' '{$NF = $NF + 1; print}' OFS='.').${VERSION/-/.}"
|
PackageVersion="$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | jq -r '.tag_name' | sed 's/v//g' | awk -F '.' '{$NF = $NF + 1; print}' OFS='.').${VERSION/-/.}"
|
||||||
if [ -n "${{ github.event.inputs.version }}" ]; then
|
if [ -n "${{ github.event.inputs.version }}" ]; then
|
||||||
VERSION=${{ github.event.inputs.version }}
|
VERSION=${{ github.event.inputs.version }}
|
||||||
PackageVersion="${VERSION#v}" >> $GITHUB_ENV
|
PackageVersion="${VERSION#v}"
|
||||||
fi
|
fi
|
||||||
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
||||||
echo "PackageVersion=${PackageVersion}" >> $GITHUB_ENV
|
echo "PackageVersion=${PackageVersion}" >> $GITHUB_ENV
|
||||||
|
|||||||
3
.github/workflows/trigger-cmfa-update.yml
vendored
3
.github/workflows/trigger-cmfa-update.yml
vendored
@@ -10,9 +10,6 @@ on:
|
|||||||
- Alpha
|
- Alpha
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
pull_request_target:
|
|
||||||
branches:
|
|
||||||
- Alpha
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies
|
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
63
common/atomic/enum.go
Normal file
63
common/atomic/enum.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Int32Enum[T ~int32] struct {
|
||||||
|
value atomic.Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Int32Enum[T]) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(i.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Int32Enum[T]) UnmarshalJSON(b []byte) error {
|
||||||
|
var v T
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Int32Enum[T]) MarshalYAML() (any, error) {
|
||||||
|
return i.Load(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Int32Enum[T]) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var v T
|
||||||
|
if err := unmarshal(&v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Int32Enum[T]) String() string {
|
||||||
|
return fmt.Sprint(i.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Int32Enum[T]) Store(v T) {
|
||||||
|
i.value.Store(int32(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Int32Enum[T]) Load() T {
|
||||||
|
return T(i.value.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Int32Enum[T]) Swap(new T) T {
|
||||||
|
return T(i.value.Swap(int32(new)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Int32Enum[T]) CompareAndSwap(old, new T) bool {
|
||||||
|
return i.value.CompareAndSwap(int32(old), int32(new))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInt32Enum[T ~int32](v T) *Int32Enum[T] {
|
||||||
|
a := &Int32Enum[T]{}
|
||||||
|
a.Store(v)
|
||||||
|
return a
|
||||||
|
}
|
||||||
@@ -29,6 +29,19 @@ func (i *Bool) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Bool) MarshalYAML() (any, error) {
|
||||||
|
return i.Load(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Bool) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var v bool
|
||||||
|
if err := unmarshal(&v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Bool) String() string {
|
func (i *Bool) String() string {
|
||||||
v := i.Load()
|
v := i.Load()
|
||||||
return strconv.FormatBool(v)
|
return strconv.FormatBool(v)
|
||||||
@@ -58,6 +71,19 @@ func (p *Pointer[T]) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Pointer[T]) MarshalYAML() (any, error) {
|
||||||
|
return p.Load(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pointer[T]) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var v *T
|
||||||
|
if err := unmarshal(&v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Pointer[T]) String() string {
|
func (p *Pointer[T]) String() string {
|
||||||
return fmt.Sprint(p.Load())
|
return fmt.Sprint(p.Load())
|
||||||
}
|
}
|
||||||
@@ -84,6 +110,19 @@ func (i *Int32) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Int32) MarshalYAML() (any, error) {
|
||||||
|
return i.Load(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Int32) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var v int32
|
||||||
|
if err := unmarshal(&v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Int32) String() string {
|
func (i *Int32) String() string {
|
||||||
v := i.Load()
|
v := i.Load()
|
||||||
return strconv.FormatInt(int64(v), 10)
|
return strconv.FormatInt(int64(v), 10)
|
||||||
@@ -111,6 +150,19 @@ func (i *Int64) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Int64) MarshalYAML() (any, error) {
|
||||||
|
return i.Load(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Int64) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var v int64
|
||||||
|
if err := unmarshal(&v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Int64) String() string {
|
func (i *Int64) String() string {
|
||||||
v := i.Load()
|
v := i.Load()
|
||||||
return strconv.FormatInt(int64(v), 10)
|
return strconv.FormatInt(int64(v), 10)
|
||||||
@@ -138,6 +190,19 @@ func (i *Uint32) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Uint32) MarshalYAML() (any, error) {
|
||||||
|
return i.Load(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Uint32) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var v uint32
|
||||||
|
if err := unmarshal(&v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Uint32) String() string {
|
func (i *Uint32) String() string {
|
||||||
v := i.Load()
|
v := i.Load()
|
||||||
return strconv.FormatUint(uint64(v), 10)
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
@@ -165,6 +230,19 @@ func (i *Uint64) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Uint64) MarshalYAML() (any, error) {
|
||||||
|
return i.Load(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Uint64) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var v uint64
|
||||||
|
if err := unmarshal(&v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Uint64) String() string {
|
func (i *Uint64) String() string {
|
||||||
v := i.Load()
|
v := i.Load()
|
||||||
return strconv.FormatUint(uint64(v), 10)
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
@@ -192,6 +270,19 @@ func (i *Uintptr) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Uintptr) MarshalYAML() (any, error) {
|
||||||
|
return i.Load(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Uintptr) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var v uintptr
|
||||||
|
if err := unmarshal(&v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Uintptr) String() string {
|
func (i *Uintptr) String() string {
|
||||||
v := i.Load()
|
v := i.Load()
|
||||||
return strconv.FormatUint(uint64(v), 10)
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
|
|||||||
@@ -27,11 +27,16 @@ type tValue[T any] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TypedValue[T]) Load() T {
|
func (t *TypedValue[T]) Load() T {
|
||||||
|
value, _ := t.LoadOk()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypedValue[T]) LoadOk() (_ T, ok bool) {
|
||||||
value := t.value.Load()
|
value := t.value.Load()
|
||||||
if value == nil {
|
if value == nil {
|
||||||
return DefaultValue[T]()
|
return DefaultValue[T](), false
|
||||||
}
|
}
|
||||||
return value.(tValue[T]).value
|
return value.(tValue[T]).value, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TypedValue[T]) Store(value T) {
|
func (t *TypedValue[T]) Store(value T) {
|
||||||
@@ -47,7 +52,11 @@ func (t *TypedValue[T]) Swap(new T) T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
|
func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
|
||||||
return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{new})
|
return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{new}) ||
|
||||||
|
// In the edge-case where [atomic.Value.Store] is uninitialized
|
||||||
|
// and trying to compare with the zero value of T,
|
||||||
|
// then compare-and-swap with the nil any value.
|
||||||
|
(any(old) == any(DefaultValue[T]()) && t.value.CompareAndSwap(any(nil), tValue[T]{new}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TypedValue[T]) MarshalJSON() ([]byte, error) {
|
func (t *TypedValue[T]) MarshalJSON() ([]byte, error) {
|
||||||
@@ -63,6 +72,19 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TypedValue[T]) MarshalYAML() (any, error) {
|
||||||
|
return t.Load(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TypedValue[T]) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var v T
|
||||||
|
if err := unmarshal(&v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewTypedValue[T any](t T) (v TypedValue[T]) {
|
func NewTypedValue[T any](t T) (v TypedValue[T]) {
|
||||||
v.Store(t)
|
v.Store(t)
|
||||||
return
|
return
|
||||||
|
|||||||
77
common/atomic/value_test.go
Normal file
77
common/atomic/value_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTypedValue(t *testing.T) {
|
||||||
|
{
|
||||||
|
// Always wrapping should not allocate for simple values
|
||||||
|
// because tValue[T] has the same memory layout as T.
|
||||||
|
var v TypedValue[bool]
|
||||||
|
bools := []bool{true, false}
|
||||||
|
if n := int(testing.AllocsPerRun(1000, func() {
|
||||||
|
for _, b := range bools {
|
||||||
|
v.Store(b)
|
||||||
|
}
|
||||||
|
})); n != 0 {
|
||||||
|
t.Errorf("AllocsPerRun = %d, want 0", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var v TypedValue[int]
|
||||||
|
got, gotOk := v.LoadOk()
|
||||||
|
if got != 0 || gotOk {
|
||||||
|
t.Fatalf("LoadOk = (%v, %v), want (0, false)", got, gotOk)
|
||||||
|
}
|
||||||
|
v.Store(1)
|
||||||
|
got, gotOk = v.LoadOk()
|
||||||
|
if got != 1 || !gotOk {
|
||||||
|
t.Fatalf("LoadOk = (%v, %v), want (1, true)", got, gotOk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var v TypedValue[error]
|
||||||
|
got, gotOk := v.LoadOk()
|
||||||
|
if got != nil || gotOk {
|
||||||
|
t.Fatalf("LoadOk = (%v, %v), want (nil, false)", got, gotOk)
|
||||||
|
}
|
||||||
|
v.Store(io.EOF)
|
||||||
|
got, gotOk = v.LoadOk()
|
||||||
|
if got != io.EOF || !gotOk {
|
||||||
|
t.Fatalf("LoadOk = (%v, %v), want (EOF, true)", got, gotOk)
|
||||||
|
}
|
||||||
|
err := &os.PathError{}
|
||||||
|
v.Store(err)
|
||||||
|
got, gotOk = v.LoadOk()
|
||||||
|
if got != err || !gotOk {
|
||||||
|
t.Fatalf("LoadOk = (%v, %v), want (%v, true)", got, gotOk, err)
|
||||||
|
}
|
||||||
|
v.Store(nil)
|
||||||
|
got, gotOk = v.LoadOk()
|
||||||
|
if got != nil || !gotOk {
|
||||||
|
t.Fatalf("LoadOk = (%v, %v), want (nil, true)", got, gotOk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
c1, c2, c3 := make(chan struct{}), make(chan struct{}), make(chan struct{})
|
||||||
|
var v TypedValue[chan struct{}]
|
||||||
|
if v.CompareAndSwap(c1, c2) != false {
|
||||||
|
t.Fatalf("CompareAndSwap = true, want false")
|
||||||
|
}
|
||||||
|
if v.CompareAndSwap(nil, c1) != true {
|
||||||
|
t.Fatalf("CompareAndSwap = false, want true")
|
||||||
|
}
|
||||||
|
if v.CompareAndSwap(c2, c3) != false {
|
||||||
|
t.Fatalf("CompareAndSwap = true, want false")
|
||||||
|
}
|
||||||
|
if v.CompareAndSwap(c1, c2) != true {
|
||||||
|
t.Fatalf("CompareAndSwap = false, want true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package convert
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,3 +44,22 @@ func decodeUrlSafe(data string) string {
|
|||||||
}
|
}
|
||||||
return string(dcBuf)
|
return string(dcBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TryDecodeBase64(s string) (decoded []byte, err error) {
|
||||||
|
if len(s)%4 == 0 {
|
||||||
|
if decoded, err = base64.StdEncoding.DecodeString(s); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if decoded, err = base64.URLEncoding.DecodeString(s); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if decoded, err = base64.RawStdEncoding.DecodeString(s); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if decoded, err = base64.RawURLEncoding.DecodeString(s); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("invalid base64-encoded string")
|
||||||
|
}
|
||||||
|
|||||||
@@ -456,12 +456,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
proxies = append(proxies, ss)
|
proxies = append(proxies, ss)
|
||||||
|
|
||||||
case "ssr":
|
case "ssr":
|
||||||
dcBuf, err := encRaw.DecodeString(body)
|
dcBuf, err := TryDecodeBase64(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64&protoparam=&remarks=urlsafebase64&group=urlsafebase64&udpport=0&uot=1
|
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64param&protoparam=urlsafebase64param&remarks=urlsafebase64remarks&group=urlsafebase64group&udpport=0&uot=1
|
||||||
|
|
||||||
before, after, ok := strings.Cut(string(dcBuf), "/?")
|
before, after, ok := strings.Cut(string(dcBuf), "/?")
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -490,7 +490,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
name := uniqueName(names, remarks)
|
name := uniqueName(names, remarks)
|
||||||
|
|
||||||
obfsParam := decodeUrlSafe(query.Get("obfsparam"))
|
obfsParam := decodeUrlSafe(query.Get("obfsparam"))
|
||||||
protocolParam := query.Get("protoparam")
|
protocolParam := decodeUrlSafe(query.Get("protoparam"))
|
||||||
|
|
||||||
ssr := make(map[string]any, 20)
|
ssr := make(map[string]any, 20)
|
||||||
|
|
||||||
@@ -513,6 +513,101 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
proxies = append(proxies, ssr)
|
proxies = append(proxies, ssr)
|
||||||
|
|
||||||
|
case "socks", "socks5", "socks5h", "http", "https":
|
||||||
|
link, err := url.Parse(line)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
server := link.Hostname()
|
||||||
|
if server == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
portStr := link.Port()
|
||||||
|
if portStr == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
remarks := link.Fragment
|
||||||
|
if remarks == "" {
|
||||||
|
remarks = fmt.Sprintf("%s:%s", server, portStr)
|
||||||
|
}
|
||||||
|
name := uniqueName(names, remarks)
|
||||||
|
encodeStr := link.User.String()
|
||||||
|
var username, password string
|
||||||
|
if encodeStr != "" {
|
||||||
|
decodeStr := string(DecodeBase64([]byte(encodeStr)))
|
||||||
|
splitStr := strings.Split(decodeStr, ":")
|
||||||
|
|
||||||
|
// todo: should use url.QueryUnescape ?
|
||||||
|
username = splitStr[0]
|
||||||
|
if len(splitStr) == 2 {
|
||||||
|
password = splitStr[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
socks := make(map[string]any, 10)
|
||||||
|
socks["name"] = name
|
||||||
|
socks["type"] = func() string {
|
||||||
|
switch scheme {
|
||||||
|
case "socks", "socks5", "socks5h":
|
||||||
|
return "socks5"
|
||||||
|
case "http", "https":
|
||||||
|
return "http"
|
||||||
|
}
|
||||||
|
return scheme
|
||||||
|
}()
|
||||||
|
socks["server"] = server
|
||||||
|
socks["port"] = portStr
|
||||||
|
socks["username"] = username
|
||||||
|
socks["password"] = password
|
||||||
|
socks["skip-cert-verify"] = true
|
||||||
|
if scheme == "https" {
|
||||||
|
socks["tls"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
proxies = append(proxies, socks)
|
||||||
|
|
||||||
|
case "anytls":
|
||||||
|
// https://github.com/anytls/anytls-go/blob/main/docs/uri_scheme.md
|
||||||
|
link, err := url.Parse(line)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
username := link.User.Username()
|
||||||
|
password, exist := link.User.Password()
|
||||||
|
if !exist {
|
||||||
|
password = username
|
||||||
|
}
|
||||||
|
query := link.Query()
|
||||||
|
server := link.Hostname()
|
||||||
|
if server == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
portStr := link.Port()
|
||||||
|
if portStr == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
insecure, sni := query.Get("insecure"), query.Get("sni")
|
||||||
|
insecureBool := insecure == "1"
|
||||||
|
fingerprint := query.Get("hpkp")
|
||||||
|
|
||||||
|
remarks := link.Fragment
|
||||||
|
if remarks == "" {
|
||||||
|
remarks = fmt.Sprintf("%s:%s", server, portStr)
|
||||||
|
}
|
||||||
|
name := uniqueName(names, remarks)
|
||||||
|
anytls := make(map[string]any, 10)
|
||||||
|
anytls["name"] = name
|
||||||
|
anytls["type"] = "anytls"
|
||||||
|
anytls["server"] = server
|
||||||
|
anytls["port"] = portStr
|
||||||
|
anytls["username"] = username
|
||||||
|
anytls["password"] = password
|
||||||
|
anytls["sni"] = sni
|
||||||
|
anytls["fingerprint"] = fingerprint
|
||||||
|
anytls["skip-cert-verify"] = insecureBool
|
||||||
|
anytls["udp"] = true
|
||||||
|
|
||||||
|
proxies = append(proxies, anytls)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -8,18 +8,23 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultAllocator = NewAllocator()
|
var DefaultAllocator = NewAllocator()
|
||||||
|
|
||||||
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
type Allocator interface {
|
||||||
type Allocator struct {
|
Get(size int) []byte
|
||||||
|
Put(buf []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultAllocator for incoming frames, optimized to prevent overwriting after zeroing
|
||||||
|
type defaultAllocator struct {
|
||||||
buffers [11]sync.Pool
|
buffers [11]sync.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAllocator initiates a []byte allocator for frames less than 65536 bytes,
|
// NewAllocator initiates a []byte allocator for frames less than 65536 bytes,
|
||||||
// the waste(memory fragmentation) of space allocation is guaranteed to be
|
// the waste(memory fragmentation) of space allocation is guaranteed to be
|
||||||
// no more than 50%.
|
// no more than 50%.
|
||||||
func NewAllocator() *Allocator {
|
func NewAllocator() Allocator {
|
||||||
return &Allocator{
|
return &defaultAllocator{
|
||||||
buffers: [...]sync.Pool{ // 64B -> 64K
|
buffers: [...]sync.Pool{ // 64B -> 64K
|
||||||
{New: func() any { return new([1 << 6]byte) }},
|
{New: func() any { return new([1 << 6]byte) }},
|
||||||
{New: func() any { return new([1 << 7]byte) }},
|
{New: func() any { return new([1 << 7]byte) }},
|
||||||
@@ -37,7 +42,7 @@ func NewAllocator() *Allocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get a []byte from pool with most appropriate cap
|
// Get a []byte from pool with most appropriate cap
|
||||||
func (alloc *Allocator) Get(size int) []byte {
|
func (alloc *defaultAllocator) Get(size int) []byte {
|
||||||
switch {
|
switch {
|
||||||
case size < 0:
|
case size < 0:
|
||||||
panic("alloc.Get: len out of range")
|
panic("alloc.Get: len out of range")
|
||||||
@@ -87,7 +92,7 @@ func (alloc *Allocator) Get(size int) []byte {
|
|||||||
|
|
||||||
// Put returns a []byte to pool for future use,
|
// Put returns a []byte to pool for future use,
|
||||||
// which the cap must be exactly 2^n
|
// which the cap must be exactly 2^n
|
||||||
func (alloc *Allocator) Put(buf []byte) error {
|
func (alloc *defaultAllocator) Put(buf []byte) error {
|
||||||
if cap(buf) == 0 || cap(buf) > 65536 {
|
if cap(buf) == 0 || cap(buf) > 65536 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,12 @@
|
|||||||
package pool
|
package pool
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// RelayBufferSize using for tcp
|
||||||
// io.Copy default buffer size is 32 KiB
|
// io.Copy default buffer size is 32 KiB
|
||||||
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
|
|
||||||
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
|
|
||||||
RelayBufferSize = 16 * 1024
|
RelayBufferSize = 16 * 1024
|
||||||
|
|
||||||
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
|
// UDPBufferSize using for udp
|
||||||
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
|
// Most UDPs are smaller than the MTU, and the TUN's MTU
|
||||||
// set to 9000, so the UDP Buffer size set to 16Kib
|
// set to 9000, so the UDP Buffer size set to 16Kib
|
||||||
UDPBufferSize = 8 * 1024
|
UDPBufferSize = 8 * 1024
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,13 +3,12 @@
|
|||||||
package pool
|
package pool
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// RelayBufferSize using for tcp
|
||||||
// io.Copy default buffer size is 32 KiB
|
// io.Copy default buffer size is 32 KiB
|
||||||
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
|
RelayBufferSize = 32 * 1024
|
||||||
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
|
|
||||||
RelayBufferSize = 20 * 1024
|
|
||||||
|
|
||||||
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
|
// UDPBufferSize using for udp
|
||||||
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
|
// Most UDPs are smaller than the MTU, and the TUN's MTU
|
||||||
// set to 9000, so the UDP Buffer size set to 16Kib
|
// set to 9000, so the UDP Buffer size set to 16Kib
|
||||||
UDPBufferSize = 16 * 1024
|
UDPBufferSize = 16 * 1024
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package pool
|
package pool
|
||||||
|
|
||||||
func Get(size int) []byte {
|
func Get(size int) []byte {
|
||||||
return defaultAllocator.Get(size)
|
return DefaultAllocator.Get(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Put(buf []byte) error {
|
func Put(buf []byte) error {
|
||||||
return defaultAllocator.Put(buf)
|
return DefaultAllocator.Put(buf)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ package pool
|
|||||||
import "github.com/metacubex/sing/common/buf"
|
import "github.com/metacubex/sing/common/buf"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
buf.DefaultAllocator = defaultAllocator
|
buf.DefaultAllocator = DefaultAllocator
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,13 +239,15 @@ func (n *num) UnmarshalText(text []byte) (err error) {
|
|||||||
|
|
||||||
func TestStructure_TextUnmarshaller(t *testing.T) {
|
func TestStructure_TextUnmarshaller(t *testing.T) {
|
||||||
rawMap := map[string]any{
|
rawMap := map[string]any{
|
||||||
"num": "255",
|
"num": "255",
|
||||||
"num_p": "127",
|
"num_p": "127",
|
||||||
|
"num_arr": []string{"1", "2", "3"},
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &struct {
|
s := &struct {
|
||||||
Num num `test:"num"`
|
Num num `test:"num"`
|
||||||
NumP *num `test:"num_p"`
|
NumP *num `test:"num_p"`
|
||||||
|
NumArr []num `test:"num_arr"`
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
err := decoder.Decode(rawMap, s)
|
err := decoder.Decode(rawMap, s)
|
||||||
@@ -253,6 +255,7 @@ func TestStructure_TextUnmarshaller(t *testing.T) {
|
|||||||
assert.Equal(t, 255, s.Num.a)
|
assert.Equal(t, 255, s.Num.a)
|
||||||
assert.NotNil(t, s.NumP)
|
assert.NotNil(t, s.NumP)
|
||||||
assert.Equal(t, s.NumP.a, 127)
|
assert.Equal(t, s.NumP.a, 127)
|
||||||
|
assert.Equal(t, s.NumArr, []num{{1}, {2}, {3}})
|
||||||
|
|
||||||
// test WeaklyTypedInput
|
// test WeaklyTypedInput
|
||||||
rawMap["num"] = 256
|
rawMap["num"] = 256
|
||||||
|
|||||||
@@ -41,3 +41,11 @@ func NewAuthenticator(users []AuthUser) Authenticator {
|
|||||||
}
|
}
|
||||||
return au
|
return au
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var AlwaysValid Authenticator = alwaysValid{}
|
||||||
|
|
||||||
|
type alwaysValid struct{}
|
||||||
|
|
||||||
|
func (alwaysValid) Verify(string, string) bool { return true }
|
||||||
|
|
||||||
|
func (alwaysValid) Users() []string { return nil }
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package geodata
|
package geodata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -76,13 +75,13 @@ func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, error) {
|
|||||||
if countryCode[0] == '!' {
|
if countryCode[0] == '!' {
|
||||||
not = true
|
not = true
|
||||||
countryCode = countryCode[1:]
|
countryCode = countryCode[1:]
|
||||||
|
if countryCode == "" {
|
||||||
|
return nil, fmt.Errorf("country code could not be empty")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
countryCode = strings.ToLower(countryCode)
|
countryCode = strings.ToLower(countryCode)
|
||||||
|
|
||||||
parts := strings.Split(countryCode, "@")
|
parts := strings.Split(countryCode, "@")
|
||||||
if len(parts) == 0 {
|
|
||||||
return nil, errors.New("empty rule")
|
|
||||||
}
|
|
||||||
listName := strings.TrimSpace(parts[0])
|
listName := strings.TrimSpace(parts[0])
|
||||||
attrVal := parts[1:]
|
attrVal := parts[1:]
|
||||||
attrs := parseAttrs(attrVal)
|
attrs := parseAttrs(attrVal)
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -1,57 +1,52 @@
|
|||||||
package process
|
package process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FindProcessAlways = "always"
|
FindProcessStrict FindProcessMode = iota
|
||||||
FindProcessStrict = "strict"
|
FindProcessAlways
|
||||||
FindProcessOff = "off"
|
FindProcessOff
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
validModes = map[string]struct{}{
|
validModes = map[string]FindProcessMode{
|
||||||
FindProcessAlways: {},
|
FindProcessStrict.String(): FindProcessStrict,
|
||||||
FindProcessOff: {},
|
FindProcessAlways.String(): FindProcessAlways,
|
||||||
FindProcessStrict: {},
|
FindProcessOff.String(): FindProcessOff,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type FindProcessMode string
|
type FindProcessMode int32
|
||||||
|
|
||||||
func (m FindProcessMode) Always() bool {
|
// UnmarshalText unserialize FindProcessMode
|
||||||
return m == FindProcessAlways
|
func (m *FindProcessMode) UnmarshalText(data []byte) error {
|
||||||
}
|
return m.Set(string(data))
|
||||||
|
|
||||||
func (m FindProcessMode) Off() bool {
|
|
||||||
return m == FindProcessOff
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FindProcessMode) UnmarshalYAML(unmarshal func(any) error) error {
|
|
||||||
var tp string
|
|
||||||
if err := unmarshal(&tp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return m.Set(tp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FindProcessMode) UnmarshalJSON(data []byte) error {
|
|
||||||
var tp string
|
|
||||||
if err := json.Unmarshal(data, &tp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return m.Set(tp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *FindProcessMode) Set(value string) error {
|
func (m *FindProcessMode) Set(value string) error {
|
||||||
mode := strings.ToLower(value)
|
mode, exist := validModes[strings.ToLower(value)]
|
||||||
_, exist := validModes[mode]
|
|
||||||
if !exist {
|
if !exist {
|
||||||
return errors.New("invalid find process mode")
|
return errors.New("invalid find process mode")
|
||||||
}
|
}
|
||||||
*m = FindProcessMode(mode)
|
*m = mode
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalText serialize FindProcessMode
|
||||||
|
func (m FindProcessMode) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(m.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m FindProcessMode) String() string {
|
||||||
|
switch m {
|
||||||
|
case FindProcessAlways:
|
||||||
|
return "always"
|
||||||
|
case FindProcessOff:
|
||||||
|
return "off"
|
||||||
|
default:
|
||||||
|
return "strict"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -46,17 +46,24 @@ func RelayDnsConn(ctx context.Context, conn net.Conn, readTimeout time.Duration)
|
|||||||
ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout)
|
ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
inData := buff[:n]
|
inData := buff[:n]
|
||||||
msg, err := relayDnsPacket(ctx, inData, buff, 0)
|
outBuff := buff[2:]
|
||||||
|
msg, err := relayDnsPacket(ctx, inData, outBuff, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = binary.Write(conn, binary.BigEndian, uint16(len(msg)))
|
if &msg[0] == &outBuff[0] { // msg is still in the buff
|
||||||
if err != nil {
|
binary.BigEndian.PutUint16(buff[:2], uint16(len(msg)))
|
||||||
return err
|
outBuff = buff[:2+len(msg)]
|
||||||
|
} else { // buff not big enough (WTF???)
|
||||||
|
newBuff := pool.Get(len(msg) + 2)
|
||||||
|
defer pool.Put(newBuff)
|
||||||
|
binary.BigEndian.PutUint16(newBuff[:2], uint16(len(msg)))
|
||||||
|
copy(newBuff[2:], msg)
|
||||||
|
outBuff = newBuff
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = conn.Write(msg)
|
_, err = conn.Write(outBuff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/metacubex/sing/common/metadata"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/lru"
|
"github.com/metacubex/mihomo/common/lru"
|
||||||
N "github.com/metacubex/mihomo/common/net"
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
@@ -72,8 +74,16 @@ func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter, packetSender C.PacketSend
|
|||||||
overrideDest := config.OverrideDest
|
overrideDest := config.OverrideDest
|
||||||
|
|
||||||
if inWhitelist {
|
if inWhitelist {
|
||||||
|
replaceDomain := func(metadata *C.Metadata, host string) {
|
||||||
|
if sd.domainCanReplace(host) {
|
||||||
|
replaceDomain(metadata, host, overrideDest)
|
||||||
|
} else {
|
||||||
|
log.Debugln("[Sniffer] Skip sni[%s]", host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if wrapable, ok := current.(sniffer.MultiPacketSniffer); ok {
|
if wrapable, ok := current.(sniffer.MultiPacketSniffer); ok {
|
||||||
return wrapable.WrapperSender(packetSender, overrideDest)
|
return wrapable.WrapperSender(packetSender, replaceDomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
host, err := current.SniffData(packet.Data())
|
host, err := current.SniffData(packet.Data())
|
||||||
@@ -81,7 +91,7 @@ func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter, packetSender C.PacketSend
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceDomain(metadata, host, overrideDest)
|
replaceDomain(metadata, host)
|
||||||
return packetSender
|
return packetSender
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,11 +138,9 @@ func (sd *Dispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, matcher := range sd.skipDomain {
|
if !sd.domainCanReplace(host) {
|
||||||
if matcher.MatchDomain(host) {
|
log.Debugln("[Sniffer] Skip sni[%s]", host)
|
||||||
log.Debugln("[Sniffer] Skip sni[%s]", host)
|
return false
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sd.skipList.Delete(dst)
|
sd.skipList.Delete(dst)
|
||||||
@@ -152,10 +160,23 @@ func replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
|
|||||||
metadata.RemoteAddress(),
|
metadata.RemoteAddress(),
|
||||||
metadata.Host, host)
|
metadata.Host, host)
|
||||||
metadata.Host = host
|
metadata.Host = host
|
||||||
|
metadata.DstIP = netip.Addr{}
|
||||||
}
|
}
|
||||||
metadata.DNSMode = C.DNSNormal
|
metadata.DNSMode = C.DNSNormal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sd *Dispatcher) domainCanReplace(host string) bool {
|
||||||
|
if host == "." || !metadata.IsDomainName(host) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, matcher := range sd.skipDomain {
|
||||||
|
if matcher.MatchDomain(host) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (sd *Dispatcher) Enable() bool {
|
func (sd *Dispatcher) Enable() bool {
|
||||||
return sd != nil && sd.enable
|
return sd != nil && sd.enable
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,6 +270,7 @@ type RawTun struct {
|
|||||||
AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
|
AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
|
||||||
AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
|
AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
|
||||||
AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
|
AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
|
||||||
|
LoopbackAddress []netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
|
||||||
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||||
RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
|
RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
|
||||||
RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"`
|
RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"`
|
||||||
@@ -360,6 +363,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 +759,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 +821,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
|
||||||
}
|
}
|
||||||
@@ -1161,10 +1169,19 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
|
|||||||
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyName := u.Fragment
|
var proxyName string
|
||||||
|
params := map[string]string{}
|
||||||
|
for _, s := range strings.Split(u.Fragment, "&") {
|
||||||
|
arr := strings.SplitN(s, "=", 2)
|
||||||
|
switch len(arr) {
|
||||||
|
case 1:
|
||||||
|
proxyName = arr[0]
|
||||||
|
case 2:
|
||||||
|
params[arr[0]] = arr[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var addr, dnsNetType string
|
var addr, dnsNetType string
|
||||||
params := map[string]string{}
|
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "udp":
|
case "udp":
|
||||||
addr, err = hostWithDefaultPort(u.Host, "53")
|
addr, err = hostWithDefaultPort(u.Host, "53")
|
||||||
@@ -1182,23 +1199,8 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
|
|||||||
addr, err = hostWithDefaultPort(u.Host, "80")
|
addr, err = hostWithDefaultPort(u.Host, "80")
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
proxyName = ""
|
|
||||||
clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User}
|
clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User}
|
||||||
addr = clearURL.String()
|
addr = clearURL.String()
|
||||||
if len(u.Fragment) != 0 {
|
|
||||||
for _, s := range strings.Split(u.Fragment, "&") {
|
|
||||||
arr := strings.Split(s, "=")
|
|
||||||
if len(arr) == 0 {
|
|
||||||
continue
|
|
||||||
} else if len(arr) == 1 {
|
|
||||||
proxyName = arr[0]
|
|
||||||
} else if len(arr) == 2 {
|
|
||||||
params[arr[0]] = arr[1]
|
|
||||||
} else {
|
|
||||||
params[arr[0]] = strings.Join(arr[1:], "=")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case "quic":
|
case "quic":
|
||||||
addr, err = hostWithDefaultPort(u.Host, "853")
|
addr, err = hostWithDefaultPort(u.Host, "853")
|
||||||
@@ -1556,6 +1558,7 @@ func parseTun(rawTun RawTun, general *General) error {
|
|||||||
AutoRedirect: rawTun.AutoRedirect,
|
AutoRedirect: rawTun.AutoRedirect,
|
||||||
AutoRedirectInputMark: rawTun.AutoRedirectInputMark,
|
AutoRedirectInputMark: rawTun.AutoRedirectInputMark,
|
||||||
AutoRedirectOutputMark: rawTun.AutoRedirectOutputMark,
|
AutoRedirectOutputMark: rawTun.AutoRedirectOutputMark,
|
||||||
|
LoopbackAddress: rawTun.LoopbackAddress,
|
||||||
StrictRoute: rawTun.StrictRoute,
|
StrictRoute: rawTun.StrictRoute,
|
||||||
RouteAddress: rawTun.RouteAddress,
|
RouteAddress: rawTun.RouteAddress,
|
||||||
RouteAddressSet: rawTun.RouteAddressSet,
|
RouteAddressSet: rawTun.RouteAddressSet,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||||
@@ -150,6 +152,9 @@ func proxyGroupsDagSort(groupsConfig []map[string]any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func verifyIP6() bool {
|
func verifyIP6() bool {
|
||||||
|
if skip, _ := strconv.ParseBool(os.Getenv("SKIP_SYSTEM_IPV6_CHECK")); skip {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if iAddrs, err := net.InterfaceAddrs(); err == nil {
|
if iAddrs, err := net.InterfaceAddrs(); err == nil {
|
||||||
for _, addr := range iAddrs {
|
for _, addr := range iAddrs {
|
||||||
if prefix, err := netip.ParsePrefix(addr.String()); err == nil {
|
if prefix, err := netip.ParsePrefix(addr.String()); err == nil {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -22,44 +21,6 @@ const (
|
|||||||
|
|
||||||
type DNSMode int
|
type DNSMode int
|
||||||
|
|
||||||
// UnmarshalYAML unserialize EnhancedMode with yaml
|
|
||||||
func (e *DNSMode) UnmarshalYAML(unmarshal func(any) error) error {
|
|
||||||
var tp string
|
|
||||||
if err := unmarshal(&tp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mode, exist := DNSModeMapping[strings.ToLower(tp)]
|
|
||||||
if !exist {
|
|
||||||
return errors.New("invalid mode")
|
|
||||||
}
|
|
||||||
*e = mode
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalYAML serialize EnhancedMode with yaml
|
|
||||||
func (e DNSMode) MarshalYAML() (any, error) {
|
|
||||||
return e.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON unserialize EnhancedMode with json
|
|
||||||
func (e *DNSMode) UnmarshalJSON(data []byte) error {
|
|
||||||
var tp string
|
|
||||||
if err := json.Unmarshal(data, &tp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mode, exist := DNSModeMapping[strings.ToLower(tp)]
|
|
||||||
if !exist {
|
|
||||||
return errors.New("invalid mode")
|
|
||||||
}
|
|
||||||
*e = mode
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON serialize EnhancedMode with json
|
|
||||||
func (e DNSMode) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(e.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText unserialize EnhancedMode
|
// UnmarshalText unserialize EnhancedMode
|
||||||
func (e *DNSMode) UnmarshalText(data []byte) error {
|
func (e *DNSMode) UnmarshalText(data []byte) error {
|
||||||
mode, exist := DNSModeMapping[strings.ToLower(string(data))]
|
mode, exist := DNSModeMapping[strings.ToLower(string(data))]
|
||||||
@@ -157,40 +118,6 @@ func (e FilterMode) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e FilterMode) MarshalYAML() (interface{}, error) {
|
|
||||||
return e.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *FilterMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
var tp string
|
|
||||||
if err := unmarshal(&tp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mode, exist := FilterModeMapping[strings.ToLower(tp)]
|
|
||||||
if !exist {
|
|
||||||
return errors.New("invalid mode")
|
|
||||||
}
|
|
||||||
*e = mode
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e FilterMode) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(e.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *FilterMode) UnmarshalJSON(data []byte) error {
|
|
||||||
var tp string
|
|
||||||
if err := json.Unmarshal(data, &tp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mode, exist := FilterModeMapping[strings.ToLower(tp)]
|
|
||||||
if !exist {
|
|
||||||
return errors.New("invalid mode")
|
|
||||||
}
|
|
||||||
*e = mode
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e FilterMode) MarshalText() ([]byte, error) {
|
func (e FilterMode) MarshalText() ([]byte, error) {
|
||||||
return []byte(e.String()), nil
|
return []byte(e.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -91,9 +91,7 @@ type RuleProvider interface {
|
|||||||
Provider
|
Provider
|
||||||
Behavior() RuleBehavior
|
Behavior() RuleBehavior
|
||||||
Count() int
|
Count() int
|
||||||
Match(*constant.Metadata) bool
|
Match(metadata *constant.Metadata, helper constant.RuleMatchHelper) bool
|
||||||
ShouldResolveIP() bool
|
|
||||||
ShouldFindProcess() bool
|
|
||||||
Strategy() any
|
Strategy() any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,14 +111,17 @@ func (rt RuleType) String() string {
|
|||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
RuleType() RuleType
|
RuleType() RuleType
|
||||||
Match(metadata *Metadata) (bool, string)
|
Match(metadata *Metadata, helper RuleMatchHelper) (bool, string)
|
||||||
Adapter() string
|
Adapter() string
|
||||||
Payload() string
|
Payload() string
|
||||||
ShouldResolveIP() bool
|
|
||||||
ShouldFindProcess() bool
|
|
||||||
ProviderNames() []string
|
ProviderNames() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RuleMatchHelper struct {
|
||||||
|
ResolveIP func()
|
||||||
|
FindProcess func()
|
||||||
|
}
|
||||||
|
|
||||||
type RuleGroup interface {
|
type RuleGroup interface {
|
||||||
Rule
|
Rule
|
||||||
GetRecodeSize() int
|
GetRecodeSize() int
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -20,42 +19,6 @@ const (
|
|||||||
|
|
||||||
type TUNStack int
|
type TUNStack int
|
||||||
|
|
||||||
// UnmarshalYAML unserialize TUNStack with yaml
|
|
||||||
func (e *TUNStack) UnmarshalYAML(unmarshal func(any) error) error {
|
|
||||||
var tp string
|
|
||||||
if err := unmarshal(&tp); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mode, exist := StackTypeMapping[strings.ToLower(tp)]
|
|
||||||
if !exist {
|
|
||||||
return errors.New("invalid tun stack")
|
|
||||||
}
|
|
||||||
*e = mode
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalYAML serialize TUNStack with yaml
|
|
||||||
func (e TUNStack) MarshalYAML() (any, error) {
|
|
||||||
return e.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON unserialize TUNStack with json
|
|
||||||
func (e *TUNStack) UnmarshalJSON(data []byte) error {
|
|
||||||
var tp string
|
|
||||||
json.Unmarshal(data, &tp)
|
|
||||||
mode, exist := StackTypeMapping[strings.ToLower(tp)]
|
|
||||||
if !exist {
|
|
||||||
return errors.New("invalid tun stack")
|
|
||||||
}
|
|
||||||
*e = mode
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON serialize TUNStack with json
|
|
||||||
func (e TUNStack) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(e.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText unserialize TUNStack
|
// UnmarshalText unserialize TUNStack
|
||||||
func (e *TUNStack) UnmarshalText(data []byte) error {
|
func (e *TUNStack) UnmarshalText(data []byte) error {
|
||||||
mode, exist := StackTypeMapping[strings.ToLower(string(data))]
|
mode, exist := StackTypeMapping[strings.ToLower(string(data))]
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
@@ -77,7 +79,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"
|
||||||
@@ -105,3 +107,24 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) ResetConnection() {}
|
func (c *client) ResetConnection() {}
|
||||||
|
|
||||||
|
func newClient(addr string, resolver *Resolver, netType string, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) *client {
|
||||||
|
host, port, _ := net.SplitHostPort(addr)
|
||||||
|
c := &client{
|
||||||
|
Client: &D.Client{
|
||||||
|
Net: netType,
|
||||||
|
TLSConfig: &tls.Config{
|
||||||
|
ServerName: host,
|
||||||
|
},
|
||||||
|
UDPSize: 4096,
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
},
|
||||||
|
port: port,
|
||||||
|
host: host,
|
||||||
|
dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
|
||||||
|
}
|
||||||
|
if params["skip-cert-verify"] == "true" {
|
||||||
|
c.TLSConfig.InsecureSkipVerify = true
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|||||||
33
dns/doh.go
33
dns/doh.go
@@ -9,7 +9,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -71,8 +70,6 @@ type dnsOverHTTPS struct {
|
|||||||
dialer *dnsDialer
|
dialer *dnsDialer
|
||||||
addr string
|
addr string
|
||||||
skipCertVerify bool
|
skipCertVerify bool
|
||||||
ecsPrefix netip.Prefix
|
|
||||||
ecsOverride bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// type check
|
// type check
|
||||||
@@ -105,28 +102,6 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
|
|||||||
doh.skipCertVerify = true
|
doh.skipCertVerify = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if ecs := params["ecs"]; ecs != "" {
|
|
||||||
prefix, err := netip.ParsePrefix(ecs)
|
|
||||||
if err != nil {
|
|
||||||
addr, err := netip.ParseAddr(ecs)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("DOH [%s] config with invalid ecs: %s", doh.addr, ecs)
|
|
||||||
} else {
|
|
||||||
doh.ecsPrefix = netip.PrefixFrom(addr, addr.BitLen())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
doh.ecsPrefix = prefix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if doh.ecsPrefix.IsValid() {
|
|
||||||
log.Debugln("DOH [%s] config with ecs: %s", doh.addr, doh.ecsPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
if params["ecs-override"] == "true" {
|
|
||||||
doh.ecsOverride = true
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close)
|
runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close)
|
||||||
|
|
||||||
return doh
|
return doh
|
||||||
@@ -154,10 +129,6 @@ func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if doh.ecsPrefix.IsValid() {
|
|
||||||
setEdns0Subnet(m, doh.ecsPrefix, doh.ecsOverride)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there was already an active client before sending the request.
|
// Check if there was already an active client before sending the request.
|
||||||
// We'll only attempt to re-connect if there was one.
|
// We'll only attempt to re-connect if there was one.
|
||||||
client, isCached, err := doh.getClient(ctx)
|
client, isCached, err := doh.getClient(ctx)
|
||||||
@@ -552,8 +523,8 @@ func (doh *dnsOverHTTPS) createTransportH3(
|
|||||||
Dial: func(
|
Dial: func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
|
||||||
// Ignore the address and always connect to the one that we got
|
// Ignore the address and always connect to the one that we got
|
||||||
// from the bootstrapper.
|
// from the bootstrapper.
|
||||||
_ string,
|
_ string,
|
||||||
tlsCfg *tlsC.Config,
|
tlsCfg *tlsC.Config,
|
||||||
cfg *quic.Config,
|
cfg *quic.Config,
|
||||||
|
|||||||
15
dns/doq.go
15
dns/doq.go
@@ -61,15 +61,16 @@ type dnsOverQUIC struct {
|
|||||||
bytesPool *sync.Pool
|
bytesPool *sync.Pool
|
||||||
bytesPoolGuard sync.Mutex
|
bytesPoolGuard sync.Mutex
|
||||||
|
|
||||||
addr string
|
addr string
|
||||||
dialer *dnsDialer
|
dialer *dnsDialer
|
||||||
|
skipCertVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// type check
|
// type check
|
||||||
var _ dnsClient = (*dnsOverQUIC)(nil)
|
var _ dnsClient = (*dnsOverQUIC)(nil)
|
||||||
|
|
||||||
// newDoQ returns the DNS-over-QUIC Upstream.
|
// newDoQ returns the DNS-over-QUIC Upstream.
|
||||||
func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) {
|
func newDoQ(addr string, resolver *Resolver, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) *dnsOverQUIC {
|
||||||
doq := &dnsOverQUIC{
|
doq := &dnsOverQUIC{
|
||||||
addr: addr,
|
addr: addr,
|
||||||
dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
|
dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
|
||||||
@@ -79,8 +80,12 @@ func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyN
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params["skip-cert-verify"] == "true" {
|
||||||
|
doq.skipCertVerify = true
|
||||||
|
}
|
||||||
|
|
||||||
runtime.SetFinalizer(doq, (*dnsOverQUIC).Close)
|
runtime.SetFinalizer(doq, (*dnsOverQUIC).Close)
|
||||||
return doq, nil
|
return doq
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address implements the Upstream interface for *dnsOverQUIC.
|
// Address implements the Upstream interface for *dnsOverQUIC.
|
||||||
@@ -329,7 +334,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
|
|||||||
tlsConfig := ca.GetGlobalTLSConfig(
|
tlsConfig := ca.GetGlobalTLSConfig(
|
||||||
&tls.Config{
|
&tls.Config{
|
||||||
ServerName: host,
|
ServerName: host,
|
||||||
InsecureSkipVerify: false,
|
InsecureSkipVerify: doq.skipCertVerify,
|
||||||
NextProtos: []string{
|
NextProtos: []string{
|
||||||
NextProtoDQ,
|
NextProtoDQ,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
107
dns/util.go
107
dns/util.go
@@ -2,10 +2,8 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -92,46 +90,95 @@ func isIPRequest(q D.Question) bool {
|
|||||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||||
ret := make([]dnsClient, 0, len(servers))
|
ret := make([]dnsClient, 0, len(servers))
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
|
var c dnsClient
|
||||||
switch s.Net {
|
switch s.Net {
|
||||||
case "https":
|
case "https":
|
||||||
ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName))
|
c = newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName)
|
||||||
continue
|
|
||||||
case "dhcp":
|
case "dhcp":
|
||||||
ret = append(ret, newDHCPClient(s.Addr))
|
c = newDHCPClient(s.Addr)
|
||||||
continue
|
|
||||||
case "system":
|
case "system":
|
||||||
ret = append(ret, newSystemClient())
|
c = newSystemClient()
|
||||||
continue
|
|
||||||
case "rcode":
|
case "rcode":
|
||||||
ret = append(ret, newRCodeClient(s.Addr))
|
c = newRCodeClient(s.Addr)
|
||||||
continue
|
|
||||||
case "quic":
|
case "quic":
|
||||||
if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter, s.ProxyName); err == nil {
|
c = newDoQ(s.Addr, resolver, s.Params, s.ProxyAdapter, s.ProxyName)
|
||||||
ret = append(ret, doq)
|
default:
|
||||||
} else {
|
c = newClient(s.Addr, resolver, s.Net, s.Params, s.ProxyAdapter, s.ProxyName)
|
||||||
log.Fatalln("DoQ format error: %v", err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(s.Addr)
|
c = warpClientWithEdns0Subnet(c, s.Params)
|
||||||
ret = append(ret, &client{
|
|
||||||
Client: &D.Client{
|
if s.Params["disable-ipv4"] == "true" {
|
||||||
Net: s.Net,
|
c = warpClientWithDisableType(c, D.TypeA)
|
||||||
TLSConfig: &tls.Config{
|
}
|
||||||
ServerName: host,
|
|
||||||
},
|
if s.Params["disable-ipv6"] == "true" {
|
||||||
UDPSize: 4096,
|
c = warpClientWithDisableType(c, D.TypeAAAA)
|
||||||
Timeout: 5 * time.Second,
|
}
|
||||||
},
|
|
||||||
port: port,
|
ret = append(ret, c)
|
||||||
host: host,
|
|
||||||
dialer: newDNSDialer(resolver, s.ProxyAdapter, s.ProxyName),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type clientWithDisableType struct {
|
||||||
|
dnsClient
|
||||||
|
qType uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientWithDisableType) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
if len(m.Question) > 0 {
|
||||||
|
q := m.Question[0]
|
||||||
|
if q.Qtype == c.qType {
|
||||||
|
return handleMsgWithEmptyAnswer(m), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.dnsClient.ExchangeContext(ctx, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func warpClientWithDisableType(c dnsClient, qType uint16) dnsClient {
|
||||||
|
return clientWithDisableType{c, qType}
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientWithEdns0Subnet struct {
|
||||||
|
dnsClient
|
||||||
|
ecsPrefix netip.Prefix
|
||||||
|
ecsOverride bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientWithEdns0Subnet) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||||
|
m = m.Copy()
|
||||||
|
setEdns0Subnet(m, c.ecsPrefix, c.ecsOverride)
|
||||||
|
return c.dnsClient.ExchangeContext(ctx, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func warpClientWithEdns0Subnet(c dnsClient, params map[string]string) dnsClient {
|
||||||
|
var ecsPrefix netip.Prefix
|
||||||
|
var ecsOverride bool
|
||||||
|
if ecs := params["ecs"]; ecs != "" {
|
||||||
|
prefix, err := netip.ParsePrefix(ecs)
|
||||||
|
if err != nil {
|
||||||
|
addr, err := netip.ParseAddr(ecs)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("DNS [%s] config with invalid ecs: %s", c.Address(), ecs)
|
||||||
|
} else {
|
||||||
|
ecsPrefix = netip.PrefixFrom(addr, addr.BitLen())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ecsPrefix = prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ecsPrefix.IsValid() {
|
||||||
|
log.Debugln("DNS [%s] config with ecs: %s", c.Address(), ecsPrefix)
|
||||||
|
if params["ecs-override"] == "true" {
|
||||||
|
ecsOverride = true
|
||||||
|
}
|
||||||
|
return clientWithEdns0Subnet{c, ecsPrefix, ecsOverride}
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
|
func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
|
||||||
msg := &D.Msg{}
|
msg := &D.Msg{}
|
||||||
msg.Answer = []D.RR{}
|
msg.Answer = []D.RR{}
|
||||||
|
|||||||
134
docs/config.yaml
134
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 不兼容!!!
|
||||||
@@ -983,7 +1020,7 @@ proxy-providers:
|
|||||||
type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5
|
type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5
|
||||||
url: "url"
|
url: "url"
|
||||||
interval: 3600
|
interval: 3600
|
||||||
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到其他位置,请通过设置 SAFE_PATHS 环境变量指定额外的安全路径。该环境变量的语法同本操作系统的PATH环境变量解析规则(即Windows下以分号分割,其他系统下以冒号分割)
|
||||||
proxy: DIRECT
|
proxy: DIRECT
|
||||||
# size-limit: 10240 # 限制下载文件最大为10kb,默认为0即不限制文件大小
|
# size-limit: 10240 # 限制下载文件最大为10kb,默认为0即不限制文件大小
|
||||||
header:
|
header:
|
||||||
@@ -1040,7 +1077,7 @@ rule-providers:
|
|||||||
rule1:
|
rule1:
|
||||||
behavior: classical # domain ipcidr
|
behavior: classical # domain ipcidr
|
||||||
interval: 259200
|
interval: 259200
|
||||||
path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到其他位置,请通过设置 SAFE_PATHS 环境变量指定额外的安全路径。该环境变量的语法同本操作系统的PATH环境变量解析规则(即Windows下以分号分割,其他系统下以冒号分割)
|
||||||
type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
|
type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
|
||||||
url: "url"
|
url: "url"
|
||||||
proxy: DIRECT
|
proxy: DIRECT
|
||||||
@@ -1127,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
|
||||||
@@ -1140,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 代理混合
|
||||||
@@ -1154,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
|
||||||
@@ -1203,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
|
||||||
@@ -1211,6 +1276,16 @@ listeners:
|
|||||||
# - 0123456789abcdef
|
# - 0123456789abcdef
|
||||||
# server-names:
|
# server-names:
|
||||||
# - test.com
|
# - test.com
|
||||||
|
# #下列两个 limit 为选填,可对未通过验证的回落连接限速,bytesPerSec 默认为 0 即不启用
|
||||||
|
# #回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
|
||||||
|
# limit-fallback-upload:
|
||||||
|
# after-bytes: 0 # 传输指定字节后开始限速
|
||||||
|
# bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||||
|
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||||
|
# limit-fallback-download:
|
||||||
|
# after-bytes: 0 # 传输指定字节后开始限速
|
||||||
|
# bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||||
|
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||||
|
|
||||||
- name: tuic-in-1
|
- name: tuic-in-1
|
||||||
type: tuic
|
type: tuic
|
||||||
@@ -1225,6 +1300,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
|
||||||
@@ -1256,6 +1338,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
|
||||||
@@ -1264,6 +1353,16 @@ listeners:
|
|||||||
- 0123456789abcdef
|
- 0123456789abcdef
|
||||||
server-names:
|
server-names:
|
||||||
- test.com
|
- test.com
|
||||||
|
#下列两个 limit 为选填,可对未通过验证的回落连接限速,bytesPerSec 默认为 0 即不启用
|
||||||
|
#回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
|
||||||
|
limit-fallback-upload:
|
||||||
|
after-bytes: 0 # 传输指定字节后开始限速
|
||||||
|
bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||||
|
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||||
|
limit-fallback-download:
|
||||||
|
after-bytes: 0 # 传输指定字节后开始限速
|
||||||
|
bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||||
|
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||||
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ###
|
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ###
|
||||||
|
|
||||||
- name: anytls-in-1
|
- name: anytls-in-1
|
||||||
@@ -1276,6 +1375,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
|
||||||
@@ -1292,6 +1398,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
|
||||||
@@ -1300,6 +1413,16 @@ listeners:
|
|||||||
# - 0123456789abcdef
|
# - 0123456789abcdef
|
||||||
# server-names:
|
# server-names:
|
||||||
# - test.com
|
# - test.com
|
||||||
|
# #下列两个 limit 为选填,可对未通过验证的回落连接限速,bytesPerSec 默认为 0 即不启用
|
||||||
|
# #回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
|
||||||
|
# limit-fallback-upload:
|
||||||
|
# after-bytes: 0 # 传输指定字节后开始限速
|
||||||
|
# bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||||
|
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||||
|
# limit-fallback-download:
|
||||||
|
# after-bytes: 0 # 传输指定字节后开始限速
|
||||||
|
# bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||||
|
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||||
# ss-option: # like trojan-go's `shadowsocks` config
|
# ss-option: # like trojan-go's `shadowsocks` config
|
||||||
# enabled: false
|
# enabled: false
|
||||||
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
|
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
|
||||||
@@ -1317,6 +1440,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
|
||||||
|
|||||||
24
go.mod
24
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.5
|
||||||
github.com/metacubex/fswatch v0.1.1
|
github.com/metacubex/fswatch v0.1.1
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||||
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b
|
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639
|
||||||
github.com/metacubex/randv2 v0.2.0
|
github.com/metacubex/randv2 v0.2.0
|
||||||
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7
|
github.com/metacubex/sing v0.5.4-0.20250605054047-54dc6097da29
|
||||||
github.com/metacubex/sing-mux v0.3.2
|
github.com/metacubex/sing-mux v0.3.2
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2
|
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.9
|
github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621023810-0e9ef9dd0c92
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.3
|
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621023950-93d605a2143d
|
||||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||||
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6
|
github.com/metacubex/sing-tun v0.4.7-0.20250611091011-60774779fdd8
|
||||||
github.com/metacubex/sing-vmess v0.2.1
|
github.com/metacubex/sing-vmess v0.2.2
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
|
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
|
||||||
github.com/metacubex/tfo-go v0.0.0-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.4-0.20250610022031-808d767c8c73
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
||||||
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
|
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0
|
github.com/mroth/weightedrand/v2 v2.1.0
|
||||||
@@ -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
|
||||||
|
|||||||
52
go.sum
52
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,12 +97,12 @@ 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.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
|
||||||
github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||||
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
|
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
|
||||||
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
|
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||||
@@ -111,35 +111,39 @@ github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM
|
|||||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
|
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
|
||||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
|
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
|
||||||
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b h1:8oDU32eJ+RRhl1FodGgPfxQxtoBAiD9D40XG2XtU/SE=
|
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639 h1:L+1brQNzBhCCxWlicwfK1TlceemCRmrDE4HmcVHc29w=
|
||||||
github.com/metacubex/quic-go v0.51.1-0.20250511032541-4e34341cf18b/go.mod h1:9R1NOzCgTcWsdWvOMlmtMuF0uKzuOpsfvEf7U3I8zM0=
|
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639/go.mod h1:Kc6h++Q/zf3AxcUCevJhJwgrskJumv+pZdR8g/E/10k=
|
||||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||||
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||||
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxMzzmnXams+ebwabcry4Ydep/zNiesQ=
|
github.com/metacubex/sing v0.5.4-0.20250605054047-54dc6097da29 h1:SD9q025FNTaepuFXFOKDhnGLVu6PQYChBvw2ZYPXeLo=
|
||||||
github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
github.com/metacubex/sing v0.5.4-0.20250605054047-54dc6097da29/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||||
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
|
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
|
||||||
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2 h1:wfmYgtECbEYo1slMtyo+2kMqscYYDSjU/TVgS3018F4=
|
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250511034158-b46e0e3e81b2/go.mod h1:P1kd57U6XXmXv9PbwWdznUGT0k9bKgFJXF0fEORbIlk=
|
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00=
|
github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621021503-4f85ef9bf4b3 h1:dtiRj7WaCAXp4UhCkmaIiFF6v886qXiuqeIDN4Z//9E=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA=
|
github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621021503-4f85ef9bf4b3/go.mod h1:/squZ38pXrYjqtg8qn+joVvwbpGNYQNp8yxKsMVbCto=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0=
|
github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621023810-0e9ef9dd0c92 h1:Y9ebcKya6ow7VHoESCN5+l4zZvg5eaL2IhI5LLCQxQA=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.3/go.mod h1:/WNy/Q8ahLCoPRriWuFZFD0Jy+JNp1MEQl28Zw6SaF8=
|
github.com/metacubex/sing-shadowsocks v0.2.11-0.20250621023810-0e9ef9dd0c92/go.mod h1:/squZ38pXrYjqtg8qn+joVvwbpGNYQNp8yxKsMVbCto=
|
||||||
|
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621021638-dcd503063651 h1:vwLj0DDjPYy4AHEZvfRVf8ih52o6wpBnJxXxqa+ztmE=
|
||||||
|
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621021638-dcd503063651/go.mod h1:+ukTd0OPFglT3bnKAYTJWYPbuox6HYNXE235r5tHdUk=
|
||||||
|
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621023950-93d605a2143d h1:Ey3A1tA8lVkRbK1FDmwuWj/57Nr8JMdpoVqe45mFzJg=
|
||||||
|
github.com/metacubex/sing-shadowsocks2 v0.2.5-0.20250621023950-93d605a2143d/go.mod h1:+ukTd0OPFglT3bnKAYTJWYPbuox6HYNXE235r5tHdUk=
|
||||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||||
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 h1:TAwL91XPa6x1QK55CRm+VTzPvLPUfEr/uFDnOZArqEU=
|
github.com/metacubex/sing-tun v0.4.7-0.20250611091011-60774779fdd8 h1:4zWKqxTx75TbfW2EmlQ3hxM6RTRg2PYOAVMCnU4I61I=
|
||||||
github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
|
github.com/metacubex/sing-tun v0.4.7-0.20250611091011-60774779fdd8/go.mod h1:2YywXPWW8Z97kTH7RffOeykKzU+l0aiKlglWV1PAS64=
|
||||||
github.com/metacubex/sing-vmess v0.2.1 h1:I6gM3VUjtvJ15D805EUbNH+SRBuqzJeFnuIbKYUsWZ0=
|
github.com/metacubex/sing-vmess v0.2.2 h1:nG6GIKF1UOGmlzs+BIetdGHkFZ20YqFVIYp5Htqzp+4=
|
||||||
github.com/metacubex/sing-vmess v0.2.1/go.mod h1:DsODWItJtOMZNna8Qhheg8r3tUivrcO3vWgaTYKnfTo=
|
github.com/metacubex/sing-vmess v0.2.2/go.mod h1:CVDNcdSLVYFgTHQlubr88d8CdqupAUDqLjROos+H9xk=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
|
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
|
||||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
|
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
|
||||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
|
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
|
||||||
github.com/metacubex/tfo-go v0.0.0-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.4-0.20250610022031-808d767c8c73 h1:HWKsf92BqLYqugATFIJ3hYiEBZ7JF6AoqyvqF39afuI=
|
||||||
github.com/metacubex/utls v1.7.0-alpha.2/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
|
github.com/metacubex/utls v1.7.4-0.20250610022031-808d767c8c73/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
||||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ type tunSchema struct {
|
|||||||
AutoRedirect *bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
|
AutoRedirect *bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
|
||||||
AutoRedirectInputMark *uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
|
AutoRedirectInputMark *uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
|
||||||
AutoRedirectOutputMark *uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
|
AutoRedirectOutputMark *uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
|
||||||
|
LoopbackAddress *[]netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
|
||||||
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||||
RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
|
RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
|
||||||
RouteAddressSet *[]string `yaml:"route-address-set" json:"route-address-set,omitempty"`
|
RouteAddressSet *[]string `yaml:"route-address-set" json:"route-address-set,omitempty"`
|
||||||
@@ -174,6 +175,9 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
|
|||||||
if p.AutoRedirectOutputMark != nil {
|
if p.AutoRedirectOutputMark != nil {
|
||||||
def.AutoRedirectOutputMark = *p.AutoRedirectOutputMark
|
def.AutoRedirectOutputMark = *p.AutoRedirectOutputMark
|
||||||
}
|
}
|
||||||
|
if p.LoopbackAddress != nil {
|
||||||
|
def.LoopbackAddress = *p.LoopbackAddress
|
||||||
|
}
|
||||||
if p.StrictRoute != nil {
|
if p.StrictRoute != nil {
|
||||||
def.StrictRoute = *p.StrictRoute
|
def.StrictRoute = *p.StrictRoute
|
||||||
}
|
}
|
||||||
@@ -371,12 +375,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.IsAbs(req.Path) {
|
} else {
|
||||||
render.Status(r, http.StatusBadRequest)
|
if !filepath.IsAbs(req.Path) {
|
||||||
render.JSON(w, r, newError("path is not a 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"`
|
||||||
|
|||||||
@@ -9,18 +9,6 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StringSliceToNetipPrefixSlice(ss []string) ([]netip.Prefix, error) {
|
|
||||||
lps := make([]netip.Prefix, 0, len(ss))
|
|
||||||
for _, s := range ss {
|
|
||||||
prefix, err := netip.ParsePrefix(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
lps = append(lps, prefix)
|
|
||||||
}
|
|
||||||
return lps, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tun struct {
|
type Tun struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
Device string `yaml:"device" json:"device"`
|
Device string `yaml:"device" json:"device"`
|
||||||
@@ -39,6 +27,7 @@ type Tun struct {
|
|||||||
AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
|
AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
|
||||||
AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
|
AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
|
||||||
AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
|
AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
|
||||||
|
LoopbackAddress []netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
|
||||||
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||||
RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
|
RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
|
||||||
RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"`
|
RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"`
|
||||||
@@ -142,6 +131,9 @@ func (t *Tun) Equal(other Tun) bool {
|
|||||||
if t.AutoRedirectOutputMark != other.AutoRedirectOutputMark {
|
if t.AutoRedirectOutputMark != other.AutoRedirectOutputMark {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if !slices.Equal(t.RouteAddress, other.RouteAddress) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if t.StrictRoute != other.StrictRoute {
|
if t.StrictRoute != other.StrictRoute {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"`
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user