This commit is contained in:
tanmingxin 2024-05-24 23:14:11 +08:00
parent 0b6ae6ffb8
commit 1cf4f04df9
56 changed files with 3343 additions and 397 deletions

View file

@ -0,0 +1,114 @@
package inbound
import (
"fmt"
C "github.com/metacubex/mihomo/constant"
gossh "golang.org/x/crypto/ssh"
"net"
"net/netip"
"time"
)
type LocalForwardChannelData struct {
DestAddr string
DestPort uint32
OriginAddr string
OriginPort uint32
}
type MySSHConn struct {
gossh.Channel
LocalAddr_ net.Addr
RemoteAddr_ net.Addr
}
var channelCloseBytes = [1]byte{97}
func (m *MySSHConn) Close() error {
//m.Write(channelCloseBytes[:])
return m.Channel.Close()
}
func (m *MySSHConn) LocalAddr() net.Addr {
return m.LocalAddr_
}
func (m *MySSHConn) RemoteAddr() net.Addr {
return m.RemoteAddr_
}
func (m *MySSHConn) SetDeadline(t time.Time) error {
return nil
}
func (m *MySSHConn) SetReadDeadline(t time.Time) error {
return nil
}
func (m *MySSHConn) SetWriteDeadline(t time.Time) error {
return nil
}
func NewSSH(data *LocalForwardChannelData, conn net.Conn, source C.Type) *C.Metadata {
metadata := &C.Metadata{}
metadata.NetWork = C.TCP
metadata.Host = data.DestAddr
metadata.DstPort = uint16(data.DestPort)
if ip := net.ParseIP(metadata.Host); ip != nil {
addr, _ := netip.AddrFromSlice([]byte(ip))
metadata.DstIP = addr
}
metadata.Type = source
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
addr, _ := netip.AddrFromSlice([]byte(ip))
metadata.SrcIP = addr
metadata.SrcPort = uint16(port)
}
return metadata
}
type TLSProxy struct {
Cert string `yaml:"cert"`
Key string `yaml:"key"`
Socks5Proxy *Socks5Proxy `yaml:"socks5"`
TlsTargets map[string]*TlsTarget `yaml:"proxy"`
}
type TlsTarget struct {
ProxyProto bool `yaml:"proxyproto"`
Target string `yaml:"target"`
}
type Socks5Proxy struct {
}
type SSHProxy struct {
ProxyProto bool `yaml:"proxyproto"`
Target string `yaml:"target"`
}
type SynDrive struct {
Target string `yaml:"target"`
}
type WanInput struct {
Port int `yaml:"port"`
Authentication []string `yaml:"authentication"`
SshProxy *SSHProxy `yaml:"ssh"`
TlsProxy *TLSProxy `yaml:"tls"`
Syndrive *SynDrive `yaml:"syn-drive"`
}
func parseAddr(addr net.Addr) (net.IP, int, error) {
switch a := addr.(type) {
case *net.TCPAddr:
return a.IP, a.Port, nil
case *net.UDPAddr:
return a.IP, a.Port, nil
default:
return nil, 0, fmt.Errorf("unknown address type %s", addr.String())
}
}

View file

@ -3,7 +3,6 @@ package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
@ -16,7 +15,6 @@ import (
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/trojan"
)
@ -31,8 +29,6 @@ type Trojan struct {
transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig
ssCipher core.Cipher
}
type TrojanOption struct {
@ -50,17 +46,9 @@ type TrojanOption struct {
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
SSOpts TrojanSSOption `proxy:"ss-opts,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
type TrojanSSOption struct {
Enabled bool `proxy:"enabled,omitempty"`
Method string `proxy:"method,omitempty"`
Password string `proxy:"password,omitempty"`
}
func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) {
if t.option.Network == "ws" {
host, port, _ := net.SplitHostPort(t.addr)
@ -107,10 +95,6 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
if metadata.NetWork == C.UDP {
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
return c, err
@ -128,10 +112,6 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
return nil, err
}
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
c.Close()
return nil, err
@ -181,11 +161,6 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil {
return nil, err
@ -218,10 +193,6 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil {
return nil, err
@ -286,20 +257,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
}
tOption.Reality = t.realityConfig
if option.SSOpts.Enabled {
if option.SSOpts.Password == "" {
return nil, errors.New("empty password")
}
if option.SSOpts.Method == "" {
option.SSOpts.Method = "AES-128-GCM"
}
ciph, err := core.PickCipher(option.SSOpts.Method, nil, option.SSOpts.Password)
if err != nil {
return nil, err
}
t.ssCipher = ciph
}
if option.Network == "grpc" {
dialFn := func(network, addr string) (net.Conn, error) {
var err error

View file

@ -152,15 +152,17 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
}
func (pp *proxySetProvider) closeAllConnections() {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
for _, chain := range c.Chains() {
if chain == pp.Name() {
_ = c.Close()
break
for _, v := range statistic.ChannelManager {
v.Range(func(c statistic.Tracker) bool {
for _, chain := range c.Chains() {
if chain == pp.Name() {
_ = c.Close()
break
}
}
}
return true
})
return true
})
}
}
func stopProxyProvider(pd *ProxySetProvider) {

View file

@ -333,7 +333,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
case "ws", "httpupgrade":
headers := make(map[string]any)
wsOpts := make(map[string]any)
wsOpts["path"] = "/"
wsOpts["path"] = []string{"/"}
if host, ok := values["host"]; ok && host != "" {
headers["Host"] = host.(string)
}

View file

@ -0,0 +1,8 @@
ECHO %date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,2%:%time:~3,2%:%time:~6,2%
set RELEASE=%date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,2%:%time:~3,2%:%time:~6,2%
ECHO %RELEASE%
set GOOS=linux
set CGO_ENABLED=0
set GOARCH=amd64
go build -o clash -trimpath -ldflags="-X 'github.com/metacubex/mihomo/constant.Version=%RELEASE%' -X 'github.com/metacubex/mihomo/constant.BuildTime=%RELEASE%' -w -s" ..

View file

@ -0,0 +1,8 @@
ECHO %date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,2%:%time:~3,2%:%time:~6,2%
set RELEASE=%date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,2%:%time:~3,2%:%time:~6,2%
ECHO %RELEASE%
set GOOS=linux
set CGO_ENABLED=0
set GOARCH=arm
go build -o clash -trimpath -ldflags="-X 'github.com/metacubex/mihomo/constant.Version=%RELEASE%' -X 'github.com/metacubex/mihomo/constant.BuildTime=%RELEASE%' -w -s" ..

View file

@ -0,0 +1,8 @@
ECHO %date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,2%:%time:~3,2%:%time:~6,2%
set RELEASE=%date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,2%:%time:~3,2%:%time:~6,2%
ECHO %RELEASE%
set GOOS=linux
set CGO_ENABLED=0
set GOARCH=arm64
go build -o clash -trimpath -ldflags="-X 'github.com/metacubex/mihomo/constant.Version=%RELEASE%' -X 'github.com/metacubex/mihomo/constant.BuildTime=%RELEASE%' -w -s" ..

View file

@ -0,0 +1,9 @@
ECHO %date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,2%:%time:~3,2%:%time:~6,2%
set RELEASE=%date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,2%:%time:~3,2%:%time:~6,2%
ECHO %RELEASE%
set GOOS=windows
set CGO_ENABLED=0
set GOARCH=amd64
set GOAMD64=v3
go build -o meta.exe -trimpath -ldflags="-X 'github.com/metacubex/mihomo/constant.Version=%RELEASE%' -X 'github.com/metacubex/mihomo/constant.BuildTime=%RELEASE%' -w -s" ..

View file

@ -63,10 +63,21 @@ func DialContext(ctx context.Context, network, address string, options ...Option
network = fmt.Sprintf("%s%d", network, opt.network)
}
ips, port, err := parseAddr(ctx, network, address, opt.resolver)
ips_, port, err := parseAddr(ctx, network, address, opt.resolver)
if err != nil {
return nil, err
}
var ips []netip.Addr
for _, v := range ips_ {
if !v.IsUnspecified() {
ips = append(ips, v)
}
}
if len(ips) == 0 {
log.Debugln("unresolved ip for %s, reject", address)
return NopConn{}, nil
}
switch network {
case "tcp4", "tcp6", "udp4", "udp6":

View file

@ -0,0 +1,23 @@
package dialer
import (
"github.com/metacubex/mihomo/common/buf"
"io"
"net"
"time"
)
type NopConn struct{}
func (rw NopConn) Read(b []byte) (int, error) { return 0, io.EOF }
func (rw NopConn) ReadBuffer(buffer *buf.Buffer) error { return io.EOF }
func (rw NopConn) Write(b []byte) (int, error) { return 0, io.EOF }
func (rw NopConn) WriteBuffer(buffer *buf.Buffer) error { return io.EOF }
func (rw NopConn) Close() error { return nil }
func (rw NopConn) LocalAddr() net.Addr { return nil }
func (rw NopConn) RemoteAddr() net.Addr { return nil }
func (rw NopConn) SetDeadline(time.Time) error { return nil }
func (rw NopConn) SetReadDeadline(time.Time) error { return nil }
func (rw NopConn) SetWriteDeadline(time.Time) error { return nil }

View file

@ -64,7 +64,17 @@ func (p proxyDialer) DialContext(ctx context.Context, network, address string) (
return nil, err
}
if p.statistic {
conn = statistic.NewTCPTracker(conn, statistic.DefaultManager, currentMeta, nil, 0, 0, false)
channel := p.proxy.Name()
manager, ok := statistic.ChannelManager[channel]
if !ok {
statistic.ChannelMutex.Lock()
manager, ok = statistic.ChannelManager[channel]
if !ok {
manager = statistic.NewManager(channel)
}
statistic.ChannelMutex.Unlock()
}
conn = statistic.NewTCPTracker(conn, manager, currentMeta, nil, 0, 0, false, "")
}
return conn, err
}
@ -87,7 +97,17 @@ func (p proxyDialer) listenPacket(ctx context.Context, currentMeta *C.Metadata)
return nil, err
}
if p.statistic {
pc = statistic.NewUDPTracker(pc, statistic.DefaultManager, currentMeta, nil, 0, 0, false)
channel := p.proxy.Name()
manager, ok := statistic.ChannelManager[channel]
if !ok {
statistic.ChannelMutex.Lock()
manager, ok = statistic.ChannelManager[channel]
if !ok {
manager = statistic.NewManager(channel)
}
statistic.ChannelMutex.Unlock()
}
pc = statistic.NewUDPTracker(pc, manager, currentMeta, nil, 0, 0, false)
}
return pc, nil
}

View file

@ -4,6 +4,7 @@ import (
"container/list"
"errors"
"fmt"
"github.com/metacubex/mihomo/adapter/inbound"
"net"
"net/netip"
"net/url"
@ -28,7 +29,6 @@ import (
SNIFF "github.com/metacubex/mihomo/component/sniffer"
tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/metacubex/mihomo/component/trie"
"github.com/metacubex/mihomo/component/updater"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
providerTypes "github.com/metacubex/mihomo/constant/provider"
@ -51,6 +51,7 @@ type General struct {
Controller
Mode T.TunnelMode `json:"mode"`
UnifiedDelay bool
Log *Log `json:"log"`
LogLevel log.LogLevel `json:"log-level"`
IPv6 bool `json:"ipv6"`
Interface string `json:"interface-name"`
@ -194,6 +195,9 @@ type Config struct {
Tunnels []LC.Tunnel
Sniffer *Sniffer
TLS *TLS
WanInput *inbound.WanInput
TlsUser []auth.AuthUser
}
type RawNTP struct {
@ -286,7 +290,14 @@ type RawTuicServer struct {
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
}
type Log struct {
File string `yaml:"file"`
Level log.LogLevel `yaml:"level"`
MaxSize int `json:"maxsize" yaml:"maxsize"`
MaxBackups int `json:"maxbackups" yaml:"maxbackups"`
MaxAge int `json:"maxage" yaml:"maxage"`
Compress bool `json:"compress" yaml:"compress"`
}
type RawConfig struct {
Port int `yaml:"port" json:"port"`
SocksPort int `yaml:"socks-port" json:"socks-port"`
@ -305,7 +316,7 @@ type RawConfig struct {
BindAddress string `yaml:"bind-address" json:"bind-address"`
Mode T.TunnelMode `yaml:"mode" json:"mode"`
UnifiedDelay bool `yaml:"unified-delay" json:"unified-delay"`
LogLevel log.LogLevel `yaml:"log-level" json:"log-level"`
Log Log `yaml:"log"`
IPv6 bool `yaml:"ipv6" json:"ipv6"`
ExternalController string `yaml:"external-controller"`
ExternalControllerUnix string `yaml:"external-controller-unix"`
@ -349,6 +360,8 @@ type RawConfig struct {
Listeners []map[string]any `yaml:"listeners"`
ClashForAndroid RawClashForAndroid `yaml:"clash-for-android" json:"clash-for-android"`
WanInput inbound.WanInput `yaml:"wan-input"`
}
type GeoXUrl struct {
@ -394,6 +407,10 @@ func Parse(buf []byte) (*Config, error) {
return nil, err
}
log.SetLevel(rawCfg.Log.Level)
log.SetOutput(rawCfg.Log.File, rawCfg.Log.MaxSize,
rawCfg.Log.MaxBackups, rawCfg.Log.MaxAge, rawCfg.Log.Compress)
return ParseRawConfig(rawCfg)
}
@ -411,7 +428,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
GeodataLoader: "memconservative",
UnifiedDelay: false,
Authentication: []string{},
LogLevel: log.INFO,
Log: Log{},
Hosts: map[string]any{},
Rule: []string{},
Proxy: []map[string]any{},
@ -513,6 +530,10 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
GeoSite: "https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat",
},
ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip",
WanInput: inbound.WanInput{
Port: 0,
},
}
if err := yaml.Unmarshal(buf, rawCfg); err != nil {
@ -557,7 +578,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
log.Infoln("Geodata Loader mode: %s", geodata.LoaderName())
log.Infoln("Geosite Matcher implementation: %s", geodata.SiteMatcherName())
ruleProviders, err := parseRuleProviders(rawCfg)
ruleProviders, err := parseRuleProviders(rawCfg, proxies)
if err != nil {
return nil, err
}
@ -602,6 +623,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
config.Users = parseAuthentication(rawCfg.Authentication)
if rawCfg.WanInput.Port != 0 {
config.TlsUser = parseAuthentication(rawCfg.WanInput.Authentication)
}
config.WanInput = &rawCfg.WanInput
config.Tunnels = rawCfg.Tunnels
// verify tunnels
for _, t := range config.Tunnels {
@ -641,28 +667,28 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second
}
updater.ExternalUIPath = cfg.ExternalUI
ExternalUIPath = cfg.ExternalUI
// checkout externalUI exist
if updater.ExternalUIPath != "" {
updater.ExternalUIPath = C.Path.Resolve(updater.ExternalUIPath)
if _, err := os.Stat(updater.ExternalUIPath); os.IsNotExist(err) {
if ExternalUIPath != "" {
ExternalUIPath = C.Path.Resolve(ExternalUIPath)
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
defaultUIpath := path.Join(C.Path.HomeDir(), "ui")
log.Warnln("external-ui: %s does not exist, creating folder in %s", updater.ExternalUIPath, defaultUIpath)
log.Warnln("external-ui: %s does not exist, creating folder in %s", ExternalUIPath, defaultUIpath)
if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil {
return nil, err
}
updater.ExternalUIPath = defaultUIpath
ExternalUIPath = defaultUIpath
cfg.ExternalUI = defaultUIpath
}
}
// checkout UIpath/name exist
if cfg.ExternalUIName != "" {
updater.ExternalUIName = cfg.ExternalUIName
ExternalUIName = cfg.ExternalUIName
} else {
updater.ExternalUIFolder = updater.ExternalUIPath
ExternalUIFolder = ExternalUIPath
}
if cfg.ExternalUIURL != "" {
updater.ExternalUIURL = cfg.ExternalUIURL
ExternalUIURL = cfg.ExternalUIURL
}
cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun
@ -692,7 +718,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
},
UnifiedDelay: cfg.UnifiedDelay,
Mode: cfg.Mode,
LogLevel: cfg.LogLevel,
Log: &cfg.Log,
IPv6: cfg.IPv6,
Interface: cfg.Interface,
RoutingMark: cfg.RoutingMark,
@ -835,7 +861,7 @@ func parseListeners(cfg *RawConfig) (listeners map[string]C.InboundListener, err
return
}
func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes.RuleProvider, err error) {
func parseRuleProviders(cfg *RawConfig, proxies map[string]C.Proxy) (ruleProviders map[string]providerTypes.RuleProvider, err error) {
ruleProviders = map[string]providerTypes.RuleProvider{}
// parse rule provider
for name, mapping := range cfg.RuleProvider {

View file

@ -2,10 +2,13 @@ package config
import (
"fmt"
"os"
"github.com/go-co-op/gocron"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel/statistic"
"os"
P "path"
"time"
)
// Init prepare necessary files
@ -28,5 +31,28 @@ func Init(dir string) error {
f.Close()
}
if _, err := os.Stat(C.Path.StatisticPath()); os.IsNotExist(err) {
if err = os.Mkdir(C.Path.StatisticPath(), os.ModePerm); err != nil {
log.Errorln(err.Error())
}
}
statistic.RestoreChannelsData(CurrentMonthStatisticFileName())
s := gocron.NewScheduler(time.Local)
s.Every(10).Minutes().Do(func() {
ts := time.Now().AddDate(0, 0, -1)
fileName := P.Join(C.Path.StatisticPath(), ts.Format("200601")+"-statistic.json")
statistic.SaveChannelsData(fileName)
if fileName != CurrentMonthStatisticFileName() {
for _, v := range statistic.ChannelManager {
v.ResetStatistic()
}
}
})
s.StartAsync()
return nil
}
func CurrentMonthStatisticFileName() string {
return P.Join(C.Path.StatisticPath(), time.Now().Format("200601")+"-statistic.json")
}

90
config/update_geo.go Normal file
View file

@ -0,0 +1,90 @@
package config
import (
"fmt"
"runtime"
"github.com/metacubex/mihomo/component/geodata"
_ "github.com/metacubex/mihomo/component/geodata/standard"
"github.com/metacubex/mihomo/component/mmdb"
C "github.com/metacubex/mihomo/constant"
"github.com/oschwald/maxminddb-golang"
)
func UpdateGeoDatabases() error {
defer runtime.GC()
geoLoader, err := geodata.GetGeoDataLoader("standard")
if err != nil {
return err
}
if C.GeodataMode {
data, err := downloadForBytes(C.GeoIpUrl)
if err != nil {
return fmt.Errorf("can't download GeoIP database file: %w", err)
}
if _, err = geoLoader.LoadIPByBytes(data, "cn"); err != nil {
return fmt.Errorf("invalid GeoIP database file: %s", err)
}
if err = saveFile(data, C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't save GeoIP database file: %w", err)
}
} else {
defer mmdb.ReloadIP()
data, err := downloadForBytes(C.MmdbUrl)
if err != nil {
return fmt.Errorf("can't download MMDB database file: %w", err)
}
instance, err := maxminddb.FromBytes(data)
if err != nil {
return fmt.Errorf("invalid MMDB database file: %s", err)
}
_ = instance.Close()
mmdb.IPInstance().Reader.Close() // mmdb is loaded with mmap, so it needs to be closed before overwriting the file
if err = saveFile(data, C.Path.MMDB()); err != nil {
return fmt.Errorf("can't save MMDB database file: %w", err)
}
}
if C.ASNEnable {
defer mmdb.ReloadASN()
data, err := downloadForBytes(C.ASNUrl)
if err != nil {
return fmt.Errorf("can't download ASN database file: %w", err)
}
instance, err := maxminddb.FromBytes(data)
if err != nil {
return fmt.Errorf("invalid ASN database file: %s", err)
}
_ = instance.Close()
mmdb.ASNInstance().Reader.Close()
if err = saveFile(data, C.Path.ASN()); err != nil {
return fmt.Errorf("can't save ASN database file: %w", err)
}
}
data, err := downloadForBytes(C.GeoSiteUrl)
if err != nil {
return fmt.Errorf("can't download GeoSite database file: %w", err)
}
if _, err = geoLoader.LoadSiteByBytes(data, "cn"); err != nil {
return fmt.Errorf("invalid GeoSite database file: %s", err)
}
if err = saveFile(data, C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't save GeoSite database file: %w", err)
}
geodata.ClearCache()
return nil
}

145
config/update_ui.go Normal file
View file

@ -0,0 +1,145 @@
package config
import (
"archive/zip"
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"sync"
C "github.com/metacubex/mihomo/constant"
)
var (
ExternalUIURL string
ExternalUIPath string
ExternalUIFolder string
ExternalUIName string
)
var (
ErrIncompleteConf = errors.New("ExternalUI configure incomplete")
)
var xdMutex sync.Mutex
func UpdateUI() error {
xdMutex.Lock()
defer xdMutex.Unlock()
err := prepare()
if err != nil {
return err
}
data, err := downloadForBytes(ExternalUIURL)
if err != nil {
return fmt.Errorf("can't download file: %w", err)
}
saved := path.Join(C.Path.HomeDir(), "download.zip")
if err = saveFile(data, saved); err != nil {
return fmt.Errorf("can't save zip file: %w", err)
}
defer os.Remove(saved)
err = cleanup(ExternalUIFolder)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("cleanup exist file error: %w", err)
}
}
unzipFolder, err := unzip(saved, C.Path.HomeDir())
if err != nil {
return fmt.Errorf("can't extract zip file: %w", err)
}
err = os.Rename(unzipFolder, ExternalUIFolder)
if err != nil {
return fmt.Errorf("can't rename folder: %w", err)
}
return nil
}
func prepare() error {
if ExternalUIPath == "" || ExternalUIURL == "" {
return ErrIncompleteConf
}
if ExternalUIName != "" {
ExternalUIFolder = filepath.Clean(path.Join(ExternalUIPath, ExternalUIName))
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil {
return err
}
}
} else {
ExternalUIFolder = ExternalUIPath
}
return nil
}
func unzip(src, dest string) (string, error) {
r, err := zip.OpenReader(src)
if err != nil {
return "", err
}
defer r.Close()
var extractedFolder string
for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
return "", fmt.Errorf("invalid file path: %s", fpath)
}
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return "", err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return "", err
}
rc, err := f.Open()
if err != nil {
return "", err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return "", err
}
if extractedFolder == "" {
extractedFolder = filepath.Dir(fpath)
}
}
return extractedFolder, nil
}
func cleanup(root string) error {
if _, err := os.Stat(root); os.IsNotExist(err) {
return nil
}
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
if err := os.RemoveAll(path); err != nil {
return err
}
} else {
if err := os.Remove(path); err != nil {
return err
}
}
return nil
})
}

View file

@ -1,15 +1,38 @@
package config
import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/netip"
"os"
"strings"
"time"
"github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/common/structure"
mihomoHttp "github.com/metacubex/mihomo/component/http"
C "github.com/metacubex/mihomo/constant"
)
func downloadForBytes(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func saveFile(bytes []byte, path string) error {
return os.WriteFile(path, bytes, 0o644)
}
func trimArr(arr []string) (r []string) {
for _, e := range arr {
r = append(r, strings.Trim(e, " "))

View file

@ -32,6 +32,7 @@ const (
TUIC
HYSTERIA2
INNER
SSH
)
type NetWork int
@ -83,6 +84,8 @@ func (t Type) String() string {
return "Hysteria2"
case INNER:
return "Inner"
case SSH:
return "SSH"
default:
return "Unknown"
}
@ -117,6 +120,7 @@ func ParseType(t string) (*Type, error) {
res = HYSTERIA2
case "INNER":
res = INNER
default:
return nil, fmt.Errorf("unknown type: %s", t)
}
@ -155,8 +159,13 @@ type Metadata struct {
RawDstAddr net.Addr `json:"-"`
// Only domain rule
SniffHost string `json:"sniffHost"`
HitRule string `-`
}
func (m *Metadata) GeoedIp() bool {
return len(m.DstGeoIP) != 0
}
func (m *Metadata) RemoteAddress() string {
return net.JoinHostPort(m.String(), strconv.FormatUint(uint64(m.DstPort), 10))
}

View file

@ -3,6 +3,7 @@ package constant
import (
"crypto/md5"
"encoding/hex"
"log"
"os"
P "path"
"path/filepath"
@ -23,20 +24,16 @@ var (
// on Unix systems, `$HOME/.config/mihomo`.
// on Windows, `%USERPROFILE%/.config/mihomo`.
var Path = func() *path {
homeDir, err := os.UserHomeDir()
if err != nil {
homeDir, _ = os.Getwd()
}
allowUnsafePath, _ := strconv.ParseBool(os.Getenv("SKIP_SAFE_PATH_CHECK"))
homeDir = P.Join(homeDir, ".config", Name)
if _, err = os.Stat(homeDir); err != nil {
if configHome, ok := os.LookupEnv("XDG_CONFIG_HOME"); ok {
homeDir = P.Join(configHome, Name)
}
ex, err := os.Executable()
if err != nil {
log.Panic(err)
}
homeDir := filepath.Dir(ex)
return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath}
homeDir = P.Join(homeDir, "config")
return &path{homeDir: homeDir, configFile: P.Join(homeDir, "config.yaml"), allowUnsafePath: allowUnsafePath}
}()
type path struct {
@ -112,7 +109,6 @@ func (p *path) MMDB() string {
}
return P.Join(p.homeDir, "geoip.metadb")
}
func (p *path) ASN() string {
files, err := os.ReadDir(p.homeDir)
if err != nil {
@ -131,7 +127,6 @@ func (p *path) ASN() string {
}
return P.Join(p.homeDir, ASNName)
}
func (p *path) OldCache() string {
return P.Join(p.homeDir, ".cache")
}
@ -190,3 +185,7 @@ func (p *path) GetExecutableFullPath() string {
res, _ := filepath.EvalSymlinks(exePath)
return res
}
func (p *path) StatisticPath() string {
return P.Join(p.homeDir, "statistic")
}

View file

@ -34,6 +34,10 @@ const (
AND
OR
NOT
RECORD
FileDI
MustResolve
ISOEmpty
)
type RuleType int
@ -104,6 +108,14 @@ func (rt RuleType) String() string {
return "OR"
case NOT:
return "NOT"
case RECORD:
return "RECORD"
case FileDI:
return "FileDI"
case MustResolve:
return "MustResolve"
case ISOEmpty:
return "ISOEmpty"
default:
return "Unknown"
}

View file

@ -8,15 +8,15 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口
allow-lan: true # 允许局域网连接
bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true'*'表示所有地址
authentication: # http,socks 入口的验证用户名,密码
authentication: # http,socks入口的验证用户名,密码
- "username:password"
skip-auth-prefixes: # 设置跳过验证的 IP
skip-auth-prefixes: # 设置跳过验证的IP段
- 127.0.0.1/8
- ::1/128
lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为 0.0.0.0/0 和::/0
lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为0.0.0.0/0和::/0
- 0.0.0.0/0
- ::/0
lan-disallowed-ips: # 禁止连接的 IP 地址段,黑名单优先级高于白名单,默认值为空
lan-disallowed-ips: # 禁止连接的 IP 地址段, 黑名单优先级高于白名单, 默认值为空
- 192.168.0.3/32
# find-process-mode has 3 values:always, strict, off
@ -58,11 +58,6 @@ external-controller: 0.0.0.0:9093 # RESTful API 监听地址
external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件
# secret: "123456" # `Authorization:Bearer ${secret}`
# RESTful API Unix socket 监听地址( windows版本大于17063也可以使用即大于等于1803/RS4版本即可使用
# !!!注意: 从Unix socket访问api接口不会验证secret 如果开启请自行保证安全问题
# 测试方法: curl -v --unix-socket "mihomo.sock" http://localhost/
external-controller-unix: mihomo.sock
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
@ -114,9 +109,9 @@ tun:
# auto-detect-interface: true # 自动识别出口网卡
# auto-route: true # 配置路由表
# mtu: 9000 # 最大传输单元
# gso: false # 启用通用分段卸载仅支持 Linux
# gso: false # 启用通用分段卸载, 仅支持 Linux
# gso-max-size: 65536 # 通用分段卸载包的最大大小
# strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问
# strict-route: true # 将所有连接路由到tun来防止泄漏但你的设备将无法其他设备被访问
inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由
- 0.0.0.0/1
- 128.0.0.0/1
@ -124,9 +119,9 @@ tun:
- "::/1"
- "8000::/1"
# endpoint-independent-nat: false # 启用独立于端点的 NAT
# include-interface: # 限制被路由的接口。默认不限制与 `exclude-interface` 冲突
# include-interface: # 限制被路由的接口。默认不限制, 与 `exclude-interface` 冲突
# - "lan0"
# exclude-interface: # 排除路由的接口与 `include-interface` 冲突
# exclude-interface: # 排除路由的接口, 与 `include-interface` 冲突
# - "lan1"
# include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto-route
# - 0
@ -148,7 +143,7 @@ tun:
# exclude-package: # 排除被路由的 Android 应用包名
# - com.android.captiveportallogin
#ebpf 配置
#ebpf配置
ebpf:
auto-redir: # redirect 模式,仅支持 TCP
- eth0
@ -205,7 +200,7 @@ tunnels: # one line config
target: target.com
proxy: proxy
# DNS 配置
# DNS配置
dns:
cache-algorithm: arc
enable: false # 关闭将使用系统 DNS
@ -213,7 +208,7 @@ dns:
listen: 0.0.0.0:53 # 开启 DNS 服务器监听
# ipv6: false # false 将返回 AAAA 的空结果
# ipv6-timeout: 300 # 单位ms内部双栈并发时向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms
# 用于解析 nameserverfallback 以及其他 DNS 服务器配置的DNS 服务域名
# 用于解析 nameserverfallback 以及其他DNS服务器配置的DNS 服务域名
# 只能使用纯 IP 地址,可使用加密 DNS
default-nameserver:
- 114.114.114.114
@ -227,12 +222,12 @@ dns:
# use-hosts: true # 查询 hosts
# 配置不使用 fake-ip 的域名
# 配置不使用fake-ip的域名
# fake-ip-filter:
# - '*.lan'
# - localhost.ptlogin2.qq.com
# DNS 主要域名配置
# DNS主要域名配置
# 支持 UDPTCPDoTDoHDoQ
# 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS
nameserver:
@ -244,7 +239,7 @@ dns:
- https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用 HTTP/3
- dhcp://en0 # dns from dhcp
- quic://dns.adguard.com:784 # DNS over QUIC
# - '8.8.8.8#en0' # 兼容指定 DNS 出口网卡
# - '8.8.8.8#en0' # 兼容指定DNS出口网卡
# 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN非必要配置
# 当不是 CN则使用 fallback 中的 DNS 查询结果
@ -254,6 +249,7 @@ dns:
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
# 专用于节点域名解析的 DNS 服务器,非必要配置项
# 配置服务器若查询失败将使用 nameserver非并发查询
# proxy-server-nameserver:
# - https://dns.google/dns-query
# - tls://one.one.one.one
@ -342,7 +338,7 @@ proxies: # socks5
# udp-over-tcp: false
# ip-version: ipv4 # 设置节点使用 IP 版本可选dualipv4ipv6ipv4-preferipv6-prefer。默认使用 dual
# ipv4仅使用 IPv4 ipv6仅使用 IPv6
# ipv4-prefer优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接
# ipv4-prefer优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接,
# UDP 则为双栈解析,获取结果中的第一个 IPv4
# ipv6-prefer 同 ipv4-prefer
# 现有协议都支持此参数TCP 效果仅在开启 tcp-concurrent 生效
@ -354,7 +350,7 @@ proxies: # socks5
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
# padding: false # Enable padding. Requires sing-box server version 1.3-beta9 or later.
# statistic: false # 控制是否将底层连接显示在面板中,方便打断底层连接
# only-tcp: false # 如果设置为 true, smux 的设置将不会对 udp 生效udp 连接会直接走底层协议
# only-tcp: false # 如果设置为true, smux的设置将不会对udp生效udp连接会直接走底层协议
- name: "ss2"
type: ss
@ -387,7 +383,6 @@ proxies: # socks5
# headers:
# custom: value
# v2ray-http-upgrade: false
# v2ray-http-upgrade-fast-open: false
- name: "ss4-shadow-tls"
type: ss
@ -410,18 +405,18 @@ proxies: # socks5
password: [YOUR_SS_PASSWORD]
client-fingerprint:
chrome # One of: chrome, ios, firefox or safari
# 可以是 chrome, ios, firefox, safari 中的一个
# 可以是chrome, ios, firefox, safari中的一个
plugin: restls
plugin-opts:
host:
"www.microsoft.com" # Must be a TLS 1.3 server
# 应当是一个 TLS 1.3 服务器
# 应当是一个TLS 1.3 服务器
password: [YOUR_RESTLS_PASSWORD]
version-hint: "tls13"
# Control your post-handshake traffic through restls-script
# Hide proxy behaviors like "tls in tls".
# see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md
# 用 restls 剧本来控制握手后的行为,隐藏"tls in tls"等特征
# 用restls剧本来控制握手后的行为隐藏"tls in tls"等特征
# 详情https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md
restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100"
@ -433,18 +428,18 @@ proxies: # socks5
password: [YOUR_SS_PASSWORD]
client-fingerprint:
chrome # One of: chrome, ios, firefox or safari
# 可以是 chrome, ios, firefox, safari 中的一个
# 可以是chrome, ios, firefox, safari中的一个
plugin: restls
plugin-opts:
host:
"vscode.dev" # Must be a TLS 1.2 server
# 应当是一个 TLS 1.2 服务器
# 应当是一个TLS 1.2 服务器
password: [YOUR_RESTLS_PASSWORD]
version-hint: "tls12"
restls-script: "1000?100<1,500~100,350~100,600~100,400~200"
# vmess
# cipher 支持 auto/aes-128-gcm/chacha20-poly1305/none
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none
- name: "vmess"
type: vmess
server: server
@ -466,7 +461,6 @@ proxies: # socks5
# max-early-data: 2048
# early-data-header-name: Sec-WebSocket-Protocol
# v2ray-http-upgrade: false
# v2ray-http-upgrade-fast-open: false
- name: "vmess-h2"
type: vmess
@ -595,7 +589,6 @@ proxies: # socks5
headers:
Host: example.com
# v2ray-http-upgrade: false
# v2ray-http-upgrade-fast-open: false
# Trojan
- name: "trojan"
@ -611,10 +604,6 @@ proxies: # socks5
# - h2
# - http/1.1
# skip-cert-verify: true
# ss-opts: # like trojan-go's `shadowsocks` config
# enabled: false
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
# password: "example"
- name: trojan-grpc
server: server
@ -644,7 +633,6 @@ proxies: # socks5
# headers:
# Host: example.com
# v2ray-http-upgrade: false
# v2ray-http-upgrade-fast-open: false
- name: "trojan-xtls"
type: trojan
@ -686,13 +674,11 @@ proxies: # socks5
type: hysteria2
server: server.com
port: 443
# ports: 1000,2000-3000,5000 # port 不可省略
# hop-interval: 15
# up 和 down 均不写或为 0 则使用 BBR 流控
# up和down均不写或为0则使用BBR流控
# up: "30 Mbps" # 若不写单位,默认为 Mbps
# down: "200 Mbps" # 若不写单位,默认为 Mbps
password: yourpassword
# obfs: salamander # 默认为空,如果填写则开启 obfs目前仅支持 salamander
# obfs: salamander # 默认为空,如果填写则开启obfs目前仅支持salamander
# obfs-password: yourpassword
# sni: server.com
# skip-cert-verify: false
@ -718,12 +704,14 @@ proxies: # socks5
# reserved: [209,98,59]
# 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接
# dialer-proxy: "ss1"
# remote-dns-resolve: true # 强制 dns 远程解析,默认值为 false
# dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在 remote-dns-resolve 为 true 时生效
# 如果 peers 不为空,该段落中的 allowed-ips 不可为空;前面段落的 server,port,public-key,pre-shared-key 均会被忽略,但 private-key 会被保留且只能在顶层指定
# remote-dns-resolve: true # 强制dns远程解析默认值为false
# dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效
# 如果peers不为空该段落中的allowed-ips不可为空前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略但private-key会被保留且只能在顶层指定
# peers:
# - server: 162.159.192.1
# port: 2480
# ip: 172.16.0.2
# ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5
# public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
# # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=
# allowed-ips: ['0.0.0.0/0']
@ -734,9 +722,9 @@ proxies: # socks5
server: www.example.com
port: 10443
type: tuic
# tuicV4 必须填写 token不可同时填写 uuid 和 password
# tuicV4必须填写token 不可同时填写uuid和password
token: TOKEN
# tuicV5 必须填写 uuid 和 password不可同时填写 token
# tuicV5必须填写uuid和password不可同时填写token
uuid: 00000000-0000-0000-0000-000000000001
password: PASSWORD_1
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
@ -754,8 +742,8 @@ proxies: # socks5
# max-open-streams: 20 # default 100, too many open streams may hurt performance
# sni: example.com
#
# meta 和 sing-box 私有扩展,将 ss-uot 用于 udp 中继,开启此选项后 udp-relay-mode 将失效
# 警告,与原版 tuic 不兼容!!!
# meta和sing-box私有扩展将ss-uot用于udp中继开启此选项后udp-relay-mode将失效
# 警告,与原版tuic不兼容
# udp-over-stream: false
# udp-over-stream-version: 1
@ -779,21 +767,9 @@ proxies: # socks5
# protocol-param: "#"
# udp: true
- name: "ssh-out"
type: ssh
server: 127.0.0.1
port: 22
username: root
password: password
privateKey: path
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
- name: "dns-out"
type: dns
proxy-groups:
# 代理链,目前 relay 可以支持 udp 的只有 vmess/vless/trojan/ss/ssr/tuic
# wireguard 目前不支持在 relay 中使用,请使用 proxy 中的 dialer-proxy 配置项
# 代理链目前relay可以支持udp的只有vmess/vless/trojan/ss/ssr/tuic
# wireguard目前不支持在relay中使用请使用proxy中的dialer-proxy配置项
# Traffic: mihomo <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
- name: "relay"
type: relay
@ -867,19 +843,10 @@ proxy-groups:
# Mihomo 格式的节点或支持 *ray 的分享格式
proxy-providers:
provider1:
type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5
type: http # http 的 path 可空置,默认储存路径为 homedir的proxies文件夹,文件名为url的md5
url: "url"
interval: 3600
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到任意位置添加环境变量 SKIP_SAFE_PATH_CHECK=1
proxy: DIRECT
header:
User-Agent:
- "Clash/v1.18.0"
- "mihomo/1.18.3"
# Accept:
# - 'application/vnd.github.v3.raw'
# Authorization:
# - 'token 1231231'
health-check:
enable: true
interval: 600
@ -895,8 +862,6 @@ proxy-providers:
# interface-name: tailscale0
# routing-mark: 233
# ip-version: ipv4-prefer
# additional-prefix: "[provider1]"
# additional-suffix: "test"
test:
type: file
path: /test.yaml
@ -909,9 +874,8 @@ rule-providers:
behavior: classical # domain ipcidr
interval: 259200
path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到任意位置添加环境变量 SKIP_SAFE_PATH_CHECK=1
type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
type: http # http 的 path 可空置,默认储存路径为 homedir的rules文件夹,文件名为url的md5
url: "url"
proxy: DIRECT
rule2:
behavior: classical
interval: 259200
@ -919,8 +883,6 @@ rule-providers:
type: file
rules:
- RULE-SET,rule1,REJECT
- IP-ASN,1,PROXY
- DOMAIN-REGEX,^abc,DIRECT
- DOMAIN-SUFFIX,baidu.com,DIRECT
- DOMAIN-KEYWORD,google,ss1
- IP-CIDR,1.1.1.1/32,ss1
@ -960,7 +922,7 @@ listeners:
port: 10808
#listen: 0.0.0.0 # 默认监听 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理
# udp: false # 默认 true
- name: http-in-1
@ -968,14 +930,14 @@ listeners:
port: 10809
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错)
- name: mixed-in-1
type: mixed # HTTP(S) 和 SOCKS 代理混合
port: 10810
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错)
# udp: false # 默认 true
- name: reidr-in-1
@ -983,14 +945,14 @@ listeners:
port: 10811
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错)
- name: tproxy-in-1
type: tproxy
port: 10812
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错)
# udp: false # 默认 true
- name: shadowsocks-in-1
@ -998,7 +960,7 @@ listeners:
port: 10813
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错)
password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=
cipher: 2022-blake3-aes-256-gcm
@ -1007,13 +969,13 @@ listeners:
port: 10814
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错)
users:
- username: 1
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
alterId: 1
# ws-path: "/" # 如果不为空则开启 websocket 传输层
# 下面两项如果填写则开启 tls需要同时填写
# ws-path: "/" # 如果不为空则开启websocket传输层
# 下面两项如果填写则开启tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
@ -1022,10 +984,10 @@ listeners:
port: 10815
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# token: # tuicV4 填写(可以同时填写 users
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错)
# token: # tuicV4填写可以同时填写users
# - TOKEN
# users: # tuicV5 填写(可以同时填写 token
# users: # tuicV5填写可以同时填写token
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
# certificate: ./server.crt
@ -1042,25 +1004,25 @@ listeners:
port: 10816
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错)
network: [tcp, udp]
target: target.com
- name: tun-in-1
type: tun
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错)
stack: system # gvisor / mixed
dns-hijack:
- 0.0.0.0:53 # 需要劫持的 DNS
# auto-detect-interface: false # 自动识别出口网卡
# auto-route: false # 配置路由表
# mtu: 9000 # 最大传输单元
inet4-address: # 必须手动设置 ipv4 地址段
inet4-address: # 必须手动设置ipv4地址段
- 198.19.0.1/30
inet6-address: # 必须手动设置 ipv6 地址段
inet6-address: # 必须手动设置ipv6地址段
- "fdfe:dcba:9877::1/126"
# strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问
# strict-route: true # 将所有连接路由到tun来防止泄漏但你的设备将无法其他设备被访问
# inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由
# - 0.0.0.0/1
# - 128.0.0.0/1
@ -1088,17 +1050,17 @@ listeners:
# exclude-package: # 排除被路由的 Android 应用包名
# - com.android.captiveportallogin
# 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理
# shadowsocks,vmess 入口配置(传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理)
# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
# vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345
# tuic 服务器入口(传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理)
# tuic服务器入口传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理
# tuic-server:
# enable: true
# listen: 127.0.0.1:10443
# token: # tuicV4 填写(可以同时填写 users
# token: # tuicV4填写可以同时填写users
# - TOKEN
# users: # tuicV5 填写(可以同时填写 token
# users: # tuicV5填写可以同时填写token
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
# certificate: ./server.crt

10
go.mod
View file

@ -9,12 +9,14 @@ require (
github.com/cilium/ebpf v0.12.3
github.com/coreos/go-iptables v0.7.0
github.com/dlclark/regexp2 v1.11.0
github.com/gliderlabs/ssh v0.3.7
github.com/go-chi/chi/v5 v5.0.12
github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.3
github.com/gobwas/ws v1.4.0
github.com/gofrs/uuid/v5 v5.2.0
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49
github.com/kardianos/service v1.2.2
github.com/klauspost/cpuid/v2 v2.2.7
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.2
@ -32,6 +34,7 @@ require (
github.com/mroth/weightedrand/v2 v2.1.0
github.com/openacid/low v0.1.21
github.com/oschwald/maxminddb-golang v1.12.0
github.com/pires/go-proxyproto v0.7.0
github.com/puzpuzpuz/xsync/v3 v3.1.0
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
@ -53,6 +56,7 @@ require (
golang.org/x/sync v0.7.0
golang.org/x/sys v0.20.0
google.golang.org/protobuf v1.34.1
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.3.0
)
@ -62,6 +66,7 @@ require (
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
@ -71,6 +76,7 @@ require (
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gaukas/godicttls v0.0.4 // indirect
github.com/go-co-op/gocron v1.37.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
@ -78,6 +84,7 @@ require (
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect
@ -92,7 +99,9 @@ require (
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/sagernet/utls v1.5.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
@ -103,6 +112,7 @@ require (
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/text v0.15.0 // indirect

35
go.sum
View file

@ -11,6 +11,8 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
@ -25,6 +27,7 @@ github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vc
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=
@ -44,12 +47,16 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
@ -75,6 +82,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -84,12 +93,20 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
@ -142,6 +159,9 @@ github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq5
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -154,6 +174,10 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
@ -165,6 +189,8 @@ github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnV
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co=
github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
@ -192,6 +218,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
@ -214,6 +241,8 @@ github.com/zhangyunhao116/fastrand v0.4.0 h1:86QB6Y+GGgLZRFRDCjMmAS28QULwspK9sgL
github.com/zhangyunhao116/fastrand v0.4.0/go.mod h1:vIyo6EyBhjGKpZv6qVlkPl4JVAklpMM4DSKzbAkMguA=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
@ -244,6 +273,7 @@ golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -271,6 +301,11 @@ google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFW
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -91,6 +91,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
}
updateUsers(cfg.Users)
updateUsersTls(cfg.TlsUser)
updateProxies(cfg.Proxies, cfg.Providers)
updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders)
updateSniffer(cfg.Sniffer)
@ -98,7 +99,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateGeneral(cfg.General)
updateNTP(cfg.NTP)
updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6)
updateListeners(cfg.General, cfg.Listeners, force)
updateListeners(cfg.General, cfg.Listeners, force, cfg.WanInput)
updateIPTables(cfg)
updateTun(cfg.General)
updateExperimental(cfg)
@ -114,7 +115,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
tunnel.OnRunning()
hcCompatibleProvider(cfg.Providers)
log.SetLevel(cfg.General.LogLevel)
log.SetLevel(cfg.General.Log.Level)
}
func initInnerTcp() {
@ -163,7 +164,8 @@ func GetGeneral() *config.General {
return general
}
func updateListeners(general *config.General, listeners map[string]C.InboundListener, force bool) {
func updateListeners(general *config.General, listeners map[string]C.InboundListener, force bool,
wanInput *inbound.WanInput) {
listener.PatchInboundListeners(listeners, tunnel.Tunnel, true)
if !force {
return
@ -188,6 +190,10 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tunnel.Tunnel)
listener.ReCreateVmess(general.VmessConfig, tunnel.Tunnel)
listener.ReCreateTuic(general.TuicServer, tunnel.Tunnel)
if wanInput.Port != 0 {
listener.ReCreateMixedTls(wanInput, tunnel.Tunnel)
}
}
func updateExperimental(c *config.Config) {
@ -419,6 +425,14 @@ func updateUsers(users []auth.AuthUser) {
}
}
func updateUsersTls(users []auth.AuthUser) {
authenticator := auth.NewAuthenticator(users)
authStore.SetAuthenticatorTls(authenticator)
if authenticator != nil {
log.Infoln("Authentication tls of local server updated")
}
}
func updateProfile(cfg *config.Config) {
profileCfg := cfg.Profile

View file

@ -50,7 +50,7 @@ func Parse(options ...Option) error {
if cfg.General.ExternalController != "" {
go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS,
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG)
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, true)
}
if cfg.General.ExternalControllerUnix != "" {

View file

@ -4,11 +4,11 @@ import (
"net/http"
"net/netip"
"path/filepath"
"sync"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/hub/executor"
@ -21,6 +21,11 @@ import (
"github.com/go-chi/render"
)
var (
updateGeoMux sync.Mutex
updatingGeo = false
)
func configRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getConfigs)
@ -364,25 +369,40 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
}
func updateGeoDatabases(w http.ResponseWriter, r *http.Request) {
err := updater.UpdateGeoDatabases()
if err != nil {
log.Errorln("[REST-API] update GEO databases failed: %v", err)
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, newError(err.Error()))
updateGeoMux.Lock()
if updatingGeo {
updateGeoMux.Unlock()
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("updating..."))
return
}
cfg, err := executor.ParseWithPath(C.Path.Config())
if err != nil {
log.Errorln("[REST-API] update GEO databases failed: %v", err)
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, newError("Error parsing configuration"))
return
}
updatingGeo = true
updateGeoMux.Unlock()
log.Warnln("[GEO] update GEO databases success, applying config")
go func() {
defer func() {
updatingGeo = false
}()
executor.ApplyConfig(cfg, false)
log.Warnln("[REST-API] updating GEO databases...")
if err := config.UpdateGeoDatabases(); err != nil {
log.Errorln("[REST-API] update GEO databases failed: %v", err)
return
}
cfg, err := executor.ParseWithPath(C.Path.Config())
if err != nil {
log.Errorln("[REST-API] update GEO databases failed: %v", err)
return
}
log.Warnln("[REST-API] update GEO databases successful, apply config...")
executor.ApplyConfig(cfg, false)
}()
render.NoContent(w, r)
}

View file

@ -25,8 +25,8 @@ func connectionRouter() http.Handler {
func getConnections(w http.ResponseWriter, r *http.Request) {
if !(r.Header.Get("Upgrade") == "websocket") {
snapshot := statistic.DefaultManager.Snapshot()
render.JSON(w, r, snapshot)
snapshots := statistic.Snapshots()
render.JSON(w, r, snapshots)
return
}
@ -51,8 +51,8 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
buf := &bytes.Buffer{}
sendSnapshot := func() error {
buf.Reset()
snapshot := statistic.DefaultManager.Snapshot()
if err := json.NewEncoder(buf).Encode(snapshot); err != nil {
snapshots := statistic.Snapshots()
if err := json.NewEncoder(buf).Encode(snapshots); err != nil {
return err
}
@ -74,16 +74,23 @@ func getConnections(w http.ResponseWriter, r *http.Request) {
func closeConnection(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
if c := statistic.DefaultManager.Get(id); c != nil {
_ = c.Close()
here:
for _, manager := range statistic.ChannelManager {
if c := manager.Get(id); c != nil {
_ = c.Close()
break here
}
}
render.NoContent(w, r)
}
func closeAllConnections(w http.ResponseWriter, r *http.Request) {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
_ = c.Close()
return true
})
for _, v := range statistic.ChannelManager {
v.Range(func(c statistic.Tracker) bool {
_ = c.Close()
return true
})
}
render.NoContent(w, r)
}

View file

@ -5,9 +5,16 @@ import (
"crypto/subtle"
"crypto/tls"
"encoding/json"
"fmt"
S "github.com/kardianos/service"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/outboundgroup"
cs "github.com/metacubex/mihomo/service"
"github.com/metacubex/mihomo/tunnel"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime/debug"
"strings"
@ -89,8 +96,9 @@ func router(isDebug bool, withAuth bool) *chi.Mux {
r.Mount("/providers/rules", ruleProviderRouter())
r.Mount("/cache", cacheRouter())
r.Mount("/dns", dnsRouter())
r.Mount("/restart", restartRouter())
r.Mount("/upgrade", upgradeRouter())
//r.Mount("/restart", restartRouter())
//r.Mount("/upgrade", upgradeRouter())
r.Get("/reboot", reboot)
addExternalRouters(r)
})
@ -106,7 +114,6 @@ func router(isDebug bool, withAuth bool) *chi.Mux {
}
return r
}
func Start(addr string, tlsAddr string, secret string,
certificate, privateKey string, isDebug bool) {
if serverAddr != "" {
@ -157,7 +164,6 @@ func Start(addr string, tlsAddr string, secret string,
}
}
func StartUnix(addr string, isDebug bool) {
addr = C.Path.Resolve(addr)
@ -190,7 +196,6 @@ func StartUnix(addr string, isDebug bool) {
log.Errorln("External controller unix serve error: %s", err)
}
}
func setPrivateNetworkAccess(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
@ -261,16 +266,41 @@ func traffic(w http.ResponseWriter, r *http.Request) {
tick := time.NewTicker(time.Second)
defer tick.Stop()
t := statistic.DefaultManager
cs := statistic.ChannelManager
buf := &bytes.Buffer{}
proxies := tunnel.Proxies()
selectors := make([]*outboundgroup.Selector, 0)
for _, proxy := range proxies {
if adapterP, ok := proxy.(*adapter.Proxy); ok {
adapter := adapterP.ProxyAdapter
if adapterP.Type() == C.Selector {
selector := adapter.(*outboundgroup.Selector)
selectors = append(selectors, selector)
}
}
}
var err error
for range tick.C {
buf.Reset()
up, down := t.Now()
if err := json.NewEncoder(buf).Encode(Traffic{
Up: up,
Down: down,
}); err != nil {
var traffics map[string]Traffic = make(map[string]Traffic)
for _, s := range selectors {
id := s.Now()
if _, ok := traffics[id]; ok {
continue
}
if v, ok := cs[id]; ok {
up, down := v.Now()
traffic := Traffic{
Up: up,
Down: down,
}
traffics[id] = traffic
}
}
if err := json.NewEncoder(buf).Encode(traffics); err != nil {
break
}
@ -304,26 +334,23 @@ func memory(w http.ResponseWriter, r *http.Request) {
tick := time.NewTicker(time.Second)
defer tick.Stop()
t := statistic.DefaultManager
buf := &bytes.Buffer{}
var err error
first := true
for range tick.C {
buf.Reset()
inuse := t.Memory()
// make chat.js begin with zero
// this is shit var,but we need output 0 for first time
if first {
inuse = 0
first = false
stat, err := statistic.Processor.MemoryInfo()
if err != nil {
return
}
inuse := stat.RSS
if err := json.NewEncoder(buf).Encode(Memory{
Inuse: inuse,
OSLimit: 0,
Inuse: inuse,
}); err != nil {
break
}
if wsConn == nil {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
@ -445,3 +472,39 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
func version(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, render.M{"meta": C.Meta, "version": C.Version})
}
func reboot(w http.ResponseWriter, r *http.Request) {
if cs.IsOpenWrt() {
confPath := "/etc/init.d/" + cs.ServiceName
cmd := exec.Command(confPath, "restart")
err := cmd.Start()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "%v", err)
return
}
w.WriteHeader(http.StatusOK)
return
}
status, _ := cs.Srv.Status()
if status == S.StatusUnknown {
w.WriteHeader(http.StatusServiceUnavailable)
} else {
execPath, err := os.Executable()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "%v", err)
return
}
cmd := exec.Command(execPath, "-s", "restart")
err = cmd.Start()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "%v", err)
return
}
w.WriteHeader(http.StatusOK)
}
}

View file

@ -6,7 +6,8 @@ import (
"net/http"
"os"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/hub/updater"
"github.com/metacubex/mihomo/log"
"github.com/go-chi/chi/v5"
@ -17,7 +18,6 @@ func upgradeRouter() http.Handler {
r := chi.NewRouter()
r.Post("/", upgradeCore)
r.Post("/ui", updateUI)
r.Post("/geo", updateGeoDatabases)
return r
}
@ -31,7 +31,7 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
return
}
err = updater.UpdateCore(execPath)
err = updater.Update(execPath)
if err != nil {
log.Warnln("%s", err)
render.Status(r, http.StatusInternalServerError)
@ -48,9 +48,9 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
}
func updateUI(w http.ResponseWriter, r *http.Request) {
err := updater.UpdateUI()
err := config.UpdateUI()
if err != nil {
if errors.Is(err, updater.ErrIncompleteConf) {
if errors.Is(err, config.ErrIncompleteConf) {
log.Warnln("%s", err)
render.Status(r, http.StatusNotImplemented)
render.JSON(w, r, newError(fmt.Sprintf("%s", err)))

View file

@ -0,0 +1,67 @@
package updater
import (
"fmt"
"io"
"golang.org/x/exp/constraints"
)
// LimitReachedError records the limit and the operation that caused it.
type LimitReachedError struct {
Limit int64
}
// Error implements the [error] interface for *LimitReachedError.
//
// TODO(a.garipov): Think about error string format.
func (lre *LimitReachedError) Error() string {
return fmt.Sprintf("attempted to read more than %d bytes", lre.Limit)
}
// limitedReader is a wrapper for [io.Reader] limiting the input and dealing
// with errors package.
type limitedReader struct {
r io.Reader
limit int64
n int64
}
// Read implements the [io.Reader] interface.
func (lr *limitedReader) Read(p []byte) (n int, err error) {
if lr.n == 0 {
return 0, &LimitReachedError{
Limit: lr.limit,
}
}
p = p[:Min(lr.n, int64(len(p)))]
n, err = lr.r.Read(p)
lr.n -= int64(n)
return n, err
}
// LimitReader wraps Reader to make it's Reader stop with ErrLimitReached after
// n bytes read.
func LimitReader(r io.Reader, n int64) (limited io.Reader, err error) {
if n < 0 {
return nil, &updateError{Message: "limit must be non-negative"}
}
return &limitedReader{
r: r,
limit: n,
n: n,
}, nil
}
// Min returns the smaller of x or y.
func Min[T constraints.Integer | ~string](x, y T) (res T) {
if x < y {
return x
}
return y
}

496
hub/updater/updater.go Normal file
View file

@ -0,0 +1,496 @@
package updater
import (
"archive/zip"
"compress/gzip"
"context"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
mihomoHttp "github.com/metacubex/mihomo/component/http"
"github.com/metacubex/mihomo/constant"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/klauspost/cpuid/v2"
)
// modify from https://github.com/AdguardTeam/AdGuardHome/blob/595484e0b3fb4c457f9bb727a6b94faa78a66c5f/internal/updater/updater.go
// Updater is the mihomo updater.
var (
goarm string
gomips string
amd64Compatible string
workDir string
// mu protects all fields below.
mu sync.Mutex
currentExeName string // 当前可执行文件
updateDir string // 更新目录
packageName string // 更新压缩文件
backupDir string // 备份目录
backupExeName string // 备份文件名
updateExeName string // 更新后的可执行文件
baseURL string = "https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/mihomo"
versionURL string = "https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt"
packageURL string
latestVersion string
)
func init() {
if runtime.GOARCH == "amd64" && cpuid.CPU.X64Level() < 3 {
amd64Compatible = "-compatible"
}
}
type updateError struct {
Message string
}
func (e *updateError) Error() string {
return fmt.Sprintf("update error: %s", e.Message)
}
// Update performs the auto-updater. It returns an error if the updater failed.
// If firstRun is true, it assumes the configuration file doesn't exist.
func Update(execPath string) (err error) {
mu.Lock()
defer mu.Unlock()
latestVersion, err = getLatestVersion()
if err != nil {
return err
}
log.Infoln("current version %s, latest version %s", constant.Version, latestVersion)
if latestVersion == constant.Version {
err := &updateError{Message: "already using latest version"}
return err
}
updateDownloadURL()
defer func() {
if err != nil {
log.Errorln("updater: failed: %v", err)
} else {
log.Infoln("updater: finished")
}
}()
workDir = filepath.Dir(execPath)
err = prepare(execPath)
if err != nil {
return fmt.Errorf("preparing: %w", err)
}
defer clean()
err = downloadPackageFile()
if err != nil {
return fmt.Errorf("downloading package file: %w", err)
}
err = unpack()
if err != nil {
return fmt.Errorf("unpacking: %w", err)
}
err = backup()
if err != nil {
return fmt.Errorf("backuping: %w", err)
}
err = replace()
if err != nil {
return fmt.Errorf("replacing: %w", err)
}
return nil
}
// prepare fills all necessary fields in Updater object.
func prepare(exePath string) (err error) {
updateDir = filepath.Join(workDir, "meta-update")
currentExeName = exePath
_, pkgNameOnly := filepath.Split(packageURL)
if pkgNameOnly == "" {
return fmt.Errorf("invalid PackageURL: %q", packageURL)
}
packageName = filepath.Join(updateDir, pkgNameOnly)
//log.Infoln(packageName)
backupDir = filepath.Join(workDir, "meta-backup")
if runtime.GOOS == "windows" {
updateExeName = "mihomo" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible + ".exe"
} else if runtime.GOOS == "android" && runtime.GOARCH == "arm64" {
updateExeName = "mihomo-android-arm64-v8"
} else {
updateExeName = "mihomo" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible
}
log.Infoln("updateExeName: %s ", updateExeName)
backupExeName = filepath.Join(backupDir, filepath.Base(exePath))
updateExeName = filepath.Join(updateDir, updateExeName)
log.Infoln(
"updater: updating using url: %s",
packageURL,
)
currentExeName = exePath
_, err = os.Stat(currentExeName)
if err != nil {
return fmt.Errorf("checking %q: %w", currentExeName, err)
}
return nil
}
// unpack extracts the files from the downloaded archive.
func unpack() error {
var err error
_, pkgNameOnly := filepath.Split(packageURL)
log.Infoln("updater: unpacking package")
if strings.HasSuffix(pkgNameOnly, ".zip") {
_, err = zipFileUnpack(packageName, updateDir)
if err != nil {
return fmt.Errorf(".zip unpack failed: %w", err)
}
} else if strings.HasSuffix(pkgNameOnly, ".gz") {
_, err = gzFileUnpack(packageName, updateDir)
if err != nil {
return fmt.Errorf(".gz unpack failed: %w", err)
}
} else {
return fmt.Errorf("unknown package extension")
}
return nil
}
// backup makes a backup of the current executable file
func backup() (err error) {
log.Infoln("updater: backing up current ExecFile:%s to %s", currentExeName, backupExeName)
_ = os.Mkdir(backupDir, 0o755)
err = os.Rename(currentExeName, backupExeName)
if err != nil {
return err
}
return nil
}
// replace moves the current executable with the updated one
func replace() error {
var err error
log.Infoln("replacing: %s to %s", updateExeName, currentExeName)
if runtime.GOOS == "windows" {
// rename fails with "File in use" error
err = copyFile(updateExeName, currentExeName)
} else {
err = os.Rename(updateExeName, currentExeName)
}
if err != nil {
return err
}
log.Infoln("updater: renamed: %s to %s", updateExeName, currentExeName)
return nil
}
// clean removes the temporary directory itself and all it's contents.
func clean() {
_ = os.RemoveAll(updateDir)
}
// MaxPackageFileSize is a maximum package file length in bytes. The largest
// package whose size is limited by this constant currently has the size of
// approximately 9 MiB.
const MaxPackageFileSize = 32 * 1024 * 1024
// Download package file and save it to disk
func downloadPackageFile() (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil {
return fmt.Errorf("http request failed: %w", err)
}
defer func() {
closeErr := resp.Body.Close()
if closeErr != nil && err == nil {
err = closeErr
}
}()
var r io.Reader
r, err = LimitReader(resp.Body, MaxPackageFileSize)
if err != nil {
return fmt.Errorf("http request failed: %w", err)
}
log.Debugln("updater: reading http body")
// This use of ReadAll is now safe, because we limited body's Reader.
body, err := io.ReadAll(r)
if err != nil {
return fmt.Errorf("io.ReadAll() failed: %w", err)
}
log.Debugln("updateDir %s", updateDir)
err = os.Mkdir(updateDir, 0o755)
if err != nil {
return fmt.Errorf("mkdir error: %w", err)
}
log.Debugln("updater: saving package to file %s", packageName)
err = os.WriteFile(packageName, body, 0o644)
if err != nil {
return fmt.Errorf("os.WriteFile() failed: %w", err)
}
return nil
}
// Unpack a single .gz file to the specified directory
// Existing files are overwritten
// All files are created inside outDir, subdirectories are not created
// Return the output file name
func gzFileUnpack(gzfile, outDir string) (string, error) {
f, err := os.Open(gzfile)
if err != nil {
return "", fmt.Errorf("os.Open(): %w", err)
}
defer func() {
closeErr := f.Close()
if closeErr != nil && err == nil {
err = closeErr
}
}()
gzReader, err := gzip.NewReader(f)
if err != nil {
return "", fmt.Errorf("gzip.NewReader(): %w", err)
}
defer func() {
closeErr := gzReader.Close()
if closeErr != nil && err == nil {
err = closeErr
}
}()
// Get the original file name from the .gz file header
originalName := gzReader.Header.Name
if originalName == "" {
// Fallback: remove the .gz extension from the input file name if the header doesn't provide the original name
originalName = filepath.Base(gzfile)
originalName = strings.TrimSuffix(originalName, ".gz")
}
outputName := filepath.Join(outDir, originalName)
// Create the output file
wc, err := os.OpenFile(
outputName,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
0o755,
)
if err != nil {
return "", fmt.Errorf("os.OpenFile(%s): %w", outputName, err)
}
defer func() {
closeErr := wc.Close()
if closeErr != nil && err == nil {
err = closeErr
}
}()
// Copy the contents of the gzReader to the output file
_, err = io.Copy(wc, gzReader)
if err != nil {
return "", fmt.Errorf("io.Copy(): %w", err)
}
return outputName, nil
}
// Unpack a single file from .zip file to the specified directory
// Existing files are overwritten
// All files are created inside 'outDir', subdirectories are not created
// Return the output file name
func zipFileUnpack(zipfile, outDir string) (string, error) {
zrc, err := zip.OpenReader(zipfile)
if err != nil {
return "", fmt.Errorf("zip.OpenReader(): %w", err)
}
defer func() {
closeErr := zrc.Close()
if closeErr != nil && err == nil {
err = closeErr
}
}()
if len(zrc.File) == 0 {
return "", fmt.Errorf("no files in the zip archive")
}
// Assuming the first file in the zip archive is the target file
zf := zrc.File[0]
var rc io.ReadCloser
rc, err = zf.Open()
if err != nil {
return "", fmt.Errorf("zip file Open(): %w", err)
}
defer func() {
closeErr := rc.Close()
if closeErr != nil && err == nil {
err = closeErr
}
}()
fi := zf.FileInfo()
name := fi.Name()
outputName := filepath.Join(outDir, name)
if fi.IsDir() {
return "", fmt.Errorf("the target file is a directory")
}
var wc io.WriteCloser
wc, err = os.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode())
if err != nil {
return "", fmt.Errorf("os.OpenFile(): %w", err)
}
defer func() {
closeErr := wc.Close()
if closeErr != nil && err == nil {
err = closeErr
}
}()
_, err = io.Copy(wc, rc)
if err != nil {
return "", fmt.Errorf("io.Copy(): %w", err)
}
return outputName, nil
}
// Copy file on disk
func copyFile(src, dst string) error {
d, e := os.ReadFile(src)
if e != nil {
return e
}
e = os.WriteFile(dst, d, 0o644)
if e != nil {
return e
}
return nil
}
func getLatestVersion() (version string, err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
if err != nil {
return "", fmt.Errorf("get Latest Version fail: %w", err)
}
defer func() {
closeErr := resp.Body.Close()
if closeErr != nil && err == nil {
err = closeErr
}
}()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("get Latest Version fail: %w", err)
}
content := strings.TrimRight(string(body), "\n")
return content, nil
}
func updateDownloadURL() {
var middle string
if runtime.GOARCH == "arm" && probeGoARM() {
//-linux-armv7-alpha-e552b54.gz
middle = fmt.Sprintf("-%s-%s%s-%s", runtime.GOOS, runtime.GOARCH, goarm, latestVersion)
} else if runtime.GOARCH == "arm64" {
//-linux-arm64-alpha-e552b54.gz
if runtime.GOOS == "android" {
middle = fmt.Sprintf("-%s-%s-v8-%s", runtime.GOOS, runtime.GOARCH, latestVersion)
} else {
middle = fmt.Sprintf("-%s-%s-%s", runtime.GOOS, runtime.GOARCH, latestVersion)
}
} else if isMIPS(runtime.GOARCH) && gomips != "" {
middle = fmt.Sprintf("-%s-%s-%s-%s", runtime.GOOS, runtime.GOARCH, gomips, latestVersion)
} else {
middle = fmt.Sprintf("-%s-%s%s-%s", runtime.GOOS, runtime.GOARCH, amd64Compatible, latestVersion)
}
if runtime.GOOS == "windows" {
middle += ".zip"
} else {
middle += ".gz"
}
packageURL = baseURL + middle
//log.Infoln(packageURL)
}
// isMIPS returns true if arch is any MIPS architecture.
func isMIPS(arch string) (ok bool) {
switch arch {
case
"mips",
"mips64",
"mips64le",
"mipsle":
return true
default:
return false
}
}
// linux only
func probeGoARM() (ok bool) {
cmd := exec.Command("cat", "/proc/cpuinfo")
output, err := cmd.Output()
if err != nil {
log.Errorln("probe goarm error:%s", err)
return false
}
cpuInfo := string(output)
if strings.Contains(cpuInfo, "vfpv3") || strings.Contains(cpuInfo, "vfpv4") {
goarm = "v7"
} else if strings.Contains(cpuInfo, "vfp") {
goarm = "v6"
} else {
goarm = "v5"
}
return true
}

View file

@ -5,11 +5,17 @@ import (
)
var authenticator auth.Authenticator
var authenticatorTls auth.Authenticator
func Authenticator() auth.Authenticator {
return authenticator
}
func AuthenticatorTls() auth.Authenticator {
return authenticatorTls
}
func SetAuthenticator(au auth.Authenticator) {
authenticator = au
}
func SetAuthenticatorTls(au auth.Authenticator) {
authenticatorTls = au
}

View file

@ -0,0 +1,96 @@
package mixed
import (
"io"
"net"
"time"
)
type MyConn struct {
net.Conn
buf []byte
bufLen int
localAddr net.Addr
remoteAddr net.Addr
peeked bool
}
func NewMyConn(conn net.Conn) *MyConn {
MyConn := MyConn{
Conn: conn,
localAddr: conn.LocalAddr(),
remoteAddr: conn.RemoteAddr(),
}
return &MyConn
}
func (c *MyConn) Peek(num int) ([]byte, error) {
c.peeked = true
c.bufLen = num
c.buf = make([]byte, c.bufLen)
_, err := io.ReadFull(c.Conn, c.buf)
return c.buf, err
}
func (c *MyConn) Read(b []byte) (n int, err error) {
if c.peeked {
n := copy(b, c.buf)
if n < c.bufLen {
c.bufLen -= n
c.buf = c.buf[n:]
} else {
c.peeked = false
c.buf = nil
c.bufLen = 0
}
return n, nil
} else {
if c.Conn == nil {
return 0, io.EOF
}
return c.Conn.Read(b)
}
}
func (c *MyConn) Write(b []byte) (n int, err error) {
if c.Conn != nil {
return c.Conn.Write(b)
}
return 0, io.EOF
}
func (c *MyConn) Close() error {
c.buf = nil
conn := c.Conn
c.Conn = nil
if conn != nil {
return conn.Close()
}
return nil
}
func (c *MyConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *MyConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *MyConn) SetDeadline(t time.Time) error {
if c.Conn != nil {
c.Conn.SetDeadline(t)
return nil
} else {
return io.EOF
}
}
func (c *MyConn) SetReadDeadline(t time.Time) error {
if c.Conn != nil {
c.Conn.SetReadDeadline(t)
return nil
} else {
return io.EOF
}
}

View file

@ -0,0 +1,202 @@
package mixed
import (
"bytes"
"crypto/tls"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/listener/socks"
"net"
"strconv"
"time"
N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/transport/socks5"
proxyproto "github.com/pires/go-proxyproto"
)
const recordTypeHandshake = 0x16
var sshPrefix = []byte("SSH-")
var SynDrive = []byte{0x25, 0x52, 0x18, 0x14}
var tlsConfig *tls.Config
func NewTls(addr string, wanInput *inbound.WanInput, tunnel C.Tunnel) (*Listener, error) {
if wanInput.TlsProxy != nil {
if wanInput.TlsProxy.Cert != "" && wanInput.TlsProxy.Key != "" {
certFile := wanInput.TlsProxy.Cert
keyFile := wanInput.TlsProxy.Key
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
}
}
}
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
proxyListener := &proxyproto.Listener{Listener: l}
ml := &Listener{
listener: proxyListener,
addr: addr,
// cache: cache.New(cache.WithAge(30)),
}
go func() {
for {
c, err := ml.listener.Accept()
// log.Warnln("local %s, remote %s", c.LocalAddr().String(), c.RemoteAddr().String())
if err != nil {
if ml.closed {
break
}
continue
}
tcpcon, ok := c.(*proxyproto.Conn).TCPConn()
if ok {
N.TCPKeepAlive(tcpcon)
}
myConn := NewMyConn(c)
head, err := myConn.Peek(4)
if err != nil {
c.Close()
continue
}
if head[0] == recordTypeHandshake && tlsConfig != nil {
go handleConnTls(myConn, wanInput.TlsProxy, tunnel)
} else if bytes.Equal(head, sshPrefix) {
go handleSSh(myConn, wanInput.SshProxy)
} else if bytes.Equal(head, SynDrive) {
go handleSynDrive(myConn, wanInput.Syndrive)
} else {
myConn.Close()
}
}
}()
return ml, nil
}
func handleSynDrive(conn net.Conn, syndrive *inbound.SynDrive) {
defer conn.Close()
if syndrive != nil {
conn2target(conn, syndrive.Target, false, "syn")
}
}
func handleSSh(conn net.Conn, sshProxy *inbound.SSHProxy) {
defer conn.Close()
if sshProxy != nil {
conn2target(conn, sshProxy.Target, sshProxy.ProxyProto, "ssh")
} else {
sshServer.HandleConn(conn)
}
}
func handleConnTls(conn net.Conn, tlsProxy *inbound.TLSProxy, tunnel C.Tunnel) {
tlsConn := tls.Server(conn, tlsConfig)
err := tlsConn.Handshake()
if err != nil {
tlsConn.Close()
return
}
myConn := NewMyConn(tlsConn)
head, err := myConn.Peek(1)
if err != nil {
myConn.Close()
return
}
if head[0] == socks5.Version {
socks.HandleSocks5Tls(myConn, tunnel)
} else {
defer myConn.Close()
connectionState := tlsConn.ConnectionState()
domain := connectionState.ServerName
target, ok := tlsProxy.TlsTargets[domain]
if ok {
conn2target(myConn, target.Target, target.ProxyProto, "tls")
} else {
target, ok := tlsProxy.TlsTargets["all"]
if ok {
conn2target(myConn, target.Target, target.ProxyProto, "tls")
}
}
}
}
func conn2target(conn net.Conn, target string, useProxyProto bool, protoType string) {
cc, err := net.DialTimeout("tcp", target, 5*time.Second)
if err != nil {
log.Errorln("%s -> %v", conn.RemoteAddr().String(), err)
return
}
defer cc.Close()
if useProxyProto {
shost, sport, err := net.SplitHostPort(conn.RemoteAddr().String())
if err != nil {
log.Errorln("%s -> %v", conn.RemoteAddr().String(), err)
return
}
siport, err := strconv.Atoi(sport)
if err != nil {
log.Errorln("%s -> %v", conn.RemoteAddr().String(), err)
return
}
lhost, lport, err := net.SplitHostPort(conn.LocalAddr().String())
if err != nil {
log.Errorln("%s -> %v", conn.RemoteAddr().String(), err)
return
}
liport, err := strconv.Atoi(lport)
if err != nil {
log.Errorln("%s -> %v", conn.RemoteAddr().String(), err)
return
}
remoteIp := net.ParseIP(shost)
tranPro := proxyproto.TCPv6
if remoteIp.To4() != nil {
tranPro = proxyproto.TCPv4
}
header := &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: tranPro,
SourceAddr: &net.TCPAddr{
IP: remoteIp,
Port: siport,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP(lhost),
Port: liport,
},
}
_, err = header.WriteTo(cc)
if err != nil {
log.Errorln("%s -> %v", conn.RemoteAddr().String(), err)
return
}
}
log.Infoln("%s %s -> %v", protoType, conn.RemoteAddr().String(), target)
N.Relay(conn, cc)
}

210
listener/mixed/sshd.go Normal file
View file

@ -0,0 +1,210 @@
package mixed
import (
"crypto/rand"
"crypto/rsa"
"github.com/gliderlabs/ssh"
"github.com/metacubex/mihomo/adapter/inbound"
C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth"
log "github.com/sirupsen/logrus"
gossh "golang.org/x/crypto/ssh"
"net"
"time"
)
var sshServer ssh.Server
type sshConn struct {
net.Conn
closeCallback func()
ctx ssh.Context
}
var (
DeadlineTimeout = 30 * time.Second
IdleTimeout = 10 * time.Second
tunnel C.Tunnel
)
func InitSShServer(tunnel_ C.Tunnel) {
tunnel = tunnel_
sshServer = ssh.Server{
PasswordHandler: passwordHandler,
ConnectionFailedCallback: sshConnectionFailed,
LocalPortForwardingCallback: ssh.LocalPortForwardingCallback(func(ctx ssh.Context, dhost string, dport uint32) bool {
//log.Println("Accepted forward", dhost, dport)
return true
}),
ChannelHandlers: map[string]ssh.ChannelHandler{
"direct-tcpip": DirectTCPIPHandler,
"session": SessionHandler,
},
PtyCallback: func(ctx ssh.Context, pty ssh.Pty) bool {
return false
},
//MaxTimeout: DeadlineTimeout,
//IdleTimeout: IdleTimeout,
}
//if len(sshServer.HostSigners) == 0 {
// signer, err := generateSigner()
// if err != nil {
//
// log.Panicf("%v", err)
// }
// sshServer.HostSigners = append(sshServer.HostSigners, signer)
//}
sshServer.SetOption(HostKeyFile())
}
func passwordHandler(ctx ssh.Context, password string) bool {
author := authStore.AuthenticatorTls()
if author == nil {
return true
}
if author.Verify(ctx.User(), password) {
return true
}
return false
}
func sshConnectionFailed(conn net.Conn, err error) {
// Log the underlying error with a specific message
log.Warnf("ssh: Failed connection from %s with error: %v", conn.RemoteAddr(), err)
}
func DirectTCPIPHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) {
d := inbound.LocalForwardChannelData{}
if err := gossh.Unmarshal(newChan.ExtraData(), &d); err != nil {
newChan.Reject(gossh.ConnectionFailed, "error parsing forward data: "+err.Error())
return
}
if srv.LocalPortForwardingCallback == nil || !srv.LocalPortForwardingCallback(ctx, d.DestAddr, d.DestPort) {
newChan.Reject(gossh.Prohibited, "port forwarding is disabled")
return
}
//dest := net.JoinHostPort(d.DestAddr, strconv.FormatInt(int64(d.DestPort), 10))
//var dialer net.Dialer
//dconn, err := dialer.DialContext(ctx, "tcp", dest)
//if err != nil {
// newChan.Reject(gossh.ConnectionFailed, err.Error())
// return
//}
ch, reqs, err := newChan.Accept()
if err != nil {
//dconn.Close()
return
}
//go gossh.DiscardRequests(reqs)
breakout := make(chan struct{})
go func() {
for {
select {
case req := <-reqs:
if req != nil && req.WantReply {
req.Reply(false, nil)
}
case <-breakout:
return
}
}
}()
//go func() {
// defer ch.Close()
// defer dconn.Close()
// io.Copy(ch, dconn)
//}()
//go func() {
// defer ch.Close()
// defer dconn.Close()
// io.Copy(dconn, ch)
//}()
sshConn := inbound.MySSHConn{
Channel: ch,
LocalAddr_: conn.LocalAddr(),
RemoteAddr_: conn.RemoteAddr(),
}
metadata := inbound.NewSSH(&d, &sshConn, C.SSH)
if !metadata.Valid() {
log.Warnln("ssh not valid: %#v", newChan.ExtraData())
}
go func(breakout chan<- struct{}) {
defer sshConn.Close()
defer func() {
breakout <- struct{}{}
}()
tunnel.HandleTCPConn(&sshConn, metadata)
}(breakout)
}
func SessionHandler(srv *ssh.Server, conn *gossh.ServerConn, newChan gossh.NewChannel, ctx ssh.Context) {
defer func() {
conn.Close()
}()
ch, _, err := newChan.Accept()
if err != nil {
return
}
defer func() {
ch.Close()
}()
var chars [1]byte
for {
_, err := ch.Read(chars[:])
if err != nil {
break
}
_, err = ch.Write(chars[:])
if err != nil {
break
}
//log.Infof("%d", chars[0])
if chars[0] == 3 || chars[0] == 26 { //ctr-c == 3 ctr-z == 26
break
}
}
}
var ed25519_key = `
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACCBH8jSvR0eyMdieVjyup2TKrtaCbB2WZzzYGKxdGLISQAAAKAVISnTFSEp
0wAAAAtzc2gtZWQyNTUxOQAAACCBH8jSvR0eyMdieVjyup2TKrtaCbB2WZzzYGKxdGLISQ
AAAEDl+FO3qPfVkYDbrC94EapwmxOYOzAHpRSz0bueb7dWI4EfyNK9HR7Ix2J5WPK6nZMq
u1oJsHZZnPNgYrF0YshJAAAAHHJvb3RAaVpicDE4dDI5bTNvNjE4OTg4b3RibloB
-----END OPENSSH PRIVATE KEY-----
`
func HostKeyFile() ssh.Option {
return func(srv *ssh.Server) error {
signer, err := gossh.ParsePrivateKey([]byte(ed25519_key))
if err != nil {
return err
}
srv.AddHostKey(signer)
return nil
}
}
func generateSigner() (ssh.Signer, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
return gossh.NewSignerFromKey(key)
}

View file

@ -59,23 +59,15 @@ func CalculateInterfaceName(name string) (tunName string) {
if err != nil {
return
}
tunIndex := 0
indexSet := make(map[int]struct{})
var tunIndex int
for _, netInterface := range interfaces {
if strings.HasPrefix(netInterface.Name, tunName) {
index, parseErr := strconv.ParseInt(netInterface.Name[len(tunName):], 10, 16)
if parseErr == nil {
indexSet[int(index)] = struct{}{}
tunIndex = int(index) + 1
}
}
}
for index := range indexSet {
if index == tunIndex {
tunIndex += 1
} else { // indexSet already sorted and distinct, so this tunIndex nobody used
break
}
}
tunName = F.ToString(tunName, tunIndex)
return
}

View file

@ -98,12 +98,11 @@ func HandleSocks4(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) {
authenticator = nil
}
addr, _, user, err := socks4.ServerHandshake(conn, authenticator)
addr, _, _, err := socks4.ServerHandshake(conn, authenticator)
if err != nil {
conn.Close()
return
}
additions = append(additions, inbound.WithInUser(user))
tunnel.HandleTCPConn(inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4, additions...))
}
@ -112,7 +111,7 @@ func HandleSocks5(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
if inbound.SkipAuthRemoteAddr(conn.RemoteAddr()) {
authenticator = nil
}
target, command, user, err := socks5.ServerHandshake(conn, authenticator)
target, command, _, err := socks5.ServerHandshake(conn, authenticator)
if err != nil {
conn.Close()
return
@ -122,6 +121,19 @@ func HandleSocks5(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition)
io.Copy(io.Discard, conn)
return
}
additions = append(additions, inbound.WithInUser(user))
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.SOCKS5, additions...))
}
func HandleSocks5Tls(conn net.Conn, tunnel C.Tunnel) {
target, command, _, err := socks5.ServerHandshake(conn, authStore.AuthenticatorTls())
if err != nil {
conn.Close()
return
}
if command == socks5.CmdUDPAssociate {
defer conn.Close()
io.Copy(io.Discard, conn)
return
}
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.SOCKS5))
}

40
listener/tls_listener.go Normal file
View file

@ -0,0 +1,40 @@
package listener
import (
"github.com/metacubex/mihomo/adapter/inbound"
C "github.com/metacubex/mihomo/constant"
"strconv"
"github.com/metacubex/mihomo/listener/mixed"
"github.com/metacubex/mihomo/log"
)
func ReCreateMixedTls(wanInput *inbound.WanInput, tunnel C.Tunnel) {
var err error
defer func() {
if err != nil {
log.Errorln("Start tls server error: %s", err.Error())
}
}()
addr := ":" + strconv.Itoa(wanInput.Port)
if portIsZero(addr) {
return
}
//mixedListener, err = mixed.New(addr, tcpIn)
mixed.InitSShServer(tunnel)
mixedTlsListener, err := mixed.NewTls(addr, wanInput, tunnel)
if err != nil {
return
}
// mixedTlsUDPLister, err = socks.NewUDP(addr, udpIn)
// if err != nil {
// mixedTlsListener.Close()
// return
// }
log.Infoln("wan proxy listening at: %s", mixedTlsListener.Address())
}

View file

@ -105,9 +105,9 @@ func listenLocalConn(rAddr, lAddr net.Addr, tunnel C.Tunnel) (*net.UDPConn, erro
buf := pool.Get(pool.UDPBufferSize)
br, err := lc.Read(buf)
if err != nil {
pool.Put(buf)
if errors.Is(err, net.ErrClosed) {
log.Debugln("TProxy local conn listener exit.. rAddr=%s lAddr=%s", rAddr.String(), lAddr.String())
pool.Put(buf)
return
}
}

View file

@ -1,7 +1,9 @@
package log
import (
"bytes"
"fmt"
"gopkg.in/natefinch/lumberjack.v2"
"os"
"github.com/metacubex/mihomo/common/observable"
@ -15,14 +17,35 @@ var (
level = INFO
)
type MylogFormatter struct {
}
func (f *MylogFormatter) Format(entry *log.Entry) ([]byte, error) {
var b *bytes.Buffer
if entry.Buffer != nil {
b = entry.Buffer
} else {
b = &bytes.Buffer{}
}
b.WriteString(entry.Time.Format("2006/01/02 15:04:05"))
b.WriteString(fmt.Sprintf(" |%.4s| ", entry.Level))
b.WriteString(entry.Message)
b.WriteByte('\n')
return b.Bytes(), nil
}
func init() {
log.SetOutput(os.Stdout)
log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2006-01-02T15:04:05.999999999Z07:00",
EnvironmentOverrideColors: true,
})
//log.SetFormatter(&log.TextFormatter{
// FullTimestamp: true,
// TimestampFormat: "2006/01/02 15:04:05",
// EnvironmentOverrideColors: true,
//})
log.SetFormatter(&MylogFormatter{})
}
type Event struct {
@ -79,6 +102,18 @@ func SetLevel(newLevel LogLevel) {
level = newLevel
}
func SetOutput(file string, maxSize, maxBackups, maxAge int, compress bool) {
if file != "" {
log.SetOutput(&lumberjack.Logger{
Filename: file,
MaxSize: maxSize, // megabytes
MaxBackups: maxBackups,
MaxAge: maxAge, //days
Compress: compress, // disabled by default
})
}
}
func print(data Event) {
if data.LogLevel < level {
return

87
main.go
View file

@ -3,39 +3,45 @@ package main
import (
"flag"
"fmt"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/hub"
"os"
"os/signal"
"path/filepath"
"runtime"
"runtime/debug"
"strings"
"syscall"
"time"
"unsafe"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
"github.com/metacubex/mihomo/hub"
"github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/log"
cs "github.com/metacubex/mihomo/service"
"go.uber.org/automaxprocs/maxprocs"
)
var (
version bool
testConfig bool
geodataMode bool
homeDir string
configFile string
version bool
testConfig bool
geodataMode bool
//homeDir string
//configFile string
externalUI string
externalController string
externalControllerUnix string
secret string
service string
flagset map[string]bool
)
func init() {
flag.StringVar(&homeDir, "d", os.Getenv("CLASH_HOME_DIR"), "set configuration directory")
flag.StringVar(&configFile, "f", os.Getenv("CLASH_CONFIG_FILE"), "specify configuration file")
//flag.StringVar(&homeDir, "d", os.Getenv("CLASH_HOME_DIR"), "set configuration directory")
//flag.StringVar(&configFile, "f", os.Getenv("CLASH_CONFIG_FILE"), "specify configuration file")
flag.StringVar(&externalUI, "ext-ui", os.Getenv("CLASH_OVERRIDE_EXTERNAL_UI_DIR"), "override external ui directory")
flag.StringVar(&externalController, "ext-ctl", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER"), "override external controller address")
flag.StringVar(&externalControllerUnix, "ext-ctl-unix", os.Getenv("CLASH_OVERRIDE_EXTERNAL_CONTROLLER_UNIX"), "override external controller unix address")
@ -44,9 +50,16 @@ func init() {
flag.BoolVar(&version, "v", false, "show current version of mihomo")
flag.BoolVar(&testConfig, "t", false, "test configuration and exit")
flag.Parse()
flagset = map[string]bool{}
flag.Visit(func(f *flag.Flag) {
flagset[f.Name] = true
})
}
func main() {
a := 1
log.Infoln("ptr size %d", unsafe.Sizeof(&a))
_, _ = maxprocs.Set(maxprocs.Logger(func(string, ...any) {}))
if version {
fmt.Printf("Mihomo Meta %s %s %s with %s %s\n",
@ -58,24 +71,6 @@ func main() {
return
}
if homeDir != "" {
if !filepath.IsAbs(homeDir) {
currentDir, _ := os.Getwd()
homeDir = filepath.Join(currentDir, homeDir)
}
C.SetHomeDir(homeDir)
}
if configFile != "" {
if !filepath.IsAbs(configFile) {
currentDir, _ := os.Getwd()
configFile = filepath.Join(currentDir, configFile)
}
} else {
configFile = filepath.Join(C.Path.HomeDir(), C.Path.Config())
}
C.SetConfig(configFile)
if geodataMode {
C.GeodataMode = true
}
@ -94,6 +89,15 @@ func main() {
return
}
prg := cs.NewService(run)
if flagset["s"] {
prg.Action(service)
return
}
prg.RunIt()
}
func run() {
var options []hub.Option
if externalUI != "" {
options = append(options, hub.WithExternalUI(externalUI))
@ -127,7 +131,7 @@ func main() {
}
defer executor.Shutdown()
memoryUsage()
termSign := make(chan os.Signal, 1)
hupSign := make(chan os.Signal, 1)
signal.Notify(termSign, syscall.SIGINT, syscall.SIGTERM)
@ -145,3 +149,28 @@ func main() {
}
}
}
// memoryUsage implements a couple of not really beautiful hacks which purpose is to
// make OS reclaim the memory freed by AdGuard Home as soon as possible.
func memoryUsage() {
debug.SetGCPercent(10)
// madvdontneed: setting madvdontneed=1 will use MADV_DONTNEED
// instead of MADV_FREE on Linux when returning memory to the
// kernel. This is less efficient, but causes RSS numbers to drop
// more quickly.
_ = os.Setenv("GODEBUG", "madvdontneed=1")
// periodically call "debug.FreeOSMemory" so
// that the OS could reclaim the free memory
go func() {
ticker := time.NewTicker(15 * time.Second)
for {
select {
case t := <-ticker.C:
t.Second()
debug.FreeOSMemory()
}
}
}()
}

View file

@ -1,9 +1,8 @@
package common
import (
C "github.com/metacubex/mihomo/constant"
"github.com/dlclark/regexp2"
C "github.com/metacubex/mihomo/constant"
)
type DomainRegex struct {

63
rules/common/fileDi.go Normal file
View file

@ -0,0 +1,63 @@
package common
import (
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/rules/domain"
P "path"
)
var sets map[string]*domain.DomainSet = make(map[string]*domain.DomainSet)
type FileDI struct {
adapter string
payload string
}
func (f *FileDI) RuleType() C.RuleType {
return C.FileDI
}
func (f *FileDI) Match(metadata *C.Metadata) (bool, string) {
zoneset, ok := sets[f.payload]
if !ok {
return false, f.adapter
}
host := metadata.Host
if host == "" {
return false, f.adapter
}
if zoneset.HasDomain(host) {
return true, f.adapter
}
return false, f.adapter
}
func (f *FileDI) Adapter() string {
return f.adapter
}
func (f *FileDI) Payload() string {
return f.payload
}
func (f *FileDI) ShouldResolveIP() bool {
return false
}
func (f *FileDI) ShouldFindProcess() bool {
return false
}
// FILEDI,alias,DIRECT,file path
func NewFileDi(payload, adapter, file string) *FileDI {
domainSet := &domain.DomainSet{
File: P.Join(C.Path.HomeDir(), file),
Payload: payload,
}
domainSet.Init()
sets[payload] = domainSet
return &FileDI{
adapter: adapter,
payload: payload,
}
}

View file

@ -0,0 +1,227 @@
package common
import (
"bufio"
"context"
"github.com/metacubex/mihomo/component/mmdb"
"net"
"os"
P "path"
"slices"
"strings"
"sync"
"unicode"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/rules/domain"
)
var (
setsRecord map[string]*domain.DomainSet = make(map[string]*domain.DomainSet)
fdrdoonce sync.Once
domainchannel chan *recordedDomain
)
type recordedDomain struct {
domain string
zoneset *domain.DomainSet
}
type FileDIRecord struct {
adapter string
payload string
conditions []string
operator string
}
func (f *FileDIRecord) RuleType() C.RuleType {
return C.RECORD
}
func (f *FileDIRecord) Match(metadata *C.Metadata) (bool, string) {
host := metadata.Host
if host == "" {
return false, ""
}
for _, zoneset := range setsRecord {
if zoneset.HasDomain(host) {
metadata.HitRule = zoneset.Payload
return true, zoneset.Adapter
}
}
if !metadata.DstIP.IsValid() {
ip, err := resolver.ResolveIP(context.TODO(), host)
if err != nil {
//log.Warnln("[DNS] resolve %s error: %s", metadata.Host, err.Error())
return false, f.adapter
} else {
metadata.DstIP = ip
}
}
for _, zoneset := range setsRecord {
if matchISO(metadata.DstIP.AsSlice(), host, zoneset, metadata) {
return true, zoneset.Adapter
}
}
return false, f.adapter
}
func (f *FileDIRecord) Adapter() string {
return f.adapter
}
func (f *FileDIRecord) Payload() string {
return f.payload
}
func (f *FileDIRecord) ShouldResolveIP() bool {
return false
}
func (f *FileDIRecord) ShouldFindProcess() bool {
return false
}
func NewFileDIRecord(payload, adapter string, params []string) *FileDIRecord {
fdrdoonce.Do(func() {
domainchannel = make(chan *recordedDomain, 100)
go func() {
for rd := range domainchannel {
readAndWrite(rd.zoneset.File, rd.domain)
rd.zoneset.Init()
}
}()
})
groups := make([][]string, 0)
var count = 0
for {
group := make([]string, 0)
if count == 0 {
group = append(group, payload)
group = append(group, adapter)
}
count++
var idx = slices.Index(params, "@")
if idx > -1 {
group = append(group, params[0:idx]...)
groups = append(groups, group)
params = params[idx+1:]
} else {
group = append(group, params[:]...)
groups = append(groups, group)
break
}
}
for _, group := range groups {
log.Infoln("%v", group)
domainSet := &domain.DomainSet{
File: P.Join(C.Path.HomeDir(), group[2]),
Conditions: group[4:],
Operator: group[3],
Adapter: group[1],
Payload: group[0],
}
domainSet.Init()
setsRecord[domainSet.Payload] = domainSet
}
return &FileDIRecord{
adapter: "unknown",
payload: "unknown",
}
}
func matchISO(ipAddress net.IP, host string, domainSet *domain.DomainSet, metadata *C.Metadata) bool {
if !metadata.GeoedIp() {
metadata.DstGeoIP = mmdb.IPInstance().LookupCode(ipAddress)
if !metadata.GeoedIp() {
return false
}
}
iso := metadata.DstGeoIP[0]
var match bool
if domainSet.Operator == "and" {
match = true
for _, cond := range domainSet.Conditions {
if cond[0] == '!' {
cond = cond[1:]
match = strings.EqualFold(iso, cond)
match = !match
} else {
match = strings.EqualFold(iso, cond)
}
if !match {
return false
}
}
} else if domainSet.Operator == "or" {
match = false
for _, cond := range domainSet.Conditions {
if cond[0] == '!' {
cond = cond[1:]
match = strings.EqualFold(iso, cond)
match = !match
} else {
match = strings.EqualFold(iso, cond)
}
if match {
break
}
}
} else {
return false
}
if match {
log.Warnln("add %s into %s, ip %s ios %s", host, domainSet.File, ipAddress, iso)
domainchannel <- &recordedDomain{
domain: host,
zoneset: domainSet,
}
return true
}
return false
}
func readAndWrite(file, domain string) {
var strs = make([]string, 0, 1000)
var seen = make(map[string]bool, 1000)
if _, err := os.Stat(file); err == nil {
f, err := os.OpenFile(file, os.O_RDONLY, os.ModePerm)
if err != nil {
log.Errorln("%v", err)
return
}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimSpace(line)
line = strings.TrimFunc(line, func(r rune) bool {
return !unicode.IsGraphic(r)
})
if line == "" {
continue
}
if seen[line] {
continue
}
strs = append(strs, line)
}
if err := scanner.Err(); err != nil {
f.Close()
return
}
f.Close()
}
strs = append(strs, domain)
f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
if err != nil {
return
}
for _, v := range strs {
f.WriteString(v + "\n")
}
f.Close()
}

View file

@ -65,7 +65,6 @@ func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) {
}
return false, g.adapter
}
if metadata.DstGeoIP != nil {
return false, g.adapter
}

View file

@ -0,0 +1,53 @@
package common
import (
"github.com/metacubex/mihomo/component/mmdb"
C "github.com/metacubex/mihomo/constant"
)
type ISOEmpty struct {
adapter string
}
func (g *ISOEmpty) RuleType() C.RuleType {
return C.ISOEmpty
}
func (g *ISOEmpty) Match(metadata *C.Metadata) (bool, string) {
ip := metadata.DstIP
if !ip.IsValid() {
return false, g.adapter
}
if metadata.GeoedIp() {
return false, g.adapter
}
metadata.DstGeoIP = mmdb.IPInstance().LookupCode(ip.AsSlice())
if metadata.GeoedIp() {
return false, g.adapter
}
return true, g.adapter
}
func (g *ISOEmpty) Adapter() string {
return g.adapter
}
func (g *ISOEmpty) Payload() string {
return "ISO_Empty"
}
func (g *ISOEmpty) ShouldResolveIP() bool {
return true
}
func (g *ISOEmpty) ShouldFindProcess() bool {
return false
}
func NewISOEmpty(adapter string) *ISOEmpty {
isoEmpty := &ISOEmpty{
adapter: adapter,
}
return isoEmpty
}

View file

@ -0,0 +1,42 @@
package common
import (
C "github.com/metacubex/mihomo/constant"
)
type MustResolve struct {
adapter string
}
func (f *MustResolve) RuleType() C.RuleType {
return C.MustResolve
}
func (f *MustResolve) Match(metadata *C.Metadata) (bool, string) {
if metadata.DstIP.IsUnspecified() {
return true, f.adapter
}
return false, f.adapter
}
func (f *MustResolve) Adapter() string {
return f.adapter
}
func (f *MustResolve) Payload() string {
return "MustResolve"
}
func (f *MustResolve) ShouldResolveIP() bool {
return true
}
func (f *MustResolve) ShouldFindProcess() bool {
return false
}
func NewMustResolve(adapter string) *MustResolve {
return &MustResolve{
adapter: adapter,
}
}

64
rules/domain/domain.go Normal file
View file

@ -0,0 +1,64 @@
package domain
import (
"bufio"
"os"
"strings"
"sync"
"unicode"
"github.com/metacubex/mihomo/log"
)
type DomainSet struct {
File string
mu sync.RWMutex
Set *Set
Conditions []string
Operator string
Adapter string
Payload string
}
func (d *DomainSet) Init() {
d.mu.Lock()
defer d.mu.Unlock()
var strs []string
f, err := os.OpenFile(d.File, os.O_RDONLY, os.ModePerm)
if err != nil {
log.Fatalln("%v", err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
var count = 0
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimSpace(line)
line = strings.TrimFunc(line, func(r rune) bool {
return !unicode.IsGraphic(r)
})
if line == "" {
continue
}
count++
strs = append(strs, line)
}
if err := scanner.Err(); err != nil {
log.Fatalln("%v", err)
}
if len(strs) == 0 {
return
}
d.Set = NewSet(strs)
log.Infoln("%s loaded %d line from %s, mem size %f MB", d.Payload, count, d.File, float32(d.Set.Size())/1024/1024)
}
func (d *DomainSet) HasDomain(domain string) bool {
d.mu.RLock()
defer d.mu.RUnlock()
if d.Set == nil {
return false
}
return d.Set.Has(domain)
}

View file

@ -0,0 +1,63 @@
package domain
import (
"bufio"
"log"
"os"
"strings"
"testing"
"unicode"
)
var set *Set
func init() {
var strs []string
f, err := os.OpenFile("direct.txt", os.O_RDONLY, os.ModePerm)
if err != nil {
log.Fatal(err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
var count = 0
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimSpace(line)
line = strings.TrimFunc(line, func(r rune) bool {
return !unicode.IsGraphic(r)
})
if line == "" {
continue
}
count++
strs = append(strs, line)
}
if err := scanner.Err(); err != nil {
log.Fatalln(err)
}
if len(strs) == 0 {
return
}
set = NewSet(strs)
log.Printf(" %d line, mem size %f MB", count, float32(set.Size())/1024/1024)
}
func TestSet_Has(t *testing.T) {
var domains []string
domains = append(domains, "u.jd.com")
domains = append(domains, "oogle.com")
domains = append(domains, "google.com")
domains = append(domains, "1google.com")
domains = append(domains, "1.google.com")
domains = append(domains, "1.google.com.1")
domains = append(domains, "com")
domains = append(domains, "rch.google")
domains = append(domains, ".rch.google")
domains = append(domains, "www.youtube.com")
domains = append(domains, "play.google.com")
domains = append(domains, "checkip.synology.com")
for _, v := range domains {
t.Log(v, set.Has(v))
}
}

186
rules/domain/sskv.go Normal file
View file

@ -0,0 +1,186 @@
package domain
import (
"reflect"
"slices"
"unsafe"
"github.com/openacid/low/bitmap"
)
// mod from https://github.com/openacid/succinct
const prefixLabel = '\r'
type Set struct {
leaves, labelBitmap []uint64
labels []byte
ranks, selects []int32
}
// NewSet creates a new *Set struct, from a slice of sorted strings.
func NewSet(strs []string) *Set {
keys := make([]string, 0, len(strs))
seen := make(map[string]bool, len(strs))
for _, v := range strs {
if seen[v] {
continue
}
seen[v] = true
keys = append(keys, reverseDomainSuffix(v))
}
slices.Sort(keys)
ss := &Set{}
lIdx := 0
type qElt struct{ s, e, col int }
queue := []qElt{{0, len(keys), 0}}
for i := 0; i < len(queue); i++ {
elt := queue[i]
if elt.col == len(keys[elt.s]) {
// a leaf node
elt.s++
setBit(&ss.leaves, i, 1)
}
for j := elt.s; j < elt.e; {
frm := j
for ; j < elt.e && keys[j][elt.col] == keys[frm][elt.col]; j++ {
}
queue = append(queue, qElt{frm, j, elt.col + 1})
ss.labels = append(ss.labels, keys[frm][elt.col])
setBit(&ss.labelBitmap, lIdx, 0)
lIdx++
}
setBit(&ss.labelBitmap, lIdx, 1)
lIdx++
}
ss.init()
return ss
}
// Has query for a key and return whether it presents in the Set.
func (ss *Set) Has(key string) bool {
kbs := s2b(key)
klen := len(kbs)
nodeId, bmIdx := 0, 0
for i := klen - 1; i >= 0; i-- {
c := kbs[i]
for ; ; bmIdx++ {
//log.Printf("%c", c)
if getBit(ss.labelBitmap, bmIdx) != 0 {
// no more labels in this node
//if c == '.' {
// return true
//} else {
// return false
//}
return false
}
la := ss.labels[bmIdx-nodeId]
// isPrefix := la == '\r'
// if isPrefix {
// log.Printf("%c '/r' \n", c)
// } else {
// log.Printf("%c '%c'\n", c, la)
// }
if c == '.' && la == prefixLabel {
return true
}
if la == c {
break
}
}
// log.Printf("%c\n", c)
// go to next level
nodeId = countZeros(ss.labelBitmap, ss.ranks, bmIdx+1)
bmIdx = selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nodeId-1) + 1
}
return ss.labels[bmIdx-nodeId] == prefixLabel
}
func setBit(bm *[]uint64, i int, v int) {
for i>>6 >= len(*bm) {
*bm = append(*bm, 0)
}
(*bm)[i>>6] |= uint64(v) << uint(i&63)
}
func getBit(bm []uint64, i int) uint64 {
return bm[i>>6] & (1 << uint(i&63))
}
// init builds pre-calculated cache to speed up rank() and select()
func (ss *Set) init() {
ss.selects, ss.ranks = bitmap.IndexSelect32R64(ss.labelBitmap)
}
// countZeros counts the number of "0" in a bitmap before the i-th bit(excluding
// the i-th bit) on behalf of rank index.
// E.g.:
//
// countZeros("010010", 4) == 3
// // 012345
func countZeros(bm []uint64, ranks []int32, i int) int {
a, _ := bitmap.Rank64(bm, ranks, int32(i))
return i - int(a)
}
// selectIthOne returns the index of the i-th "1" in a bitmap, on behalf of rank
// and select indexes.
// E.g.:
//
// selectIthOne("010010", 1) == 4
// // 012345
func selectIthOne(bm []uint64, ranks, selects []int32, i int) int {
a, _ := bitmap.Select32R64(bm, selects, ranks, int32(i))
return int(a)
}
func reverseDomainSuffix(domain string) string {
l := len(domain)
b := []byte(domain)
for i := 0; i < l/2; i++ {
b[i] = domain[l-i-1]
b[l-i-1] = domain[i]
}
b = append(b, prefixLabel)
return string(b)
}
func s2b(s string) (b []byte) {
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh.Data = sh.Data
bh.Cap = sh.Len
bh.Len = sh.Len
return b
}
func (ss *Set) Size() int {
// leaves, labelBitmap []uint64
// labels []byte
// ranks, selects []int32
leavesSize := cap(ss.leaves) * 8
labelBitmapSize := cap(ss.labelBitmap) * 8
labelsSize := cap(ss.labels)
ranksSize := cap(ss.ranks) * 4
selectsSize := cap(ss.selects) * 4
return leavesSize + labelBitmapSize + labelsSize + ranksSize + selectsSize
}

View file

@ -81,6 +81,14 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string]
case "MATCH":
parsed = RC.NewMatch(target)
parseErr = nil
case "FILEDI":
parsed = RC.NewFileDi(payload, target, params[0])
case "RECORD":
parsed = RC.NewFileDIRecord(payload, target, params)
case "MUSTRESOLVE":
parsed = RC.NewMustResolve(target)
case "ISOEMPTY":
parsed = RC.NewISOEmpty(target)
default:
parseErr = fmt.Errorf("unsupported rule type %s", tp)
}

246
service/service.go Normal file
View file

@ -0,0 +1,246 @@
package service
import (
"flag"
"fmt"
S "github.com/kardianos/service"
"io/ioutil"
"os"
"os/exec"
"runtime"
"strings"
)
var Srv S.Service
var Prg *Program
var ServiceName = "clash"
type Program struct {
Run func()
}
func (p *Program) Start(s S.Service) error {
// Start should not block. Do the actual work async.
go p.run()
return nil
}
func (p *Program) run() {
// Do work here
p.Run()
}
func (p *Program) Stop(s S.Service) error {
// Stop should not block. Return with a few seconds.
return nil
}
func NewService(Run func()) *Program {
svcConfig := &S.Config{
Name: ServiceName,
DisplayName: ServiceName,
Description: ServiceName,
}
Prg = &Program{Run: Run}
var err error
Srv, err = S.New(Prg, svcConfig)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if IsOpenWrt() {
svcConfig.Option = S.KeyValue{}
svcConfig.Option["SysvScript"] = openWrtScript
}
fmt.Println("platform", Srv.Platform())
return Prg
}
func (p *Program) Action(action string) {
var err error
switch action {
case "install":
err := Srv.Install()
if err != nil {
fmt.Println(err)
return
}
if IsOpenWrt() {
// On OpenWrt it is important to run enable after the service installation
// Otherwise, the service won't start on the system startup
_, err := runInitdCommand("enable")
if err != nil {
fmt.Println(err)
}
return
}
case "uninstall":
if IsOpenWrt() {
// On OpenWrt it is important to run disable command first
// as it will remove the symlink
_, err := runInitdCommand("disable")
if err != nil {
fmt.Println(err)
return
}
}
err = Srv.Uninstall()
if err != nil {
fmt.Println(err)
}
case "status":
status, err := Srv.Status()
if err != nil {
if IsOpenWrt() {
status = 0
} else {
fmt.Println(err)
return
}
}
switch status {
case 0:
fmt.Printf("unknown\n")
case 1:
fmt.Printf("running\n")
case 2:
fmt.Printf("stopped\n")
}
case "start":
fallthrough
case "stop":
fallthrough
case "restart":
err := S.Control(Srv, action)
if err != nil {
if IsOpenWrt() {
_, err := runInitdCommand(action)
if err != nil {
fmt.Println(1, err)
}
} else {
fmt.Println(2, err)
}
}
default:
flag.PrintDefaults()
}
}
func (p *Program) RunIt() {
status, _ := Srv.Status()
if status != S.StatusUnknown {
// if installed as service, it always goes here.
err := Srv.Run()
if err != nil {
fmt.Println(err)
}
} else {
// this makes use of running directly otherwise.
p.Run()
}
}
// IsOpenWrt checks if OS is OpenWRT
func IsOpenWrt() bool {
if runtime.GOOS != "linux" {
return false
}
body, err := ioutil.ReadFile("/etc/os-release")
if err != nil {
return false
}
return strings.Contains(string(body), "OpenWrt")
}
// runInitdCommand runs init.d service command
// returns command code or error if any
func runInitdCommand(action string) (int, error) {
confPath := "/etc/init.d/" + ServiceName
code, _, err := RunCommand("sh", "-c", confPath+" "+action)
return code, err
}
// runCommand runs shell command
func RunCommand(command string, arguments ...string) (int, string, error) {
cmd := exec.Command(command, arguments...)
out, err := cmd.Output()
if err != nil {
return 1, "", fmt.Errorf("exec.Command(%s) failed: %v: %s", command, err, string(out))
}
return cmd.ProcessState.ExitCode(), string(out), nil
}
// OpenWrt procd init script
// https://github.com/AdguardTeam/AdGuardHome/issues/1386
const openWrtScript = `#!/bin/sh /etc/rc.common
DESCRIPTION="{{.Name}}"
cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}"
name="{{.Name}}"
pid_file="/var/run/$name.pid"
stdout_log="/var/log/$name.log"
stderr_log="/var/log/$name.err"
START=99
get_pid() {
cat "$pid_file"
}
is_running() {
[ -f "$pid_file" ] && cat /proc/$(get_pid)/stat > /dev/null 2>&1
}
start() {
if is_running; then
echo "Already started"
else
echo "Starting $name"
$cmd >> "$stdout_log" 2>> "$stderr_log" &
echo $! > "$pid_file"
if ! is_running; then
echo "Unable to start, see $stdout_log and $stderr_log"
exit 1
fi
fi
}
stop() {
if is_running; then
echo -n "Stopping $name.."
kill $(get_pid)
for i in $(seq 1 10)
do
if ! is_running; then
break
fi
echo -n "."
sleep 1
done
echo
if is_running; then
echo "Not stopped; may still be shutting down or shutdown may have failed"
exit 1
else
echo "Stopped"
if [ -f "$pid_file" ]; then
rm "$pid_file"
fi
fi
else
echo "Not running"
fi
}
restart() {
stop
if is_running; then
echo "Unable to stop, will not attempt to start"
exit 1
fi
start
}
status() {
if is_running; then
echo "Running"
else
echo "Stopped"
exit 1
fi
}
`

View file

@ -1,14 +1,16 @@
module mihomo-test
go 1.20
go 1.21.0
toolchain go1.22.1
require (
github.com/docker/docker v20.10.21+incompatible
github.com/docker/go-connections v0.4.0
github.com/metacubex/mihomo v0.0.0
github.com/miekg/dns v1.1.57
github.com/stretchr/testify v1.8.4
golang.org/x/net v0.18.0
github.com/miekg/dns v1.1.59
github.com/stretchr/testify v1.9.0
golang.org/x/net v0.25.0
)
replace github.com/metacubex/mihomo => ../
@ -16,21 +18,21 @@ replace github.com/metacubex/mihomo => ../
require (
github.com/3andne/restls-client-go v0.1.6 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/RyuaNerin/go-krypto v1.0.2 // indirect
github.com/RyuaNerin/go-krypto v1.2.4 // indirect
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cilium/ebpf v0.12.3 // indirect
github.com/cilium/ebpf v0.15.0 // indirect
github.com/coreos/go-iptables v0.7.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
github.com/ericlagergren/polyval v0.0.0-20230805202542-18692a1b76f9 // indirect
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
@ -39,85 +41,85 @@ require (
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.3.1 // indirect
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/gofrs/uuid/v5 v5.2.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/google/pprof v0.0.0-20240509144519-723abb6459b7 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c // indirect
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 // indirect
github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394 // indirect
github.com/metacubex/sing-quic v0.0.0-20231130141855-0022295e524b // indirect
github.com/metacubex/sing-shadowsocks v0.2.5 // indirect
github.com/metacubex/sing-shadowsocks2 v0.1.4 // indirect
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd // indirect
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 // indirect
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 // indirect
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
github.com/metacubex/quic-go v0.44.1-0.20240521004242-fcd70d587e22 // indirect
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 // indirect
github.com/metacubex/sing-shadowsocks v0.2.6 // indirect
github.com/metacubex/sing-shadowsocks2 v0.2.0 // indirect
github.com/metacubex/sing-tun v0.2.7-0.20240521155100-e8316a45a414 // indirect
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f // indirect
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/onsi/ginkgo/v2 v2.17.3 // indirect
github.com/openacid/low v0.1.21 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/puzpuzpuz/xsync/v3 v3.0.2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/sagernet/sing v0.2.18-0.20231108041402-4fbbd193203c // indirect
github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07 // indirect
github.com/sagernet/sing v0.3.8 // indirect
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
github.com/shirou/gopsutil/v3 v3.23.10 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 // indirect
github.com/sagernet/utls v1.5.4 // indirect
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e // indirect
github.com/samber/lo v1.39.0 // indirect
github.com/scjalliance/comshim v0.0.0-20231116235529-bbacf79a4691 // indirect
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
github.com/zhangyunhao116/fastrand v0.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zhangyunhao116/fastrand v0.4.0 // indirect
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
go.uber.org/mock v0.3.0 // indirect
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.15.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)

View file

@ -3,14 +3,18 @@ github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/RyuaNerin/elliptic2 v1.0.0/go.mod h1:wWB8fWrJI/6EPJkyV/r1Rj0hxUgrusmqSj8JN6yNf/A=
github.com/RyuaNerin/go-krypto v1.0.2 h1:9KiZrrBs+tDrQ66dNy4nrX6SzntKtSKdm0wKHhdB4WM=
github.com/RyuaNerin/go-krypto v1.0.2/go.mod h1:17LzMeJCgzGTkPH3TmfzRnEJ/yA7ErhTPp9sxIqONtA=
github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok=
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
@ -21,6 +25,9 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
github.com/cilium/ebpf v0.13.2/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
github.com/cilium/ebpf v0.14.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -30,6 +37,7 @@ github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v20.10.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog=
@ -42,6 +50,7 @@ github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po=
github.com/ericlagergren/polyval v0.0.0-20230805202542-18692a1b76f9/go.mod h1:aXxf//HFNaacVV7/YZ8qevpNZAEoxSCpoBjscNhjrCI=
github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBEz5nGDMvswiajqh7k8ogWRlhRwKy5mY=
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4=
@ -64,8 +73,12 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU=
github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gofrs/uuid/v5 v5.1.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -80,12 +93,19 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20240320155624-b11c3daa6f07/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/pprof v0.0.0-20240509144519-723abb6459b7/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c h1:PgxFEySCI41sH0mB7/2XswdXbUykQsRUGod8Rn+NubM=
github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/insomniacslk/dhcp v0.0.0-20240227161007-c728f5dd21c8/go.mod h1:izxuNQZeFrbx2nK2fAyN5iNUB34Fe9j0nK4PwLzAkKw=
github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
@ -96,12 +116,19 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
@ -110,26 +137,52 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
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.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
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/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 h1:npBvaPAT145UY8682AzpUMWpdIxJti/WPLjy7gCiYYs=
github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8/go.mod h1:ZR6Gas7P1GcADCVBc1uOrA0bLQqDDyp70+63fD/BE2c=
github.com/metacubex/gvisor v0.0.0-20231209122014-3e43224c7bbc/go.mod h1:rhBU9tD5ktoGPBtXUquhWuGJ4u+8ZZzBMi2cAdv9q8Y=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394 h1:dIT+KB2hknBCrwVAXPeY9tpzzkOZP5m40yqUteRT6/Y=
github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394/go.mod h1:F/t8VnA47xoia8ABlNA4InkZjssvFJ5p6E6jKdbkgAs=
github.com/metacubex/quic-go v0.41.1-0.20240120014142-a02f4a533d4a/go.mod h1:F/t8VnA47xoia8ABlNA4InkZjssvFJ5p6E6jKdbkgAs=
github.com/metacubex/quic-go v0.41.1-0.20240307164142-46c6f7cdf2d1/go.mod h1:F/t8VnA47xoia8ABlNA4InkZjssvFJ5p6E6jKdbkgAs=
github.com/metacubex/quic-go v0.42.1-0.20240319071510-a251e5c66a5c/go.mod h1:iGx3Y1zynls/FjFgykLSqDcM81U0IKePRTXEz5g3iiQ=
github.com/metacubex/quic-go v0.43.1-0.20240428051621-a109abfb4cf6/go.mod h1:uXHODgJFUfUnkkCMWLd5Er6L5QY/LFRZb9LD5jyyhsk=
github.com/metacubex/quic-go v0.43.2-0.20240518033621-2c3d14c6b38e/go.mod h1:uXHODgJFUfUnkkCMWLd5Er6L5QY/LFRZb9LD5jyyhsk=
github.com/metacubex/quic-go v0.44.1-0.20240521004242-fcd70d587e22/go.mod h1:88wAATpevav4xdy5N8oejQ2cbbI6EcLYEklFeo+qywA=
github.com/metacubex/sing-quic v0.0.0-20231130141855-0022295e524b h1:7XXoEePvxfkQN9b2wB8UXU3uzb9uL8syEFF7A9VAKKQ=
github.com/metacubex/sing-quic v0.0.0-20231130141855-0022295e524b/go.mod h1:Gu5/zqZDd5G1AUtoV2yjAPWOEy7zwbU2DBUjdxJh0Kw=
github.com/metacubex/sing-quic v0.0.0-20240130040922-cbe613c88f20/go.mod h1:bdHqEysJclB9BzIa5jcKKSZ1qua+YEPjR8fOzzE3vZU=
github.com/metacubex/sing-quic v0.0.0-20240310154810-47bca850fc01/go.mod h1:WyY0zYxv+o+18R/Ece+QFontlgXoobKbNqbtYn2zjz8=
github.com/metacubex/sing-quic v0.0.0-20240418004036-814c531c378d/go.mod h1:WyY0zYxv+o+18R/Ece+QFontlgXoobKbNqbtYn2zjz8=
github.com/metacubex/sing-quic v0.0.0-20240429040940-fa3a4ff2533e/go.mod h1:nfqibK+vkBtE6Ch8vYqrFTcaX4BC6TwKqNNl5rORll4=
github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.5 h1:O2RRSHlKGEpAVG/OHJQxyHqDy8uvvdCW/oW2TDBOIhc=
github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo=
github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg=
github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE=
github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k=
github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8=
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd h1:k0+92eARqyTAovGhg2AxdsMWHjUsdiGCnR5NuXF3CQY=
github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd/go.mod h1:Q7zmpJ+qOvMMXyUoYlxGQuWkqALUpXzFSSqO+KLPyzA=
github.com/metacubex/sing-tun v0.2.1-0.20240130042529-1f983547e9d4/go.mod h1:P+TjrGTG5AdQRaskP6NiI9gZmgnwR3o5ze9CkIQE+/s=
github.com/metacubex/sing-tun v0.2.6/go.mod h1:4VsMwZH1IlgPGFK1ZbBomZ/B2MYkTgs2+gnBAr5GOIo=
github.com/metacubex/sing-tun v0.2.7-0.20240512075008-89e7c6208eec/go.mod h1:4VsMwZH1IlgPGFK1ZbBomZ/B2MYkTgs2+gnBAr5GOIo=
github.com/metacubex/sing-tun v0.2.7-0.20240521155100-e8316a45a414/go.mod h1:4VsMwZH1IlgPGFK1ZbBomZ/B2MYkTgs2+gnBAr5GOIo=
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY=
github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM=
github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 h1:DBGA0hmrP4pVIwLiXUONdphjcppED+plmVaKf1oqkwk=
github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170/go.mod h1:/VbJfbdLnANE+SKXyMk/96sTRrD4GdFLh5mkegqqFcY=
github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232/go.mod h1:NGCrBZ+fUmp81yaA1kVskcNWBnwl5z4UHxz47A01zm8=
github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63/go.mod h1:uY+BYb0UEknLrqvbGcwi9i++KgrKxsurysgI6G1Pveo=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
@ -140,6 +193,11 @@ github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/ginkgo/v2 v2.17.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
github.com/openacid/low v0.1.21 h1:Tr2GNu4N/+rGRYdOsEHOE89cxUIaDViZbVmKz29uKGo=
@ -154,14 +212,17 @@ github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq5
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew=
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
@ -177,24 +238,40 @@ github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
github.com/sagernet/sing v0.2.18-0.20231108041402-4fbbd193203c h1:uask61Pxc3nGqsOSjqnBKrwfODWRoEa80lXm04LNk0E=
github.com/sagernet/sing v0.2.18-0.20231108041402-4fbbd193203c/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.3.0/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
github.com/sagernet/sing v0.3.5/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
github.com/sagernet/sing v0.3.6/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
github.com/sagernet/sing v0.3.8/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07 h1:ncKb5tVOsCQgCsv6UpsA0jinbNb5OQ5GMPJlyQP3EHM=
github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07/go.mod h1:u/MZf32xPG8jEKe3t+xUV67EBnKtDtCaPhsJQOQGUYU=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q=
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M=
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s=
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
github.com/scjalliance/comshim v0.0.0-20231116235529-bbacf79a4691/go.mod h1:frmTThEHn5H+hHqLPGBDKVlFLpE8f/4vY2M9od2tW9k=
github.com/shirou/gopsutil/v3 v3.23.10 h1:/N42opWlYzegYaVkWejXWJpbzKv2JDy3mrgGzKsh9hM=
github.com/shirou/gopsutil/v3 v3.23.10/go.mod h1:JIE26kpucQi+innVlAUnIEOSBhBUkirr5b44yr55+WE=
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk=
github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg=
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@ -210,6 +287,7 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -217,55 +295,84 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc=
github.com/zhangyunhao116/fastrand v0.4.0/go.mod h1:vIyo6EyBhjGKpZv6qVlkPl4JVAklpMM4DSKzbAkMguA=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -284,14 +391,22 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -299,6 +414,10 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -306,6 +425,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@ -314,3 +436,5 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
lukechampine.com/blake3 v1.2.2/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=

View file

@ -1,30 +1,36 @@
package statistic
import (
"encoding/json"
"github.com/shirou/gopsutil/v3/process"
"os"
"sync"
"time"
"github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/log"
"github.com/puzpuzpuz/xsync/v3"
"github.com/shirou/gopsutil/v3/process"
)
var DefaultManager *Manager
var Processor *process.Process
var ChannelManager map[string]*Manager
var ChannelMutex sync.Mutex
func init() {
DefaultManager = &Manager{
connections: xsync.NewMapOf[string, Tracker](),
uploadTemp: atomic.NewInt64(0),
downloadTemp: atomic.NewInt64(0),
uploadBlip: atomic.NewInt64(0),
downloadBlip: atomic.NewInt64(0),
uploadTotal: atomic.NewInt64(0),
downloadTotal: atomic.NewInt64(0),
process: &process.Process{Pid: int32(os.Getpid())},
}
ChannelManager = make(map[string]*Manager)
go DefaultManager.handle()
Processor = &process.Process{Pid: int32(os.Getpid())}
go func() {
ticker := time.NewTicker(time.Second)
for range ticker.C {
ChannelMutex.Lock()
for _, v := range ChannelManager {
v.handle()
}
ChannelMutex.Unlock()
}
}()
}
type Manager struct {
@ -35,8 +41,20 @@ type Manager struct {
downloadBlip atomic.Int64
uploadTotal atomic.Int64
downloadTotal atomic.Int64
process *process.Process
memory uint64
}
func NewManager(channelname string) *Manager {
manager := &Manager{
connections: xsync.NewMapOf[string, Tracker](),
uploadTemp: atomic.NewInt64(0),
downloadTemp: atomic.NewInt64(0),
uploadBlip: atomic.NewInt64(0),
downloadBlip: atomic.NewInt64(0),
uploadTotal: atomic.NewInt64(0),
downloadTotal: atomic.NewInt64(0),
}
ChannelManager[channelname] = manager
return manager
}
func (m *Manager) Join(c Tracker) {
@ -74,13 +92,8 @@ func (m *Manager) Now() (up int64, down int64) {
return m.uploadBlip.Load(), m.downloadBlip.Load()
}
func (m *Manager) Memory() uint64 {
m.updateMemory()
return m.memory
}
func (m *Manager) Snapshot() *Snapshot {
var connections []*TrackerInfo
var connections []*TrackerInfo = make([]*TrackerInfo, 0)
m.Range(func(c Tracker) bool {
connections = append(connections, c.Info())
return true
@ -89,18 +102,9 @@ func (m *Manager) Snapshot() *Snapshot {
UploadTotal: m.uploadTotal.Load(),
DownloadTotal: m.downloadTotal.Load(),
Connections: connections,
Memory: m.memory,
}
}
func (m *Manager) updateMemory() {
stat, err := m.process.MemoryInfo()
if err != nil {
return
}
m.memory = stat.RSS
}
func (m *Manager) ResetStatistic() {
m.uploadTemp.Store(0)
m.uploadBlip.Store(0)
@ -111,14 +115,10 @@ func (m *Manager) ResetStatistic() {
}
func (m *Manager) handle() {
ticker := time.NewTicker(time.Second)
for range ticker.C {
m.uploadBlip.Store(m.uploadTemp.Load())
m.uploadTemp.Store(0)
m.downloadBlip.Store(m.downloadTemp.Load())
m.downloadTemp.Store(0)
}
m.uploadBlip.Store(m.uploadTemp.Load())
m.uploadTemp.Store(0)
m.downloadBlip.Store(m.downloadTemp.Load())
m.downloadTemp.Store(0)
}
type Snapshot struct {
@ -127,3 +127,45 @@ type Snapshot struct {
Connections []*TrackerInfo `json:"connections"`
Memory uint64 `json:"memory"`
}
func Snapshots() map[string]*Snapshot {
var snapshots map[string]*Snapshot = make(map[string]*Snapshot)
for k, v := range ChannelManager {
snapshots[k] = v.Snapshot()
}
return snapshots
}
func SaveChannelsData(filename string) {
snapshots := Snapshots()
for _, v := range snapshots {
v.Connections = nil
}
if bytes, err := json.Marshal(snapshots); err == nil {
err = os.WriteFile(filename, bytes, 0666)
if err != nil {
log.Errorln(err.Error())
}
}
}
func RestoreChannelsData(filename string) {
bytes, err := os.ReadFile(filename)
if err != nil {
return
}
var snapshots = map[string]Snapshot{}
err = json.Unmarshal(bytes, &snapshots)
if err != nil {
log.Errorln(err.Error())
return
}
ChannelMutex.Lock()
defer ChannelMutex.Unlock()
for k, v := range snapshots {
manager := NewManager(k)
manager.downloadTotal.Add(v.DownloadTotal)
manager.uploadTotal.Add(v.UploadTotal)
}
}

View file

@ -131,7 +131,8 @@ func parseRemoteDestination(addr net.Addr, conn C.Connection) string {
}
}
func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64, pushToManager bool) *tcpTracker {
func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule,
uploadTotal int64, downloadTotal int64, pushToManager bool, ruleadapter string) *tcpTracker {
if conn != nil {
metadata.RemoteDst = parseRemoteDestination(conn.RemoteAddr(), conn)
}
@ -162,7 +163,8 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
if rule != nil {
t.TrackerInfo.Rule = rule.RuleType().String()
t.TrackerInfo.RulePayload = rule.Payload()
//t.TrackerInfo.RulePayload = rule.Payload()
t.TrackerInfo.RulePayload = ruleadapter
}
manager.Join(t)

View file

@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/metacubex/mihomo/component/loopback"
"net"
"net/netip"
"path/filepath"
@ -12,7 +13,6 @@ import (
"time"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/loopback"
"github.com/metacubex/mihomo/component/nat"
P "github.com/metacubex/mihomo/component/process"
"github.com/metacubex/mihomo/component/resolver"
@ -402,7 +402,18 @@ func handleUDPConn(packet C.PacketAdapter) {
return
}
pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule, 0, 0, true)
channel := rawPc.Chains()[0]
manager, ok := statistic.ChannelManager[channel]
if !ok {
statistic.ChannelMutex.Lock()
manager, ok = statistic.ChannelManager[channel]
if !ok {
manager = statistic.NewManager(channel)
}
statistic.ChannelMutex.Unlock()
}
pc := statistic.NewUDPTracker(rawPc, manager, metadata, rule, 0, 0, true)
switch true {
case metadata.SpecialProxy != "":
@ -555,7 +566,24 @@ func handleTCPConn(connCtx C.ConnContext) {
return
}
remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule, 0, int64(peekLen), true)
channel := remoteConn.Chains()[0]
manager, ok := statistic.ChannelManager[channel]
if !ok {
statistic.ChannelMutex.Lock()
manager, ok = statistic.ChannelManager[channel]
if !ok {
manager = statistic.NewManager(channel)
}
statistic.ChannelMutex.Unlock()
}
rulePayload := metadata.HitRule
if rule != nil && rulePayload == "" {
rulePayload = rule.Payload()
}
remoteConn = statistic.NewTCPTracker(remoteConn, manager, metadata, rule,
0, int64(peekLen), true, rulePayload)
defer func(remoteConn C.Conn) {
_ = remoteConn.Close()
}(remoteConn)
@ -564,11 +592,23 @@ func handleTCPConn(connCtx C.ConnContext) {
case metadata.SpecialProxy != "":
log.Infoln("[TCP] %s --> %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), metadata.SpecialProxy)
case rule != nil:
if rule.Payload() != "" {
log.Infoln("[TCP] %s --> %s match %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), fmt.Sprintf("%s(%s)", rule.RuleType().String(), rule.Payload()), remoteConn.Chains().String())
} else {
log.Infoln("[TCP] %s --> %s match %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), rule.RuleType().String(), remoteConn.Chains().String())
}
//if rule.Payload() != "" {
// log.Infoln("[TCP] %s --> %s match %s using %s",
// metadata.SourceDetail(), metadata.RemoteAddress(), fmt.Sprintf("%s(%s)",
// rule.RuleType().String(), rule.Payload()), remoteConn.Chains().String())
//} else {
// log.Infoln("[TCP] %s --> %s match %s using %s", metadata.SourceDetail(),
// metadata.RemoteAddress(), rule.RuleType().String(), remoteConn.Chains().String())
//}
log.Infoln(
"%6s(%7s) | %12s | %s -> %s %s",
rule.RuleType().String(),
rulePayload,
remoteConn.Chains().String(),
metadata.SourceDetail(),
metadata.Type,
metadata.RemoteAddress(),
)
case mode == Global:
log.Infoln("[TCP] %s --> %s using GLOBAL", metadata.SourceDetail(), metadata.RemoteAddress())
case mode == Direct: