mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-02-28 17:49:55 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c5f4a3ab1 | ||
|
|
368b1e1296 | ||
|
|
a9ce5da09d | ||
|
|
301c78ff9a | ||
|
|
72a126e580 | ||
|
|
20739f5db7 | ||
|
|
89dfabe9b3 | ||
|
|
5a9ad0ed3c | ||
|
|
bb803249fa | ||
|
|
3f6823ba49 | ||
|
|
c786b72030 | ||
|
|
269c52575c | ||
|
|
c7fc93df37 | ||
|
|
5d9d8f4d3b | ||
|
|
f3a43fe3a6 | ||
|
|
9a959202ed | ||
|
|
cd23112dc5 | ||
|
|
613becd8ea | ||
|
|
d6b496d3c0 | ||
|
|
5a24efdabf | ||
|
|
9de9f1ef51 | ||
|
|
fbead56ec9 | ||
|
|
1fff34d30e | ||
|
|
a35f712478 | ||
|
|
f805a9f4c6 | ||
|
|
eb985b002e | ||
|
|
462343531e | ||
|
|
671d901ee2 | ||
|
|
80e4eaad14 | ||
|
|
25b3c86d31 | ||
|
|
de19f927e8 | ||
|
|
792f16265e | ||
|
|
215bf0995f | ||
|
|
91d54bdac1 | ||
|
|
ce52c3438b | ||
|
|
d4478dbfa2 | ||
|
|
69454b030e | ||
|
|
e6d1c8cedf | ||
|
|
fabd216c34 | ||
|
|
a86c562852 |
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -104,29 +104,22 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.goarch != 'loong64' }}
|
||||
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.abi != '1' }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23'
|
||||
|
||||
- name: Set up Go
|
||||
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.goarch != 'loong64' }}
|
||||
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.abi != '1' }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.jobs.goversion }}
|
||||
|
||||
- name: Set up Go1.22 loongarch abi1
|
||||
- name: Set up Go1.23 loongarch abi1
|
||||
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
|
||||
run: |
|
||||
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi1.tar.gz
|
||||
sudo tar zxf go1.22.4.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Set up Go1.22 loongarch abi2
|
||||
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '2' }}
|
||||
run: |
|
||||
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi2.tar.gz
|
||||
sudo tar zxf go1.22.4.linux-amd64-abi2.tar.gz -C /usr/local
|
||||
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.23.0/go1.23.0.linux-amd64-abi1.tar.gz
|
||||
sudo tar zxf go1.23.0.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
@@ -194,7 +187,7 @@ jobs:
|
||||
uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r27
|
||||
ndk-version: r28-beta1
|
||||
|
||||
- name: Set NDK path
|
||||
if: ${{ matrix.jobs.goos == 'android' }}
|
||||
|
||||
@@ -163,8 +163,17 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
mapping["alive"] = p.alive.Load()
|
||||
mapping["name"] = p.Name()
|
||||
mapping["udp"] = p.SupportUDP()
|
||||
mapping["xudp"] = p.SupportXUDP()
|
||||
mapping["tfo"] = p.SupportTFO()
|
||||
mapping["uot"] = p.SupportUOT()
|
||||
|
||||
proxyInfo := p.ProxyInfo()
|
||||
mapping["xudp"] = proxyInfo.XUDP
|
||||
mapping["tfo"] = proxyInfo.TFO
|
||||
mapping["mptcp"] = proxyInfo.MPTCP
|
||||
mapping["smux"] = proxyInfo.SMUX
|
||||
mapping["interface"] = proxyInfo.Interface
|
||||
mapping["dialer-proxy"] = proxyInfo.DialerProxy
|
||||
mapping["routing-mark"] = proxyInfo.RoutingMark
|
||||
|
||||
return json.Marshal(mapping)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,5 @@ func SkipAuthRemoteAddress(addr string) bool {
|
||||
}
|
||||
|
||||
func skipAuth(addr netip.Addr) bool {
|
||||
if addr.IsValid() {
|
||||
for _, prefix := range skipAuthPrefixes {
|
||||
if prefix.Contains(addr.Unmap()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return prefixesContains(skipAuthPrefixes, addr)
|
||||
}
|
||||
|
||||
@@ -31,27 +31,17 @@ func IsRemoteAddrDisAllowed(addr net.Addr) bool {
|
||||
if err := m.SetRemoteAddr(addr); err != nil {
|
||||
return false
|
||||
}
|
||||
return isAllowed(m.AddrPort().Addr().Unmap()) && !isDisAllowed(m.AddrPort().Addr().Unmap())
|
||||
ipAddr := m.AddrPort().Addr()
|
||||
if ipAddr.IsValid() {
|
||||
return isAllowed(ipAddr) && !isDisAllowed(ipAddr)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isAllowed(addr netip.Addr) bool {
|
||||
if addr.IsValid() {
|
||||
for _, prefix := range lanAllowedIPs {
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return prefixesContains(lanAllowedIPs, addr)
|
||||
}
|
||||
|
||||
func isDisAllowed(addr netip.Addr) bool {
|
||||
if addr.IsValid() {
|
||||
for _, prefix := range lanDisAllowedIPs {
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return prefixesContains(lanDisAllowedIPs, addr)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/component/keepalive"
|
||||
@@ -42,6 +44,27 @@ func MPTCP() bool {
|
||||
}
|
||||
|
||||
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
|
||||
switch network { // like net.Resolver.internetAddrList but filter domain to avoid call net.Resolver.lookupIPAddr
|
||||
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "ip", "ip4", "ip6":
|
||||
if host, port, err := net.SplitHostPort(address); err == nil {
|
||||
switch host {
|
||||
case "localhost":
|
||||
switch network {
|
||||
case "tcp6", "udp6", "ip6":
|
||||
address = net.JoinHostPort("::1", port)
|
||||
default:
|
||||
address = net.JoinHostPort("127.0.0.1", port)
|
||||
}
|
||||
case "": // internetAddrList can handle this special case
|
||||
break
|
||||
default:
|
||||
if _, err := netip.ParseAddr(host); err != nil { // not ip
|
||||
return nil, fmt.Errorf("invalid network address: %s", address)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return lc.Listen(ctx, network, address)
|
||||
|
||||
@@ -61,3 +61,19 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
func prefixesContains(prefixes []netip.Prefix, addr netip.Addr) bool {
|
||||
if len(prefixes) == 0 {
|
||||
return false
|
||||
}
|
||||
if !addr.IsValid() {
|
||||
return false
|
||||
}
|
||||
addr = addr.Unmap().WithZone("") // netip.Prefix.Contains returns false if ip has an IPv6 zone
|
||||
for _, prefix := range prefixes {
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -85,14 +85,15 @@ func (b *Base) SupportUDP() bool {
|
||||
return b.udp
|
||||
}
|
||||
|
||||
// SupportXUDP implements C.ProxyAdapter
|
||||
func (b *Base) SupportXUDP() bool {
|
||||
return b.xudp
|
||||
}
|
||||
|
||||
// SupportTFO implements C.ProxyAdapter
|
||||
func (b *Base) SupportTFO() bool {
|
||||
return b.tfo
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (b *Base) ProxyInfo() (info C.ProxyInfo) {
|
||||
info.XUDP = b.xudp
|
||||
info.TFO = b.tfo
|
||||
info.MPTCP = b.mpTcp
|
||||
info.SMUX = false
|
||||
info.Interface = b.iface
|
||||
info.RoutingMark = b.rmark
|
||||
return
|
||||
}
|
||||
|
||||
// IsL3Protocol implements C.ProxyAdapter
|
||||
|
||||
@@ -3,18 +3,12 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/loopback"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
)
|
||||
|
||||
var DisableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
|
||||
|
||||
type Direct struct {
|
||||
*Base
|
||||
loopBack *loopback.Detector
|
||||
@@ -27,10 +21,8 @@ type DirectOption struct {
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
if !features.CMFA && !DisableLoopBackDetector {
|
||||
if err := d.loopBack.CheckConn(metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := d.loopBack.CheckConn(metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = append(opts, dialer.WithResolver(resolver.DirectHostResolver))
|
||||
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
|
||||
@@ -42,10 +34,8 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
if !features.CMFA && !DisableLoopBackDetector {
|
||||
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
|
||||
@@ -92,6 +92,13 @@ func (h *Http) SupportWithDialer() C.NetWork {
|
||||
return C.TCP
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (h *Http) ProxyInfo() C.ProxyInfo {
|
||||
info := h.Base.ProxyInfo()
|
||||
info.DialerProxy = h.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
||||
addr := metadata.RemoteAddress()
|
||||
HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n"
|
||||
|
||||
@@ -87,6 +87,13 @@ func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.Pack
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (h *Hysteria) ProxyInfo() C.ProxyInfo {
|
||||
info := h.Base.ProxyInfo()
|
||||
info.DialerProxy = h.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
type HysteriaOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
|
||||
@@ -96,6 +96,13 @@ func closeHysteria2(h *Hysteria2) {
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (h *Hysteria2) ProxyInfo() C.ProxyInfo {
|
||||
info := h.Base.ProxyInfo()
|
||||
info.DialerProxy = h.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
var salamanderPassword string
|
||||
|
||||
281
adapter/outbound/mieru.go
Normal file
281
adapter/outbound/mieru.go
Normal file
@@ -0,0 +1,281 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
mieruclient "github.com/enfein/mieru/v3/apis/client"
|
||||
mierumodel "github.com/enfein/mieru/v3/apis/model"
|
||||
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type Mieru struct {
|
||||
*Base
|
||||
option *MieruOption
|
||||
client mieruclient.Client
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
type MieruOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
PortRange string `proxy:"port-range,omitempty"`
|
||||
Transport string `proxy:"transport"`
|
||||
UserName string `proxy:"username"`
|
||||
Password string `proxy:"password"`
|
||||
Multiplexing string `proxy:"multiplexing,omitempty"`
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
if err := m.ensureClientIsRunning(opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr := metadataToMieruNetAddrSpec(metadata)
|
||||
c, err := m.client.DialContext(ctx, addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dial to %s failed: %w", addr, err)
|
||||
}
|
||||
return NewConn(c, m), nil
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (m *Mieru) ProxyInfo() C.ProxyInfo {
|
||||
info := m.Base.ProxyInfo()
|
||||
info.DialerProxy = m.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func (m *Mieru) ensureClientIsRunning(opts ...dialer.Option) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.client.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a dialer and add it to the client config, before starting the client.
|
||||
var dialer C.Dialer = dialer.NewDialer(m.Base.DialOptions(opts...)...)
|
||||
var err error
|
||||
if len(m.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(m.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
config, err := m.client.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Dialer = dialer
|
||||
if err := m.client.Store(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.client.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start mieru client: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMieru(option MieruOption) (*Mieru, error) {
|
||||
config, err := buildMieruClientConfig(option)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build mieru client config: %w", err)
|
||||
}
|
||||
c := mieruclient.NewClient()
|
||||
if err := c.Store(config); err != nil {
|
||||
return nil, fmt.Errorf("failed to store mieru client config: %w", err)
|
||||
}
|
||||
// Client is started lazily on the first use.
|
||||
|
||||
var addr string
|
||||
if option.Port != 0 {
|
||||
addr = net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
} else {
|
||||
beginPort, _, _ := beginAndEndPortFromPortRange(option.PortRange)
|
||||
addr = net.JoinHostPort(option.Server, strconv.Itoa(beginPort))
|
||||
}
|
||||
outbound := &Mieru{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
iface: option.Interface,
|
||||
tp: C.Mieru,
|
||||
udp: false,
|
||||
xudp: false,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
option: &option,
|
||||
client: c,
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeMieru)
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func closeMieru(m *Mieru) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.client != nil && m.client.IsRunning() {
|
||||
m.client.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec {
|
||||
if metadata.Host != "" {
|
||||
return mierumodel.NetAddrSpec{
|
||||
AddrSpec: mierumodel.AddrSpec{
|
||||
FQDN: metadata.Host,
|
||||
Port: int(metadata.DstPort),
|
||||
},
|
||||
Net: "tcp",
|
||||
}
|
||||
} else {
|
||||
return mierumodel.NetAddrSpec{
|
||||
AddrSpec: mierumodel.AddrSpec{
|
||||
IP: metadata.DstIP.AsSlice(),
|
||||
Port: int(metadata.DstPort),
|
||||
},
|
||||
Net: "tcp",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, error) {
|
||||
if err := validateMieruOption(option); err != nil {
|
||||
return nil, fmt.Errorf("failed to validate mieru option: %w", err)
|
||||
}
|
||||
|
||||
transportProtocol := mierupb.TransportProtocol_TCP.Enum()
|
||||
var server *mierupb.ServerEndpoint
|
||||
if net.ParseIP(option.Server) != nil {
|
||||
// server is an IP address
|
||||
if option.PortRange != "" {
|
||||
server = &mierupb.ServerEndpoint{
|
||||
IpAddress: proto.String(option.Server),
|
||||
PortBindings: []*mierupb.PortBinding{
|
||||
{
|
||||
PortRange: proto.String(option.PortRange),
|
||||
Protocol: transportProtocol,
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
server = &mierupb.ServerEndpoint{
|
||||
IpAddress: proto.String(option.Server),
|
||||
PortBindings: []*mierupb.PortBinding{
|
||||
{
|
||||
Port: proto.Int32(int32(option.Port)),
|
||||
Protocol: transportProtocol,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// server is a domain name
|
||||
if option.PortRange != "" {
|
||||
server = &mierupb.ServerEndpoint{
|
||||
DomainName: proto.String(option.Server),
|
||||
PortBindings: []*mierupb.PortBinding{
|
||||
{
|
||||
PortRange: proto.String(option.PortRange),
|
||||
Protocol: transportProtocol,
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
server = &mierupb.ServerEndpoint{
|
||||
DomainName: proto.String(option.Server),
|
||||
PortBindings: []*mierupb.PortBinding{
|
||||
{
|
||||
Port: proto.Int32(int32(option.Port)),
|
||||
Protocol: transportProtocol,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
config := &mieruclient.ClientConfig{
|
||||
Profile: &mierupb.ClientProfile{
|
||||
ProfileName: proto.String(option.Name),
|
||||
User: &mierupb.User{
|
||||
Name: proto.String(option.UserName),
|
||||
Password: proto.String(option.Password),
|
||||
},
|
||||
Servers: []*mierupb.ServerEndpoint{server},
|
||||
},
|
||||
}
|
||||
if multiplexing, ok := mierupb.MultiplexingLevel_value[option.Multiplexing]; ok {
|
||||
config.Profile.Multiplexing = &mierupb.MultiplexingConfig{
|
||||
Level: mierupb.MultiplexingLevel(multiplexing).Enum(),
|
||||
}
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func validateMieruOption(option MieruOption) error {
|
||||
if option.Name == "" {
|
||||
return fmt.Errorf("name is empty")
|
||||
}
|
||||
if option.Server == "" {
|
||||
return fmt.Errorf("server is empty")
|
||||
}
|
||||
if option.Port == 0 && option.PortRange == "" {
|
||||
return fmt.Errorf("either port or port-range must be set")
|
||||
}
|
||||
if option.Port != 0 && option.PortRange != "" {
|
||||
return fmt.Errorf("port and port-range cannot be set at the same time")
|
||||
}
|
||||
if option.Port != 0 && (option.Port < 1 || option.Port > 65535) {
|
||||
return fmt.Errorf("port must be between 1 and 65535")
|
||||
}
|
||||
if option.PortRange != "" {
|
||||
begin, end, err := beginAndEndPortFromPortRange(option.PortRange)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid port-range format")
|
||||
}
|
||||
if begin < 1 || begin > 65535 {
|
||||
return fmt.Errorf("begin port must be between 1 and 65535")
|
||||
}
|
||||
if end < 1 || end > 65535 {
|
||||
return fmt.Errorf("end port must be between 1 and 65535")
|
||||
}
|
||||
if begin > end {
|
||||
return fmt.Errorf("begin port must be less than or equal to end port")
|
||||
}
|
||||
}
|
||||
|
||||
if option.Transport != "TCP" {
|
||||
return fmt.Errorf("transport must be TCP")
|
||||
}
|
||||
if option.UserName == "" {
|
||||
return fmt.Errorf("username is empty")
|
||||
}
|
||||
if option.Password == "" {
|
||||
return fmt.Errorf("password is empty")
|
||||
}
|
||||
if option.Multiplexing != "" {
|
||||
if _, ok := mierupb.MultiplexingLevel_value[option.Multiplexing]; !ok {
|
||||
return fmt.Errorf("invalid multiplexing level: %s", option.Multiplexing)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func beginAndEndPortFromPortRange(portRange string) (int, int, error) {
|
||||
var begin, end int
|
||||
_, err := fmt.Sscanf(portRange, "%d-%d", &begin, &end)
|
||||
return begin, end, err
|
||||
}
|
||||
92
adapter/outbound/mieru_test.go
Normal file
92
adapter/outbound/mieru_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package outbound
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNewMieru(t *testing.T) {
|
||||
testCases := []struct {
|
||||
option MieruOption
|
||||
wantBaseAddr string
|
||||
}{
|
||||
{
|
||||
option: MieruOption{
|
||||
Name: "test",
|
||||
Server: "1.2.3.4",
|
||||
Port: 10000,
|
||||
Transport: "TCP",
|
||||
UserName: "test",
|
||||
Password: "test",
|
||||
},
|
||||
wantBaseAddr: "1.2.3.4:10000",
|
||||
},
|
||||
{
|
||||
option: MieruOption{
|
||||
Name: "test",
|
||||
Server: "2001:db8::1",
|
||||
PortRange: "10001-10002",
|
||||
Transport: "TCP",
|
||||
UserName: "test",
|
||||
Password: "test",
|
||||
},
|
||||
wantBaseAddr: "[2001:db8::1]:10001",
|
||||
},
|
||||
{
|
||||
option: MieruOption{
|
||||
Name: "test",
|
||||
Server: "example.com",
|
||||
Port: 10003,
|
||||
Transport: "TCP",
|
||||
UserName: "test",
|
||||
Password: "test",
|
||||
},
|
||||
wantBaseAddr: "example.com:10003",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
mieru, err := NewMieru(testCase.option)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if mieru.addr != testCase.wantBaseAddr {
|
||||
t.Errorf("got addr %q, want %q", mieru.addr, testCase.wantBaseAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBeginAndEndPortFromPortRange(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
begin int
|
||||
end int
|
||||
hasErr bool
|
||||
}{
|
||||
{"1-10", 1, 10, false},
|
||||
{"1000-2000", 1000, 2000, false},
|
||||
{"65535-65535", 65535, 65535, false},
|
||||
{"1", 0, 0, true},
|
||||
{"1-", 0, 0, true},
|
||||
{"-10", 0, 0, true},
|
||||
{"a-b", 0, 0, true},
|
||||
{"1-b", 0, 0, true},
|
||||
{"a-10", 0, 0, true},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
begin, end, err := beginAndEndPortFromPortRange(testCase.input)
|
||||
if testCase.hasErr {
|
||||
if err == nil {
|
||||
t.Errorf("beginAndEndPortFromPortRange(%s) should return an error", testCase.input)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("beginAndEndPortFromPortRange(%s) should not return an error, but got %v", testCase.input, err)
|
||||
}
|
||||
if begin != testCase.begin {
|
||||
t.Errorf("beginAndEndPortFromPortRange(%s) begin port mismatch, got %d, want %d", testCase.input, begin, testCase.begin)
|
||||
}
|
||||
if end != testCase.end {
|
||||
t.Errorf("beginAndEndPortFromPortRange(%s) end port mismatch, got %d, want %d", testCase.input, end, testCase.end)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +196,13 @@ func (ss *ShadowSocks) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) ProxyInfo() C.ProxyInfo {
|
||||
info := ss.Base.ProxyInfo()
|
||||
info.DialerProxy = ss.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if ss.option.UDPOverTCP {
|
||||
|
||||
@@ -122,6 +122,13 @@ func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) ProxyInfo() C.ProxyInfo {
|
||||
info := ssr.Base.ProxyInfo()
|
||||
info.DialerProxy = ssr.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||
// SSR protocol compatibility
|
||||
// https://github.com/metacubex/mihomo/pull/2056
|
||||
|
||||
@@ -97,6 +97,12 @@ func (s *SingMux) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *SingMux) ProxyInfo() C.ProxyInfo {
|
||||
info := s.ProxyAdapter.ProxyInfo()
|
||||
info.SMUX = true
|
||||
return info
|
||||
}
|
||||
|
||||
func closeSingMux(s *SingMux) {
|
||||
_ = s.client.Close()
|
||||
}
|
||||
|
||||
@@ -141,6 +141,13 @@ func (s *Snell) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (s *Snell) ProxyInfo() C.ProxyInfo {
|
||||
info := s.Base.ProxyInfo()
|
||||
info.DialerProxy = s.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewSnell(option SnellOption) (*Snell, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
psk := []byte(option.Psk)
|
||||
@@ -204,7 +211,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -171,6 +171,13 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (ss *Socks5) ProxyInfo() C.ProxyInfo {
|
||||
info := ss.Base.ProxyInfo()
|
||||
info.DialerProxy = ss.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewSocks5(option Socks5Option) (*Socks5, error) {
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
|
||||
@@ -121,6 +121,13 @@ func closeSsh(s *Ssh) {
|
||||
_ = s.client.Close()
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (s *Ssh) ProxyInfo() C.ProxyInfo {
|
||||
info := s.Base.ProxyInfo()
|
||||
info.DialerProxy = s.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewSsh(option SshOption) (*Ssh, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
|
||||
@@ -244,6 +244,13 @@ func (t *Trojan) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (t *Trojan) ProxyInfo() C.ProxyInfo {
|
||||
info := t.Base.ProxyInfo()
|
||||
info.DialerProxy = t.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
|
||||
@@ -146,6 +146,13 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
|
||||
return
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (t *Tuic) ProxyInfo() C.ProxyInfo {
|
||||
info := t.Base.ProxyInfo()
|
||||
info.DialerProxy = t.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
serverName := option.Server
|
||||
|
||||
@@ -379,6 +379,13 @@ func (v *Vless) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (v *Vless) ProxyInfo() C.ProxyInfo {
|
||||
info := v.Base.ProxyInfo()
|
||||
info.DialerProxy = v.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
||||
var addrType byte
|
||||
var addr []byte
|
||||
|
||||
@@ -388,6 +388,13 @@ func (v *Vmess) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (v *Vmess) ProxyInfo() C.ProxyInfo {
|
||||
info := v.Base.ProxyInfo()
|
||||
info.DialerProxy = v.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
|
||||
|
||||
@@ -611,18 +611,11 @@ func (r *refProxyAdapter) SupportUDP() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *refProxyAdapter) SupportXUDP() bool {
|
||||
func (r *refProxyAdapter) ProxyInfo() C.ProxyInfo {
|
||||
if r.proxyAdapter != nil {
|
||||
return r.proxyAdapter.SupportXUDP()
|
||||
return r.proxyAdapter.ProxyInfo()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *refProxyAdapter) SupportTFO() bool {
|
||||
if r.proxyAdapter != nil {
|
||||
return r.proxyAdapter.SupportTFO()
|
||||
}
|
||||
return false
|
||||
return C.ProxyInfo{}
|
||||
}
|
||||
|
||||
func (r *refProxyAdapter) MarshalJSON() ([]byte, error) {
|
||||
|
||||
@@ -141,6 +141,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewSsh(*sshOption)
|
||||
case "mieru":
|
||||
mieruOption := &outbound.MieruOption{}
|
||||
err = decoder.Decode(mapping, mieruOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewMieru(*mieruOption)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||
}
|
||||
|
||||
@@ -57,15 +57,17 @@ type OverrideSchema struct {
|
||||
}
|
||||
|
||||
type proxyProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Proxy string `provider:"proxy,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
||||
ExcludeType string `provider:"exclude-type,omitempty"`
|
||||
DialerProxy string `provider:"dialer-proxy,omitempty"`
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Proxy string `provider:"proxy,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
||||
ExcludeType string `provider:"exclude-type,omitempty"`
|
||||
DialerProxy string `provider:"dialer-proxy,omitempty"`
|
||||
SizeLimit int64 `provider:"size-limit,omitempty"`
|
||||
Payload []map[string]any `provider:"payload,omitempty"`
|
||||
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
Override OverrideSchema `provider:"override,omitempty"`
|
||||
@@ -98,6 +100,11 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
}
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, uint(schema.HealthCheck.TestTimeout), hcInterval, schema.HealthCheck.Lazy, expectedStatus)
|
||||
|
||||
parser, err := NewProxiesParser(schema.Filter, schema.ExcludeFilter, schema.ExcludeType, schema.DialerProxy, schema.Override)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vehicle types.Vehicle
|
||||
switch schema.Type {
|
||||
case "file":
|
||||
@@ -111,17 +118,14 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout)
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout, schema.SizeLimit)
|
||||
case "inline":
|
||||
return NewInlineProvider(name, schema.Payload, parser, hc)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||
}
|
||||
|
||||
interval := time.Duration(uint(schema.Interval)) * time.Second
|
||||
filter := schema.Filter
|
||||
excludeFilter := schema.ExcludeFilter
|
||||
excludeType := schema.ExcludeType
|
||||
dialerProxy := schema.DialerProxy
|
||||
override := schema.Override
|
||||
|
||||
return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, dialerProxy, override, vehicle, hc)
|
||||
return NewProxySetProvider(name, interval, parser, vehicle, hc)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -30,44 +31,101 @@ type ProxySchema struct {
|
||||
Proxies []map[string]any `yaml:"proxies"`
|
||||
}
|
||||
|
||||
type providerForApi struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
VehicleType string `json:"vehicleType"`
|
||||
Proxies []C.Proxy `json:"proxies"`
|
||||
TestUrl string `json:"testUrl"`
|
||||
ExpectedStatus string `json:"expectedStatus"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||
SubscriptionInfo *SubscriptionInfo `json:"subscriptionInfo,omitempty"`
|
||||
}
|
||||
|
||||
type baseProvider struct {
|
||||
name string
|
||||
proxies []C.Proxy
|
||||
healthCheck *HealthCheck
|
||||
version uint32
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Name() string {
|
||||
return bp.name
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Version() uint32 {
|
||||
return bp.version
|
||||
}
|
||||
|
||||
func (bp *baseProvider) HealthCheck() {
|
||||
bp.healthCheck.check()
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Type() types.ProviderType {
|
||||
return types.Proxy
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Proxies() []C.Proxy {
|
||||
return bp.proxies
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Count() int {
|
||||
return len(bp.proxies)
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Touch() {
|
||||
bp.healthCheck.touch()
|
||||
}
|
||||
|
||||
func (bp *baseProvider) HealthCheckURL() string {
|
||||
return bp.healthCheck.url
|
||||
}
|
||||
|
||||
func (bp *baseProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
bp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
|
||||
func (bp *baseProvider) setProxies(proxies []C.Proxy) {
|
||||
bp.proxies = proxies
|
||||
bp.healthCheck.setProxy(proxies)
|
||||
if bp.healthCheck.auto() {
|
||||
go bp.healthCheck.check()
|
||||
}
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Close() error {
|
||||
bp.healthCheck.close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProxySetProvider for auto gc
|
||||
type ProxySetProvider struct {
|
||||
*proxySetProvider
|
||||
}
|
||||
|
||||
type proxySetProvider struct {
|
||||
baseProvider
|
||||
*resource.Fetcher[[]C.Proxy]
|
||||
proxies []C.Proxy
|
||||
healthCheck *HealthCheck
|
||||
version uint32
|
||||
subscriptionInfo *SubscriptionInfo
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"name": pp.Name(),
|
||||
"type": pp.Type().String(),
|
||||
"vehicleType": pp.VehicleType().String(),
|
||||
"proxies": pp.Proxies(),
|
||||
"testUrl": pp.healthCheck.url,
|
||||
"expectedStatus": pp.healthCheck.expectedStatus.String(),
|
||||
"updatedAt": pp.UpdatedAt(),
|
||||
"subscriptionInfo": pp.subscriptionInfo,
|
||||
return json.Marshal(providerForApi{
|
||||
Name: pp.Name(),
|
||||
Type: pp.Type().String(),
|
||||
VehicleType: pp.VehicleType().String(),
|
||||
Proxies: pp.Proxies(),
|
||||
TestUrl: pp.healthCheck.url,
|
||||
ExpectedStatus: pp.healthCheck.expectedStatus.String(),
|
||||
UpdatedAt: pp.UpdatedAt(),
|
||||
SubscriptionInfo: pp.subscriptionInfo,
|
||||
})
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Version() uint32 {
|
||||
return pp.version
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Name() string {
|
||||
return pp.Fetcher.Name()
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) HealthCheck() {
|
||||
pp.healthCheck.check()
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Update() error {
|
||||
_, _, err := pp.Fetcher.Update()
|
||||
return err
|
||||
@@ -79,54 +137,12 @@ func (pp *proxySetProvider) Initial() error {
|
||||
return err
|
||||
}
|
||||
if subscriptionInfo := cachefile.Cache().GetSubscriptionInfo(pp.Name()); subscriptionInfo != "" {
|
||||
pp.SetSubscriptionInfo(subscriptionInfo)
|
||||
pp.subscriptionInfo.Update(subscriptionInfo)
|
||||
}
|
||||
pp.closeAllConnections()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Type() types.ProviderType {
|
||||
return types.Proxy
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Proxies() []C.Proxy {
|
||||
return pp.proxies
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Count() int {
|
||||
return len(pp.proxies)
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Touch() {
|
||||
pp.healthCheck.touch()
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) HealthCheckURL() string {
|
||||
return pp.healthCheck.url
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
pp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||
pp.proxies = proxies
|
||||
pp.healthCheck.setProxy(proxies)
|
||||
if pp.healthCheck.auto() {
|
||||
go pp.healthCheck.check()
|
||||
}
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) SetSubscriptionInfo(userInfo string) {
|
||||
pp.subscriptionInfo = NewSubscriptionInfo(userInfo)
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) SetProvider(provider types.ProxyProvider) {
|
||||
if httpVehicle, ok := pp.Vehicle().(*resource.HTTPVehicle); ok {
|
||||
httpVehicle.SetProvider(provider)
|
||||
}
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) closeAllConnections() {
|
||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||
for _, chain := range c.Chains() {
|
||||
@@ -140,44 +156,37 @@ func (pp *proxySetProvider) closeAllConnections() {
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Close() error {
|
||||
pp.healthCheck.close()
|
||||
_ = pp.baseProvider.Close()
|
||||
return pp.Fetcher.Close()
|
||||
}
|
||||
|
||||
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
var excludeTypeArray []string
|
||||
if excludeType != "" {
|
||||
excludeTypeArray = strings.Split(excludeType, "|")
|
||||
}
|
||||
|
||||
var filterRegs []*regexp2.Regexp
|
||||
for _, filter := range strings.Split(filter, "`") {
|
||||
filterReg, err := regexp2.Compile(filter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||
}
|
||||
filterRegs = append(filterRegs, filterReg)
|
||||
}
|
||||
|
||||
func NewProxySetProvider(name string, interval time.Duration, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
si := new(SubscriptionInfo)
|
||||
pd := &proxySetProvider{
|
||||
proxies: []C.Proxy{},
|
||||
healthCheck: hc,
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
proxies: []C.Proxy{},
|
||||
healthCheck: hc,
|
||||
},
|
||||
subscriptionInfo: si,
|
||||
}
|
||||
|
||||
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd))
|
||||
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, parser, proxiesOnUpdate(pd))
|
||||
pd.Fetcher = fetcher
|
||||
wrapper := &ProxySetProvider{pd}
|
||||
if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok {
|
||||
httpVehicle.SetProvider(wrapper)
|
||||
httpVehicle.SetInRead(func(resp *http.Response) {
|
||||
if subscriptionInfo := resp.Header.Get("subscription-userinfo"); subscriptionInfo != "" {
|
||||
cachefile.Cache().SetSubscriptionInfo(name, subscriptionInfo)
|
||||
si.Update(subscriptionInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
wrapper := &ProxySetProvider{pd}
|
||||
runtime.SetFinalizer(wrapper, (*ProxySetProvider).Close)
|
||||
return wrapper, nil
|
||||
}
|
||||
@@ -187,8 +196,73 @@ func (pp *ProxySetProvider) Close() error {
|
||||
return pp.proxySetProvider.Close()
|
||||
}
|
||||
|
||||
func (pp *ProxySetProvider) SetProvider(provider types.ProxyProvider) {
|
||||
pp.proxySetProvider.SetProvider(provider)
|
||||
// InlineProvider for auto gc
|
||||
type InlineProvider struct {
|
||||
*inlineProvider
|
||||
}
|
||||
|
||||
type inlineProvider struct {
|
||||
baseProvider
|
||||
updateAt time.Time
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(providerForApi{
|
||||
Name: ip.Name(),
|
||||
Type: ip.Type().String(),
|
||||
VehicleType: ip.VehicleType().String(),
|
||||
Proxies: ip.Proxies(),
|
||||
TestUrl: ip.healthCheck.url,
|
||||
ExpectedStatus: ip.healthCheck.expectedStatus.String(),
|
||||
UpdatedAt: ip.updateAt,
|
||||
})
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) VehicleType() types.VehicleType {
|
||||
return types.Inline
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) Initial() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) Update() error {
|
||||
// make api update happy
|
||||
ip.updateAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewInlineProvider(name string, payload []map[string]any, parser resource.Parser[[]C.Proxy], hc *HealthCheck) (*InlineProvider, error) {
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
ps := ProxySchema{Proxies: payload}
|
||||
buf, err := yaml.Marshal(ps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxies, err := parser(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip := &inlineProvider{
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
proxies: proxies,
|
||||
healthCheck: hc,
|
||||
},
|
||||
updateAt: time.Now(),
|
||||
}
|
||||
wrapper := &InlineProvider{ip}
|
||||
runtime.SetFinalizer(wrapper, (*InlineProvider).Close)
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
func (ip *InlineProvider) Close() error {
|
||||
runtime.SetFinalizer(ip, nil)
|
||||
return ip.baseProvider.Close()
|
||||
}
|
||||
|
||||
// CompatibleProvider for auto gc
|
||||
@@ -197,36 +271,20 @@ type CompatibleProvider struct {
|
||||
}
|
||||
|
||||
type compatibleProvider struct {
|
||||
name string
|
||||
healthCheck *HealthCheck
|
||||
subscriptionInfo *SubscriptionInfo
|
||||
proxies []C.Proxy
|
||||
version uint32
|
||||
baseProvider
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"name": cp.Name(),
|
||||
"type": cp.Type().String(),
|
||||
"vehicleType": cp.VehicleType().String(),
|
||||
"proxies": cp.Proxies(),
|
||||
"testUrl": cp.healthCheck.url,
|
||||
"expectedStatus": cp.healthCheck.expectedStatus.String(),
|
||||
return json.Marshal(providerForApi{
|
||||
Name: cp.Name(),
|
||||
Type: cp.Type().String(),
|
||||
VehicleType: cp.VehicleType().String(),
|
||||
Proxies: cp.Proxies(),
|
||||
TestUrl: cp.healthCheck.url,
|
||||
ExpectedStatus: cp.healthCheck.expectedStatus.String(),
|
||||
})
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Version() uint32 {
|
||||
return cp.version
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Name() string {
|
||||
return cp.name
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) HealthCheck() {
|
||||
cp.healthCheck.check()
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Update() error {
|
||||
return nil
|
||||
}
|
||||
@@ -242,39 +300,6 @@ func (cp *compatibleProvider) VehicleType() types.VehicleType {
|
||||
return types.Compatible
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Type() types.ProviderType {
|
||||
return types.Proxy
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Proxies() []C.Proxy {
|
||||
return cp.proxies
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Count() int {
|
||||
return len(cp.proxies)
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Touch() {
|
||||
cp.healthCheck.touch()
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) HealthCheckURL() string {
|
||||
return cp.healthCheck.url
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
cp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Close() error {
|
||||
cp.healthCheck.close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) SetSubscriptionInfo(userInfo string) {
|
||||
cp.subscriptionInfo = NewSubscriptionInfo(userInfo)
|
||||
}
|
||||
|
||||
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
|
||||
if len(proxies) == 0 {
|
||||
return nil, errors.New("provider need one proxy at least")
|
||||
@@ -285,9 +310,11 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
|
||||
}
|
||||
|
||||
pd := &compatibleProvider{
|
||||
name: name,
|
||||
proxies: proxies,
|
||||
healthCheck: hc,
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
proxies: proxies,
|
||||
healthCheck: hc,
|
||||
},
|
||||
}
|
||||
|
||||
wrapper := &CompatibleProvider{pd}
|
||||
@@ -307,7 +334,25 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
|
||||
}
|
||||
}
|
||||
|
||||
func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string, override OverrideSchema) resource.Parser[[]C.Proxy] {
|
||||
func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
var excludeTypeArray []string
|
||||
if excludeType != "" {
|
||||
excludeTypeArray = strings.Split(excludeType, "|")
|
||||
}
|
||||
|
||||
var filterRegs []*regexp2.Regexp
|
||||
for _, filter := range strings.Split(filter, "`") {
|
||||
filterReg, err := regexp2.Compile(filter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||
}
|
||||
filterRegs = append(filterRegs, filterReg)
|
||||
}
|
||||
|
||||
return func(buf []byte) ([]C.Proxy, error) {
|
||||
schema := &ProxySchema{}
|
||||
|
||||
@@ -422,5 +467,5 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
|
||||
}
|
||||
|
||||
return proxies, nil
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -15,9 +15,8 @@ type SubscriptionInfo struct {
|
||||
Expire int64
|
||||
}
|
||||
|
||||
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
|
||||
func (info *SubscriptionInfo) Update(userinfo string) {
|
||||
userinfo = strings.ReplaceAll(strings.ToLower(userinfo), " ", "")
|
||||
si = new(SubscriptionInfo)
|
||||
|
||||
for _, field := range strings.Split(userinfo, ";") {
|
||||
name, value, ok := strings.Cut(field, "=")
|
||||
@@ -33,17 +32,15 @@ func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
|
||||
|
||||
switch name {
|
||||
case "upload":
|
||||
si.Upload = intValue
|
||||
info.Upload = intValue
|
||||
case "download":
|
||||
si.Download = intValue
|
||||
info.Download = intValue
|
||||
case "total":
|
||||
si.Total = intValue
|
||||
info.Total = intValue
|
||||
case "expire":
|
||||
si.Expire = intValue
|
||||
info.Expire = intValue
|
||||
}
|
||||
}
|
||||
|
||||
return si
|
||||
}
|
||||
|
||||
func parseValue(value string) (int64, error) {
|
||||
|
||||
@@ -86,7 +86,27 @@ func (d *Decoder) Decode(src map[string]any, dst any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// isNil returns true if the input is nil or a typed nil pointer.
|
||||
func isNil(input any) bool {
|
||||
if input == nil {
|
||||
return true
|
||||
}
|
||||
val := reflect.ValueOf(input)
|
||||
return val.Kind() == reflect.Pointer && val.IsNil()
|
||||
}
|
||||
|
||||
func (d *Decoder) decode(name string, data any, val reflect.Value) error {
|
||||
if isNil(data) {
|
||||
// If the data is nil, then we don't set anything
|
||||
// Maybe we should set to zero value?
|
||||
return nil
|
||||
}
|
||||
if !reflect.ValueOf(data).IsValid() {
|
||||
// If the input value is invalid, then we just set the value
|
||||
// to be the zero value.
|
||||
val.Set(reflect.Zero(val.Type()))
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
kind := val.Kind()
|
||||
if kind == reflect.Pointer && val.IsNil() {
|
||||
|
||||
@@ -267,3 +267,21 @@ func TestStructure_TextUnmarshaller(t *testing.T) {
|
||||
err = decoder.Decode(rawMap, s)
|
||||
assert.NotNilf(t, err, "should throw error: %#v", s)
|
||||
}
|
||||
|
||||
func TestStructure_Null(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"opt": map[string]any{
|
||||
"bar": nil,
|
||||
},
|
||||
}
|
||||
|
||||
s := struct {
|
||||
Opt struct {
|
||||
Bar string `test:"bar,optional"`
|
||||
} `test:"opt,optional"`
|
||||
}{}
|
||||
|
||||
err := decoder.Decode(rawMap, &s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, s.Opt.Bar, "")
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ type Interface struct {
|
||||
Name string
|
||||
Addresses []netip.Prefix
|
||||
HardwareAddr net.HardwareAddr
|
||||
Flags net.Flags
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -66,6 +67,7 @@ func Interfaces() (map[string]*Interface, error) {
|
||||
Name: iface.Name,
|
||||
Addresses: ipNets,
|
||||
HardwareAddr: iface.HardwareAddr,
|
||||
Flags: iface.Flags,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ func KeepAliveInterval() time.Duration {
|
||||
|
||||
func SetDisableKeepAlive(disable bool) {
|
||||
if runtime.GOOS == "android" {
|
||||
setDisableKeepAlive(false)
|
||||
setDisableKeepAlive(true)
|
||||
} else {
|
||||
setDisableKeepAlive(disable)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,25 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/metacubex/mihomo/common/callback"
|
||||
"github.com/metacubex/mihomo/component/iface"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
var disableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
|
||||
|
||||
func init() {
|
||||
if features.CMFA {
|
||||
disableLoopBackDetector = true
|
||||
}
|
||||
}
|
||||
|
||||
var ErrReject = errors.New("reject loopback connection")
|
||||
|
||||
type Detector struct {
|
||||
@@ -20,6 +31,9 @@ type Detector struct {
|
||||
}
|
||||
|
||||
func NewDetector() *Detector {
|
||||
if disableLoopBackDetector {
|
||||
return nil
|
||||
}
|
||||
return &Detector{
|
||||
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
|
||||
packetConnMap: xsync.NewMapOf[uint16, struct{}](),
|
||||
@@ -27,6 +41,9 @@ func NewDetector() *Detector {
|
||||
}
|
||||
|
||||
func (l *Detector) NewConn(conn C.Conn) C.Conn {
|
||||
if l == nil || l.connMap == nil {
|
||||
return conn
|
||||
}
|
||||
metadata := C.Metadata{}
|
||||
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
||||
return conn
|
||||
@@ -42,6 +59,9 @@ func (l *Detector) NewConn(conn C.Conn) C.Conn {
|
||||
}
|
||||
|
||||
func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
|
||||
if l == nil || l.packetConnMap == nil {
|
||||
return conn
|
||||
}
|
||||
metadata := C.Metadata{}
|
||||
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
||||
return conn
|
||||
@@ -58,6 +78,9 @@ func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
|
||||
}
|
||||
|
||||
func (l *Detector) CheckConn(metadata *C.Metadata) error {
|
||||
if l == nil || l.connMap == nil {
|
||||
return nil
|
||||
}
|
||||
connAddr := metadata.SourceAddrPort()
|
||||
if !connAddr.IsValid() {
|
||||
return nil
|
||||
@@ -69,6 +92,9 @@ func (l *Detector) CheckConn(metadata *C.Metadata) error {
|
||||
}
|
||||
|
||||
func (l *Detector) CheckPacketConn(metadata *C.Metadata) error {
|
||||
if l == nil || l.packetConnMap == nil {
|
||||
return nil
|
||||
}
|
||||
connAddr := metadata.SourceAddrPort()
|
||||
if !connAddr.IsValid() {
|
||||
return nil
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
)
|
||||
|
||||
@@ -23,11 +24,16 @@ type ASNReader struct {
|
||||
*maxminddb.Reader
|
||||
}
|
||||
|
||||
type ASNResult struct {
|
||||
type GeoLite2 struct {
|
||||
AutonomousSystemNumber uint32 `maxminddb:"autonomous_system_number"`
|
||||
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
|
||||
}
|
||||
|
||||
type IPInfo struct {
|
||||
ASN string `maxminddb:"asn"`
|
||||
Name string `maxminddb:"name"`
|
||||
}
|
||||
|
||||
func (r IPReader) LookupCode(ipAddress net.IP) []string {
|
||||
switch r.databaseType {
|
||||
case typeMaxmind:
|
||||
@@ -66,8 +72,18 @@ func (r IPReader) LookupCode(ipAddress net.IP) []string {
|
||||
}
|
||||
}
|
||||
|
||||
func (r ASNReader) LookupASN(ip net.IP) ASNResult {
|
||||
var result ASNResult
|
||||
r.Lookup(ip, &result)
|
||||
return result
|
||||
func (r ASNReader) LookupASN(ip net.IP) (string, string) {
|
||||
switch r.Metadata.DatabaseType {
|
||||
case "GeoLite2-ASN", "DBIP-ASN-Lite (compat=GeoLite2-ASN)":
|
||||
var result GeoLite2
|
||||
_ = r.Lookup(ip, &result)
|
||||
return fmt.Sprint(result.AutonomousSystemNumber), result.AutonomousSystemOrganization
|
||||
case "ipinfo generic_asn_free.mmdb":
|
||||
var result IPInfo
|
||||
_ = r.Lookup(ip, &result)
|
||||
return result.ASN[2:], result.Name
|
||||
default:
|
||||
log.Warnln("Unsupported ASN type: %s", r.Metadata.DatabaseType)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ func newSearcher(isV4, isTCP bool) *searcher {
|
||||
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
|
||||
for size, buf := uint32(8), make([]byte, 8); ; {
|
||||
ptr := unsafe.Pointer(&buf[0])
|
||||
err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
|
||||
err, _, _ := syscall.Syscall6(fn, 6, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
|
||||
|
||||
switch err {
|
||||
case 0:
|
||||
@@ -215,13 +215,13 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
||||
|
||||
buf := make([]uint16, syscall.MAX_LONG_PATH)
|
||||
size := uint32(len(buf))
|
||||
r1, _, err := syscall.SyscallN(
|
||||
queryProcName,
|
||||
r1, _, err := syscall.Syscall6(
|
||||
queryProcName, 4,
|
||||
uintptr(h),
|
||||
uintptr(0),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
)
|
||||
0, 0)
|
||||
if r1 == 0 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -84,12 +84,14 @@ func NewFileVehicle(path string) *FileVehicle {
|
||||
}
|
||||
|
||||
type HTTPVehicle struct {
|
||||
url string
|
||||
path string
|
||||
proxy string
|
||||
header http.Header
|
||||
timeout time.Duration
|
||||
provider types.ProxyProvider
|
||||
url string
|
||||
path string
|
||||
proxy string
|
||||
header http.Header
|
||||
timeout time.Duration
|
||||
sizeLimit int64
|
||||
inRead func(response *http.Response)
|
||||
provider types.ProxyProvider
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) Url() string {
|
||||
@@ -112,8 +114,8 @@ func (h *HTTPVehicle) Write(buf []byte) error {
|
||||
return safeWrite(h.path, buf)
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) SetProvider(provider types.ProxyProvider) {
|
||||
h.provider = provider
|
||||
func (h *HTTPVehicle) SetInRead(fn func(response *http.Response)) {
|
||||
h.inRead = fn
|
||||
}
|
||||
|
||||
func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) {
|
||||
@@ -139,9 +141,8 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []b
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if subscriptionInfo := resp.Header.Get("subscription-userinfo"); h.provider != nil && subscriptionInfo != "" {
|
||||
cachefile.Cache().SetSubscriptionInfo(h.provider.Name(), subscriptionInfo)
|
||||
h.provider.SetSubscriptionInfo(subscriptionInfo)
|
||||
if h.inRead != nil {
|
||||
h.inRead(resp)
|
||||
}
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
@@ -151,7 +152,11 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []b
|
||||
err = errors.New(resp.Status)
|
||||
return
|
||||
}
|
||||
buf, err = io.ReadAll(resp.Body)
|
||||
var reader io.Reader = resp.Body
|
||||
if h.sizeLimit > 0 {
|
||||
reader = io.LimitReader(reader, h.sizeLimit)
|
||||
}
|
||||
buf, err = io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -166,12 +171,13 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []b
|
||||
return
|
||||
}
|
||||
|
||||
func NewHTTPVehicle(url string, path string, proxy string, header http.Header, timeout time.Duration) *HTTPVehicle {
|
||||
func NewHTTPVehicle(url string, path string, proxy string, header http.Header, timeout time.Duration, sizeLimit int64) *HTTPVehicle {
|
||||
return &HTTPVehicle{
|
||||
url: url,
|
||||
path: path,
|
||||
proxy: proxy,
|
||||
header: header,
|
||||
timeout: timeout,
|
||||
url: url,
|
||||
path: path,
|
||||
proxy: proxy,
|
||||
header: header,
|
||||
timeout: timeout,
|
||||
sizeLimit: sizeLimit,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,8 +145,12 @@ func (sd *Dispatcher) Enable() bool {
|
||||
}
|
||||
|
||||
func (sd *Dispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (string, error) {
|
||||
//defer func(start time.Time) {
|
||||
// log.Debugln("[Sniffer] [%s] Sniffing took %s", metadata.DstIP, time.Since(start))
|
||||
//}(time.Now())
|
||||
|
||||
for s := range sd.sniffers {
|
||||
if s.SupportNetwork() == C.TCP {
|
||||
if s.SupportNetwork() == C.TCP && s.SupportPort(metadata.DstPort) {
|
||||
_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
_, err := conn.Peek(1)
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
@@ -154,7 +158,7 @@ func (sd *Dispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (s
|
||||
_, ok := err.(*net.OpError)
|
||||
if ok {
|
||||
sd.cacheSniffFailed(metadata)
|
||||
log.Errorln("[Sniffer] [%s] may not have any sent data, Consider adding skip", metadata.DstIP.String())
|
||||
log.Errorln("[Sniffer] [%s] [%s] may not have any sent data, Consider adding skip", metadata.DstIP, s.Protocol())
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
@@ -164,22 +168,36 @@ func (sd *Dispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (s
|
||||
bufferedLen := conn.Buffered()
|
||||
bytes, err := conn.Peek(bufferedLen)
|
||||
if err != nil {
|
||||
log.Debugln("[Sniffer] the data length not enough")
|
||||
log.Debugln("[Sniffer] [%s] [%s] the data length not enough, error: %v", metadata.DstIP, s.Protocol(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
host, err := s.SniffData(bytes)
|
||||
var e *errNeedAtLeastData
|
||||
if errors.As(err, &e) {
|
||||
//log.Debugln("[Sniffer] [%s] [%s] %v, got length: %d", metadata.DstIP, s.Protocol(), e, len(bytes))
|
||||
_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
bytes, err = conn.Peek(e.length)
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
//log.Debugln("[Sniffer] [%s] [%s] try again, got length: %d", metadata.DstIP, s.Protocol(), len(bytes))
|
||||
if err != nil {
|
||||
log.Debugln("[Sniffer] [%s] [%s] the data length not enough, error: %v", metadata.DstIP, s.Protocol(), err)
|
||||
continue
|
||||
}
|
||||
host, err = s.SniffData(bytes)
|
||||
}
|
||||
if err != nil {
|
||||
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP)
|
||||
//log.Debugln("[Sniffer] [%s] [%s] Sniff data failed, error: %v", metadata.DstIP, s.Protocol(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = netip.ParseAddr(host)
|
||||
if err == nil {
|
||||
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP)
|
||||
//log.Debugln("[Sniffer] [%s] [%s] Sniff data failed, got host [%s]", metadata.DstIP, s.Protocol(), host)
|
||||
continue
|
||||
}
|
||||
|
||||
//log.Debugln("[Sniffer] [%s] [%s] Sniffed [%s]", metadata.DstIP, s.Protocol(), host)
|
||||
return host, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package sniffer
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
@@ -15,6 +16,19 @@ var (
|
||||
errNotClientHello = errors.New("not client hello")
|
||||
)
|
||||
|
||||
type errNeedAtLeastData struct {
|
||||
length int
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *errNeedAtLeastData) Error() string {
|
||||
return fmt.Sprintf("%v, need at least length: %d", e.err, e.length)
|
||||
}
|
||||
|
||||
func (e *errNeedAtLeastData) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
var _ sniffer.Sniffer = (*TLSSniffer)(nil)
|
||||
|
||||
type TLSSniffer struct {
|
||||
@@ -160,7 +174,10 @@ func SniffTLS(b []byte) (*string, error) {
|
||||
}
|
||||
headerLen := int(binary.BigEndian.Uint16(b[3:5]))
|
||||
if 5+headerLen > len(b) {
|
||||
return nil, ErrNoClue
|
||||
return nil, &errNeedAtLeastData{
|
||||
length: 5 + headerLen,
|
||||
err: ErrNoClue,
|
||||
}
|
||||
}
|
||||
|
||||
domain, err := ReadClientHello(b[5 : 5+headerLen])
|
||||
|
||||
@@ -45,7 +45,7 @@ func SetGeoUpdateInterval(newGeoUpdateInterval int) {
|
||||
}
|
||||
|
||||
func UpdateMMDB() (err error) {
|
||||
vehicle := resource.NewHTTPVehicle(geodata.MmdbUrl(), C.Path.MMDB(), "", nil, defaultHttpTimeout)
|
||||
vehicle := resource.NewHTTPVehicle(geodata.MmdbUrl(), C.Path.MMDB(), "", nil, defaultHttpTimeout, 0)
|
||||
var oldHash utils.HashType
|
||||
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
||||
oldHash = utils.MakeHash(buf)
|
||||
@@ -76,7 +76,7 @@ func UpdateMMDB() (err error) {
|
||||
}
|
||||
|
||||
func UpdateASN() (err error) {
|
||||
vehicle := resource.NewHTTPVehicle(geodata.ASNUrl(), C.Path.ASN(), "", nil, defaultHttpTimeout)
|
||||
vehicle := resource.NewHTTPVehicle(geodata.ASNUrl(), C.Path.ASN(), "", nil, defaultHttpTimeout, 0)
|
||||
var oldHash utils.HashType
|
||||
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
||||
oldHash = utils.MakeHash(buf)
|
||||
@@ -109,7 +109,7 @@ func UpdateASN() (err error) {
|
||||
func UpdateGeoIp() (err error) {
|
||||
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
||||
|
||||
vehicle := resource.NewHTTPVehicle(geodata.GeoIpUrl(), C.Path.GeoIP(), "", nil, defaultHttpTimeout)
|
||||
vehicle := resource.NewHTTPVehicle(geodata.GeoIpUrl(), C.Path.GeoIP(), "", nil, defaultHttpTimeout, 0)
|
||||
var oldHash utils.HashType
|
||||
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
||||
oldHash = utils.MakeHash(buf)
|
||||
@@ -139,7 +139,7 @@ func UpdateGeoIp() (err error) {
|
||||
func UpdateGeoSite() (err error) {
|
||||
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
||||
|
||||
vehicle := resource.NewHTTPVehicle(geodata.GeoSiteUrl(), C.Path.GeoSite(), "", nil, defaultHttpTimeout)
|
||||
vehicle := resource.NewHTTPVehicle(geodata.GeoSiteUrl(), C.Path.GeoSite(), "", nil, defaultHttpTimeout, 0)
|
||||
var oldHash utils.HashType
|
||||
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
||||
oldHash = utils.MakeHash(buf)
|
||||
|
||||
@@ -42,6 +42,7 @@ const (
|
||||
WireGuard
|
||||
Tuic
|
||||
Ssh
|
||||
Mieru
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -99,13 +100,24 @@ type Dialer interface {
|
||||
ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error)
|
||||
}
|
||||
|
||||
type ProxyInfo struct {
|
||||
XUDP bool
|
||||
TFO bool
|
||||
MPTCP bool
|
||||
SMUX bool
|
||||
Interface string
|
||||
RoutingMark int
|
||||
DialerProxy string
|
||||
}
|
||||
|
||||
type ProxyAdapter interface {
|
||||
Name() string
|
||||
Type() AdapterType
|
||||
Addr() string
|
||||
SupportUDP() bool
|
||||
SupportXUDP() bool
|
||||
SupportTFO() bool
|
||||
|
||||
// ProxyInfo contains some extra information maybe useful for MarshalJSON
|
||||
ProxyInfo() ProxyInfo
|
||||
MarshalJSON() ([]byte, error)
|
||||
|
||||
// Deprecated: use DialContextWithDialer and ListenPacketWithDialer instead.
|
||||
@@ -215,7 +227,8 @@ func (at AdapterType) String() string {
|
||||
return "Tuic"
|
||||
case Ssh:
|
||||
return "Ssh"
|
||||
|
||||
case Mieru:
|
||||
return "Mieru"
|
||||
case Relay:
|
||||
return "Relay"
|
||||
case Selector:
|
||||
|
||||
@@ -13,6 +13,7 @@ const (
|
||||
File VehicleType = iota
|
||||
HTTP
|
||||
Compatible
|
||||
Inline
|
||||
)
|
||||
|
||||
// VehicleType defined
|
||||
@@ -26,6 +27,8 @@ func (v VehicleType) String() string {
|
||||
return "HTTP"
|
||||
case Compatible:
|
||||
return "Compatible"
|
||||
case Inline:
|
||||
return "Inline"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
@@ -81,7 +84,6 @@ type ProxyProvider interface {
|
||||
Version() uint32
|
||||
RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint)
|
||||
HealthCheckURL() string
|
||||
SetSubscriptionInfo(userInfo string)
|
||||
}
|
||||
|
||||
// RuleProvider interface
|
||||
|
||||
@@ -846,6 +846,18 @@ proxies: # socks5
|
||||
password: password
|
||||
privateKey: path
|
||||
|
||||
# mieru
|
||||
- name: mieru
|
||||
type: mieru
|
||||
server: 1.2.3.4
|
||||
port: 2999
|
||||
# port-range: 2090-2099 #(不可同时填写 port 和 port-range)
|
||||
transport: TCP # 只支持 TCP
|
||||
username: user
|
||||
password: password
|
||||
# 可以使用的值包括 MULTIPLEXING_OFF, MULTIPLEXING_LOW, MULTIPLEXING_MIDDLE, MULTIPLEXING_HIGH。其中 MULTIPLEXING_OFF 会关闭多路复用功能。默认值为 MULTIPLEXING_LOW。
|
||||
# multiplexing: MULTIPLEXING_LOW
|
||||
|
||||
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
|
||||
- name: "dns-out"
|
||||
type: dns
|
||||
@@ -930,6 +942,7 @@ proxy-providers:
|
||||
interval: 3600
|
||||
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
||||
proxy: DIRECT
|
||||
# size-limit: 10240 # 限制下载文件最大为10kb,默认为0即不限制文件大小
|
||||
header:
|
||||
User-Agent:
|
||||
- "Clash/v1.18.0"
|
||||
@@ -962,6 +975,17 @@ proxy-providers:
|
||||
# - pattern: "IPLC-(.*?)倍"
|
||||
# target: "iplc x $1"
|
||||
|
||||
provider2:
|
||||
type: inline
|
||||
dialer-proxy: proxy
|
||||
payload:
|
||||
- name: "ss1"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
|
||||
test:
|
||||
type: file
|
||||
path: /test.yaml
|
||||
@@ -977,6 +1001,7 @@ rule-providers:
|
||||
type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
|
||||
url: "url"
|
||||
proxy: DIRECT
|
||||
# size-limit: 10240 # 限制下载文件最大为10kb,默认为0即不限制文件大小
|
||||
rule2:
|
||||
behavior: classical
|
||||
interval: 259200
|
||||
@@ -1000,6 +1025,14 @@ rule-providers:
|
||||
format: mrs
|
||||
behavior: domain
|
||||
path: /path/to/save/file.mrs
|
||||
rule4:
|
||||
type: inline
|
||||
behavior: domain # classical / ipcidr
|
||||
payload:
|
||||
- '.blogger.com'
|
||||
- '*.*.microsoft.com'
|
||||
- 'books.itunes.apple.com'
|
||||
|
||||
rules:
|
||||
- RULE-SET,rule1,REJECT
|
||||
- IP-ASN,1,PROXY
|
||||
@@ -1200,4 +1233,4 @@ listeners:
|
||||
# authentication-timeout: 1000
|
||||
# alpn:
|
||||
# - h3
|
||||
# max-udp-relay-packet-size: 1500
|
||||
# max-udp-relay-packet-size: 1500
|
||||
|
||||
53
go.mod
53
go.mod
@@ -7,55 +7,56 @@ require (
|
||||
github.com/bahlo/generic-list-go v0.2.0
|
||||
github.com/coreos/go-iptables v0.8.0
|
||||
github.com/dlclark/regexp2 v1.11.4
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
github.com/enfein/mieru/v3 v3.9.0
|
||||
github.com/go-chi/chi/v5 v5.2.0
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gobwas/ws v1.4.0
|
||||
github.com/gofrs/uuid/v5 v5.3.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475
|
||||
github.com/klauspost/compress v1.17.9
|
||||
github.com/klauspost/cpuid/v2 v2.2.8
|
||||
github.com/insomniacslk/dhcp v0.0.0-20241224095048-b56fa0d5f25d
|
||||
github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20
|
||||
github.com/klauspost/cpuid/v2 v2.2.9
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399
|
||||
github.com/metacubex/chacha v0.1.0
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/quic-go v0.48.1-0.20241021013658-51ca987e0174
|
||||
github.com/metacubex/quic-go v0.48.3-0.20241126053724-b69fea3888da
|
||||
github.com/metacubex/randv2 v0.2.0
|
||||
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20241021011113-857bcd6ee47c
|
||||
github.com/metacubex/sing-tun v0.4.5
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3
|
||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
||||
github.com/metacubex/utls v1.6.6
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
||||
github.com/miekg/dns v1.1.62
|
||||
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/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
||||
github.com/sagernet/cors v1.2.1
|
||||
github.com/sagernet/fswatch v0.1.1
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
||||
github.com/sagernet/sing v0.5.0-rc.4
|
||||
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6
|
||||
github.com/sagernet/sing-shadowtls v0.1.4
|
||||
github.com/sagernet/sing v0.5.1
|
||||
github.com/sagernet/sing-mux v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.1.5
|
||||
github.com/samber/lo v1.47.0
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/shirou/gopsutil/v4 v4.24.11
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // lastest version compatible with golang1.20
|
||||
golang.org/x/net v0.30.0
|
||||
golang.org/x/sys v0.26.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
golang.org/x/net v0.33.0
|
||||
golang.org/x/sys v0.28.0
|
||||
google.golang.org/protobuf v1.34.2 // lastest version compatible with golang1.20
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.3.0
|
||||
)
|
||||
@@ -68,6 +69,7 @@ require (
|
||||
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
|
||||
github.com/ebitengine/purego v0.8.1 // indirect
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||
@@ -78,16 +80,16 @@ 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/google/btree v1.1.2 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
@@ -97,7 +99,6 @@ require (
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // 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
|
||||
@@ -110,10 +111,10 @@ require (
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20241021005542-18b67490300a
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000
|
||||
|
||||
93
go.sum
93
go.sum
@@ -27,6 +27,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.9.0 h1:h8KIXKxwPg8jT0uFdABIi0864O10UMAAOkkWo4zKl04=
|
||||
github.com/enfein/mieru/v3 v3.9.0/go.mod h1:jH2nXzJSNUn6UWuzD8E8AsRVa9Ca0CqcTcr9Z+CJO1o=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
|
||||
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=
|
||||
@@ -40,8 +44,8 @@ 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/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
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-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
@@ -60,27 +64,27 @@ github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
|
||||
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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/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/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 h1:hxST5pwMBEOWmxpkX20w9oZG+hXdhKmAIPQ3NGGAxas=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20241224095048-b56fa0d5f25d h1:VkCNWh6tuQLgDBc6KrUOz/L1mCUQGnR1Ujj8uTgpwwk=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20241224095048-b56fa0d5f25d/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
|
||||
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=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
@@ -102,28 +106,28 @@ github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0s
|
||||
github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
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-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
|
||||
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
|
||||
github.com/metacubex/quic-go v0.48.1-0.20241021013658-51ca987e0174 h1:GvigRPEU+cbnzdLWne47cxLrc28Abohl3ECtVdnrbq0=
|
||||
github.com/metacubex/quic-go v0.48.1-0.20241021013658-51ca987e0174/go.mod h1:AiZ+UPgrkO1DTnmiAX4b+kRoV1Vfc65UkYD7RbFlIZA=
|
||||
github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a h1:cZ6oNVrsmsi3SNlnSnRio4zOgtQq+/XidwsaNgKICcg=
|
||||
github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a/go.mod h1:xBw/SYJPgUMPQ1tklV/brGn2nxhfr3BnvBzNlyi4Nic=
|
||||
github.com/metacubex/quic-go v0.48.3-0.20241126053724-b69fea3888da h1:Mq6cbHbPTLLTUfA9scrwBmOGkvl6y99E3WmtMIMqo30=
|
||||
github.com/metacubex/quic-go v0.48.3-0.20241126053724-b69fea3888da/go.mod h1:AiZ+UPgrkO1DTnmiAX4b+kRoV1Vfc65UkYD7RbFlIZA=
|
||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||
github.com/metacubex/sing v0.0.0-20241021005542-18b67490300a h1:JuR0/7RDxQtlZp/GOzrdqNq04HplTxavPRHrek8ouJk=
|
||||
github.com/metacubex/sing v0.0.0-20241021005542-18b67490300a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000 h1:gUbMXcQXhXGj0vCpCVFTUyIH7TMpD1dpTcNv/MCS+ok=
|
||||
github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 h1:HobpULaPK6OoxrHMmgcwLkwwIduXVmwdcznwUfH1GQM=
|
||||
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20241021011113-857bcd6ee47c h1:qfUZ8xBrViOCZamvcC8CyV7Ii8sAUrn7RqZxFGn56tQ=
|
||||
github.com/metacubex/sing-tun v0.2.7-0.20241021011113-857bcd6ee47c/go.mod h1:lCrP0AW7ieKnXG1JEeZLW+9h99QzjuOX0MfCQfz6TgE=
|
||||
github.com/metacubex/sing-tun v0.4.5 h1:kWSyQzuzHI40r50OFBczfWIDvMBMy1RIk+JsXeBPRB0=
|
||||
github.com/metacubex/sing-tun v0.4.5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 h1:xg71VmzLS6ByAzi/57phwDvjE+dLLs+ozH00k4DnOns=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3/go.mod h1:6nitcmzPDL3MXnLdhu6Hm126Zk4S1fBbX3P7jxUxSFw=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
|
||||
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
||||
@@ -166,19 +170,16 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
|
||||
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/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo=
|
||||
github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE=
|
||||
github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo=
|
||||
github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4=
|
||||
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/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
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=
|
||||
github.com/shirou/gopsutil/v4 v4.24.11 h1:WaU9xqGFKvFfsUv94SXcUPD7rCkU0vr/asVdQOBZNj8=
|
||||
github.com/shirou/gopsutil/v4 v4.24.11/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
|
||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
|
||||
@@ -197,8 +198,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
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.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=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.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/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
@@ -230,8 +232,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
||||
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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
|
||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
@@ -240,11 +242,11 @@ golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.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=
|
||||
@@ -256,19 +258,18 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
|
||||
@@ -127,10 +127,10 @@ func router(isDebug bool, secret string, dohServer string, cors Cors) *chi.Mux {
|
||||
r.Mount("/providers/rules", ruleProviderRouter())
|
||||
r.Mount("/cache", cacheRouter())
|
||||
r.Mount("/dns", dnsRouter())
|
||||
if !embedMode { // disallow restart and upgrade in embed mode
|
||||
if !embedMode { // disallow restart in embed mode
|
||||
r.Mount("/restart", restartRouter())
|
||||
r.Mount("/upgrade", upgradeRouter())
|
||||
}
|
||||
r.Mount("/upgrade", upgradeRouter())
|
||||
addExternalRouters(r)
|
||||
|
||||
})
|
||||
|
||||
@@ -14,9 +14,11 @@ import (
|
||||
|
||||
func upgradeRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/", upgradeCore)
|
||||
r.Post("/ui", updateUI)
|
||||
r.Post("/geo", updateGeoDatabases)
|
||||
if !embedMode { // disallow upgrade core/geo in embed mode
|
||||
r.Post("/", upgradeCore)
|
||||
r.Post("/geo", updateGeoDatabases)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
@@ -23,18 +23,18 @@ type TunOption struct {
|
||||
GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"`
|
||||
Inet4Address []string `inbound:"inet4-address,omitempty"`
|
||||
Inet6Address []string `inbound:"inet6-address,omitempty"`
|
||||
IPRoute2TableIndex int `inbound:"iproute2-table-index"`
|
||||
IPRoute2RuleIndex int `inbound:"iproute2-rule-index"`
|
||||
AutoRedirect bool `inbound:"auto-redirect"`
|
||||
AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark"`
|
||||
AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark"`
|
||||
IPRoute2TableIndex int `inbound:"iproute2-table-index,omitempty"`
|
||||
IPRoute2RuleIndex int `inbound:"iproute2-rule-index,omitempty"`
|
||||
AutoRedirect bool `inbound:"auto-redirect,omitempty"`
|
||||
AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark,omitempty"`
|
||||
AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark,omitempty"`
|
||||
StrictRoute bool `inbound:"strict-route,omitempty"`
|
||||
RouteAddress []string `inbound:"route-address"`
|
||||
RouteAddressSet []string `inbound:"route-address-set"`
|
||||
RouteExcludeAddress []string `inbound:"route-exclude-address"`
|
||||
RouteExcludeAddressSet []string `inbound:"route-exclude-address-set"`
|
||||
RouteAddress []string `inbound:"route-address,omitempty"`
|
||||
RouteAddressSet []string `inbound:"route-address-set,omitempty"`
|
||||
RouteExcludeAddress []string `inbound:"route-exclude-address,omitempty"`
|
||||
RouteExcludeAddressSet []string `inbound:"route-exclude-address-set,omitempty"`
|
||||
IncludeInterface []string `inbound:"include-interface,omitempty"`
|
||||
ExcludeInterface []string `inbound:"exclude-interface"`
|
||||
ExcludeInterface []string `inbound:"exclude-interface,omitempty"`
|
||||
IncludeUID []uint32 `inbound:"include-uid,omitempty"`
|
||||
IncludeUIDRange []string `inbound:"include-uid-range,omitempty"`
|
||||
ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"`
|
||||
|
||||
@@ -279,7 +279,11 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
return
|
||||
}
|
||||
|
||||
defaultInterfaceMonitor, err = tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, log.SingLogger, tun.DefaultInterfaceMonitorOptions{InterfaceFinder: interfaceFinder, OverrideAndroidVPN: true})
|
||||
overrideAndroidVPN := true
|
||||
if disable, _ := strconv.ParseBool(os.Getenv("DISABLE_OVERRIDE_ANDROID_VPN")); disable {
|
||||
overrideAndroidVPN = false
|
||||
}
|
||||
defaultInterfaceMonitor, err = tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, log.SingLogger, tun.DefaultInterfaceMonitorOptions{InterfaceFinder: interfaceFinder, OverrideAndroidVPN: overrideAndroidVPN})
|
||||
if err != nil {
|
||||
err = E.Cause(err, "create DefaultInterfaceMonitor")
|
||||
return
|
||||
|
||||
@@ -20,7 +20,7 @@ func init() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2006-01-02T15:04:05.999999999Z07:00",
|
||||
TimestampFormat: "2006-01-02T15:04:05.000000000Z07:00",
|
||||
EnvironmentOverrideColors: true,
|
||||
})
|
||||
}
|
||||
|
||||
8
main.go
8
main.go
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -99,6 +100,13 @@ func main() {
|
||||
log.Fatalln("Initial configuration error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
} else if configFile == "-" {
|
||||
var err error
|
||||
configBytes, err = io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
log.Fatalln("Initial configuration error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if configFile != "" {
|
||||
if !filepath.IsAbs(configFile) {
|
||||
|
||||
@@ -3,6 +3,8 @@ package common
|
||||
import (
|
||||
"errors"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
@@ -38,3 +40,5 @@ func ParseParams(params []string) (isSrc bool, noResolve bool) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ParseRuleFunc func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (C.Rule, error)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/metacubex/mihomo/component/geodata"
|
||||
"github.com/metacubex/mihomo/component/mmdb"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
@@ -26,17 +24,14 @@ func (a *ASN) Match(metadata *C.Metadata) (bool, string) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
result := mmdb.ASNInstance().LookupASN(ip.AsSlice())
|
||||
asnNumber := strconv.FormatUint(uint64(result.AutonomousSystemNumber), 10)
|
||||
ipASN := asnNumber + " " + result.AutonomousSystemOrganization
|
||||
asn, aso := mmdb.ASNInstance().LookupASN(ip.AsSlice())
|
||||
if a.isSourceIP {
|
||||
metadata.SrcIPASN = ipASN
|
||||
metadata.SrcIPASN = asn + " " + aso
|
||||
} else {
|
||||
metadata.DstIPASN = ipASN
|
||||
metadata.DstIPASN = asn + " " + aso
|
||||
}
|
||||
|
||||
match := a.asn == asnNumber
|
||||
return match, a.adapter
|
||||
return a.asn == asn, a.adapter
|
||||
}
|
||||
|
||||
func (a *ASN) RuleType() C.RuleType {
|
||||
|
||||
@@ -40,7 +40,7 @@ func (i *IPCIDR) Match(metadata *C.Metadata) (bool, string) {
|
||||
if i.isSourceIP {
|
||||
ip = metadata.SrcIP
|
||||
}
|
||||
return ip.IsValid() && i.ipnet.Contains(ip), i.adapter
|
||||
return ip.IsValid() && i.ipnet.Contains(ip.WithZone("")), i.adapter
|
||||
}
|
||||
|
||||
func (i *IPCIDR) Adapter() string {
|
||||
|
||||
@@ -23,9 +23,7 @@ type Logic struct {
|
||||
payloadOnce sync.Once
|
||||
}
|
||||
|
||||
type ParseRuleFunc func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (C.Rule, error)
|
||||
|
||||
func NewSubRule(payload, adapter string, subRules map[string][]C.Rule, parseRule ParseRuleFunc) (*Logic, error) {
|
||||
func NewSubRule(payload, adapter string, subRules map[string][]C.Rule, parseRule common.ParseRuleFunc) (*Logic, error) {
|
||||
logic := &Logic{Base: &common.Base{}, payload: payload, adapter: adapter, ruleType: C.SubRules, subRules: subRules}
|
||||
err := logic.parsePayload(fmt.Sprintf("(%s)", payload), parseRule)
|
||||
if err != nil {
|
||||
@@ -38,7 +36,7 @@ func NewSubRule(payload, adapter string, subRules map[string][]C.Rule, parseRule
|
||||
return logic, nil
|
||||
}
|
||||
|
||||
func NewNOT(payload string, adapter string, parseRule ParseRuleFunc) (*Logic, error) {
|
||||
func NewNOT(payload string, adapter string, parseRule common.ParseRuleFunc) (*Logic, error) {
|
||||
logic := &Logic{Base: &common.Base{}, payload: payload, adapter: adapter, ruleType: C.NOT}
|
||||
err := logic.parsePayload(payload, parseRule)
|
||||
if err != nil {
|
||||
@@ -51,7 +49,7 @@ func NewNOT(payload string, adapter string, parseRule ParseRuleFunc) (*Logic, er
|
||||
return logic, nil
|
||||
}
|
||||
|
||||
func NewOR(payload string, adapter string, parseRule ParseRuleFunc) (*Logic, error) {
|
||||
func NewOR(payload string, adapter string, parseRule common.ParseRuleFunc) (*Logic, error) {
|
||||
logic := &Logic{Base: &common.Base{}, payload: payload, adapter: adapter, ruleType: C.OR}
|
||||
err := logic.parsePayload(payload, parseRule)
|
||||
if err != nil {
|
||||
@@ -60,7 +58,7 @@ func NewOR(payload string, adapter string, parseRule ParseRuleFunc) (*Logic, err
|
||||
return logic, nil
|
||||
}
|
||||
|
||||
func NewAND(payload string, adapter string, parseRule ParseRuleFunc) (*Logic, error) {
|
||||
func NewAND(payload string, adapter string, parseRule common.ParseRuleFunc) (*Logic, error) {
|
||||
logic := &Logic{Base: &common.Base{}, payload: payload, adapter: adapter, ruleType: C.AND}
|
||||
err := logic.parsePayload(payload, parseRule)
|
||||
if err != nil {
|
||||
@@ -79,7 +77,7 @@ func (r Range) containRange(preStart, preEnd int) bool {
|
||||
return preStart < r.start && preEnd > r.end
|
||||
}
|
||||
|
||||
func (logic *Logic) payloadToRule(subPayload string, parseRule ParseRuleFunc) (C.Rule, error) {
|
||||
func (logic *Logic) payloadToRule(subPayload string, parseRule common.ParseRuleFunc) (C.Rule, error) {
|
||||
splitStr := strings.SplitN(subPayload, ",", 2)
|
||||
if len(splitStr) < 2 {
|
||||
return nil, fmt.Errorf("[%s] format is error", subPayload)
|
||||
@@ -160,7 +158,7 @@ func (logic *Logic) findSubRuleRange(payload string, ruleRanges []Range) []Range
|
||||
return subRuleRange
|
||||
}
|
||||
|
||||
func (logic *Logic) parsePayload(payload string, parseRule ParseRuleFunc) error {
|
||||
func (logic *Logic) parsePayload(payload string, parseRule common.ParseRuleFunc) error {
|
||||
regex, err := regexp.Compile("\\(.*\\)")
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -91,3 +91,5 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var _ RC.ParseRuleFunc = ParseRule
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
P "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/rules/common"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -16,16 +17,18 @@ var (
|
||||
)
|
||||
|
||||
type ruleProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Behavior string `provider:"behavior"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Proxy string `provider:"proxy,omitempty"`
|
||||
Format string `provider:"format,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Type string `provider:"type"`
|
||||
Behavior string `provider:"behavior"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Proxy string `provider:"proxy,omitempty"`
|
||||
Format string `provider:"format,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
SizeLimit int64 `provider:"size-limit,omitempty"`
|
||||
Payload []string `provider:"payload,omitempty"`
|
||||
}
|
||||
|
||||
func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) (P.RuleProvider, error) {
|
||||
func ParseRuleProvider(name string, mapping map[string]any, parse common.ParseRuleFunc) (P.RuleProvider, error) {
|
||||
schema := &ruleProviderSchema{}
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
||||
if err := decoder.Decode(mapping, schema); err != nil {
|
||||
@@ -53,7 +56,9 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, nil, resource.DefaultHttpTimeout)
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, nil, resource.DefaultHttpTimeout, schema.SizeLimit)
|
||||
case "inline":
|
||||
return NewInlineProvider(name, behavior, schema.Payload, parse), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
P "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/rules/common"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -23,17 +24,6 @@ func SetTunnel(t P.Tunnel) {
|
||||
tunnel = t
|
||||
}
|
||||
|
||||
type ruleSetProvider struct {
|
||||
*resource.Fetcher[ruleStrategy]
|
||||
behavior P.RuleBehavior
|
||||
format P.RuleFormat
|
||||
strategy ruleStrategy
|
||||
}
|
||||
|
||||
type RuleSetProvider struct {
|
||||
*ruleSetProvider
|
||||
}
|
||||
|
||||
type RulePayload struct {
|
||||
/**
|
||||
key: Domain or IP Cidr
|
||||
@@ -43,6 +33,17 @@ type RulePayload struct {
|
||||
Rules []string `yaml:"rules"`
|
||||
}
|
||||
|
||||
type providerForApi struct {
|
||||
Behavior string `json:"behavior"`
|
||||
Format string `json:"format"`
|
||||
Name string `json:"name"`
|
||||
RuleCount int `json:"ruleCount"`
|
||||
Type string `json:"type"`
|
||||
VehicleType string `json:"vehicleType"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Payload []string `json:"payload,omitempty"`
|
||||
}
|
||||
|
||||
type ruleStrategy interface {
|
||||
Behavior() P.RuleBehavior
|
||||
Match(metadata *C.Metadata) bool
|
||||
@@ -61,10 +62,49 @@ type mrsRuleStrategy interface {
|
||||
DumpMrs(f func(key string) bool)
|
||||
}
|
||||
|
||||
func (rp *ruleSetProvider) Type() P.ProviderType {
|
||||
type baseProvider struct {
|
||||
behavior P.RuleBehavior
|
||||
strategy ruleStrategy
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Type() P.ProviderType {
|
||||
return P.Rule
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Behavior() P.RuleBehavior {
|
||||
return bp.behavior
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Count() int {
|
||||
return bp.strategy.Count()
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Match(metadata *C.Metadata) bool {
|
||||
return bp.strategy != nil && bp.strategy.Match(metadata)
|
||||
}
|
||||
|
||||
func (bp *baseProvider) ShouldResolveIP() bool {
|
||||
return bp.strategy.ShouldResolveIP()
|
||||
}
|
||||
|
||||
func (bp *baseProvider) ShouldFindProcess() bool {
|
||||
return bp.strategy.ShouldFindProcess()
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Strategy() any {
|
||||
return bp.strategy
|
||||
}
|
||||
|
||||
type ruleSetProvider struct {
|
||||
baseProvider
|
||||
*resource.Fetcher[ruleStrategy]
|
||||
format P.RuleFormat
|
||||
}
|
||||
|
||||
type RuleSetProvider struct {
|
||||
*ruleSetProvider
|
||||
}
|
||||
|
||||
func (rp *ruleSetProvider) Initial() error {
|
||||
_, err := rp.Fetcher.Initial()
|
||||
return err
|
||||
@@ -75,40 +115,16 @@ func (rp *ruleSetProvider) Update() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (rp *ruleSetProvider) Behavior() P.RuleBehavior {
|
||||
return rp.behavior
|
||||
}
|
||||
|
||||
func (rp *ruleSetProvider) Count() int {
|
||||
return rp.strategy.Count()
|
||||
}
|
||||
|
||||
func (rp *ruleSetProvider) Match(metadata *C.Metadata) bool {
|
||||
return rp.strategy != nil && rp.strategy.Match(metadata)
|
||||
}
|
||||
|
||||
func (rp *ruleSetProvider) ShouldResolveIP() bool {
|
||||
return rp.strategy.ShouldResolveIP()
|
||||
}
|
||||
|
||||
func (rp *ruleSetProvider) ShouldFindProcess() bool {
|
||||
return rp.strategy.ShouldFindProcess()
|
||||
}
|
||||
|
||||
func (rp *ruleSetProvider) Strategy() any {
|
||||
return rp.strategy
|
||||
}
|
||||
|
||||
func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(
|
||||
map[string]interface{}{
|
||||
"behavior": rp.behavior.String(),
|
||||
"format": rp.format.String(),
|
||||
"name": rp.Name(),
|
||||
"ruleCount": rp.strategy.Count(),
|
||||
"type": rp.Type().String(),
|
||||
"updatedAt": rp.UpdatedAt(),
|
||||
"vehicleType": rp.VehicleType().String(),
|
||||
providerForApi{
|
||||
Behavior: rp.behavior.String(),
|
||||
Format: rp.format.String(),
|
||||
Name: rp.Fetcher.Name(),
|
||||
RuleCount: rp.strategy.Count(),
|
||||
Type: rp.Type().String(),
|
||||
UpdatedAt: rp.UpdatedAt(),
|
||||
VehicleType: rp.VehicleType().String(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -117,11 +133,12 @@ func (rp *RuleSetProvider) Close() error {
|
||||
return rp.ruleSetProvider.Close()
|
||||
}
|
||||
|
||||
func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleFormat, interval time.Duration, vehicle P.Vehicle,
|
||||
parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) P.RuleProvider {
|
||||
func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleFormat, interval time.Duration, vehicle P.Vehicle, parse common.ParseRuleFunc) P.RuleProvider {
|
||||
rp := &ruleSetProvider{
|
||||
behavior: behavior,
|
||||
format: format,
|
||||
baseProvider: baseProvider{
|
||||
behavior: behavior,
|
||||
},
|
||||
format: format,
|
||||
}
|
||||
|
||||
onUpdate := func(strategy ruleStrategy) {
|
||||
@@ -142,7 +159,7 @@ func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleForma
|
||||
return wrapper
|
||||
}
|
||||
|
||||
func newStrategy(behavior P.RuleBehavior, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) ruleStrategy {
|
||||
func newStrategy(behavior P.RuleBehavior, parse common.ParseRuleFunc) ruleStrategy {
|
||||
switch behavior {
|
||||
case P.Domain:
|
||||
strategy := NewDomainStrategy()
|
||||
@@ -158,8 +175,10 @@ func newStrategy(behavior P.RuleBehavior, parse func(tp, payload, target string,
|
||||
}
|
||||
}
|
||||
|
||||
var ErrNoPayload = errors.New("file must have a `payload` field")
|
||||
var ErrInvalidFormat = errors.New("invalid format")
|
||||
var (
|
||||
ErrNoPayload = errors.New("file must have a `payload` field")
|
||||
ErrInvalidFormat = errors.New("invalid format")
|
||||
)
|
||||
|
||||
func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (ruleStrategy, error) {
|
||||
strategy.Reset()
|
||||
@@ -254,3 +273,76 @@ func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (ruleStr
|
||||
|
||||
return strategy, nil
|
||||
}
|
||||
|
||||
func rulesParseInline(rs []string, strategy ruleStrategy) ruleStrategy {
|
||||
strategy.Reset()
|
||||
for _, r := range rs {
|
||||
if r != "" {
|
||||
strategy.Insert(r)
|
||||
}
|
||||
}
|
||||
strategy.FinishInsert()
|
||||
return strategy
|
||||
}
|
||||
|
||||
type InlineProvider struct {
|
||||
*inlineProvider
|
||||
}
|
||||
|
||||
type inlineProvider struct {
|
||||
baseProvider
|
||||
name string
|
||||
updateAt time.Time
|
||||
payload []string
|
||||
}
|
||||
|
||||
func (i *inlineProvider) Name() string {
|
||||
return i.name
|
||||
}
|
||||
|
||||
func (i *inlineProvider) Initial() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *inlineProvider) Update() error {
|
||||
// make api update happy
|
||||
i.updateAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *inlineProvider) VehicleType() P.VehicleType {
|
||||
return P.Inline
|
||||
}
|
||||
|
||||
func (i *inlineProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(
|
||||
providerForApi{
|
||||
Behavior: i.behavior.String(),
|
||||
Name: i.Name(),
|
||||
RuleCount: i.strategy.Count(),
|
||||
Type: i.Type().String(),
|
||||
VehicleType: i.VehicleType().String(),
|
||||
UpdatedAt: i.updateAt,
|
||||
Payload: i.payload,
|
||||
})
|
||||
}
|
||||
|
||||
func NewInlineProvider(name string, behavior P.RuleBehavior, payload []string, parse common.ParseRuleFunc) P.RuleProvider {
|
||||
ip := &inlineProvider{
|
||||
baseProvider: baseProvider{
|
||||
behavior: behavior,
|
||||
strategy: newStrategy(behavior, parse),
|
||||
},
|
||||
payload: payload,
|
||||
name: name,
|
||||
updateAt: time.Now(),
|
||||
}
|
||||
ip.strategy = rulesParseInline(payload, ip.strategy)
|
||||
|
||||
wrapper := &InlineProvider{
|
||||
ip,
|
||||
}
|
||||
|
||||
//runtime.SetFinalizer(wrapper, (*InlineProvider).Close)
|
||||
return wrapper
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"github.com/shirou/gopsutil/v3/process"
|
||||
"github.com/shirou/gopsutil/v4/process"
|
||||
)
|
||||
|
||||
var DefaultManager *Manager
|
||||
|
||||
@@ -117,24 +117,19 @@ func (tt *tcpTracker) Upstream() any {
|
||||
}
|
||||
|
||||
func parseRemoteDestination(addr net.Addr, conn C.Connection) string {
|
||||
if addr == nil && conn != nil {
|
||||
return conn.RemoteDestination()
|
||||
}
|
||||
if addrPort, err := netip.ParseAddrPort(addr.String()); err == nil && addrPort.Addr().IsValid() {
|
||||
return addrPort.Addr().String()
|
||||
} else {
|
||||
if conn != nil {
|
||||
return conn.RemoteDestination()
|
||||
} else {
|
||||
return ""
|
||||
if addr != nil {
|
||||
if addrPort, err := netip.ParseAddrPort(addr.String()); err == nil && addrPort.Addr().IsValid() {
|
||||
return addrPort.Addr().String()
|
||||
}
|
||||
}
|
||||
if conn != nil {
|
||||
return conn.RemoteDestination()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64, pushToManager bool) *tcpTracker {
|
||||
if conn != nil {
|
||||
metadata.RemoteDst = parseRemoteDestination(conn.RemoteAddr(), conn)
|
||||
}
|
||||
metadata.RemoteDst = parseRemoteDestination(conn.RemoteAddr(), conn)
|
||||
|
||||
t := &tcpTracker{
|
||||
Conn: conn,
|
||||
|
||||
Reference in New Issue
Block a user