diff --git a/adapter/outbound/wireguard.go b/adapter/outbound/wireguard.go index f136a20a..834e5fe2 100644 --- a/adapter/outbound/wireguard.go +++ b/adapter/outbound/wireguard.go @@ -15,6 +15,7 @@ import ( CN "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/proxydialer" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" @@ -48,6 +49,7 @@ type WireGuardOption struct { MTU int `proxy:"mtu,omitempty"` UDP bool `proxy:"udp,omitempty"` PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"` + DialerProxy string `proxy:"dialer-proxy,omitempty"` Peers []WireGuardPeerOption `proxy:"peers,omitempty"` } @@ -64,17 +66,34 @@ type WireGuardPeerOption struct { } type wgSingDialer struct { - dialer dialer.Dialer + dialer dialer.Dialer + proxyName string } var _ N.Dialer = (*wgSingDialer)(nil) func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - return d.dialer.DialContext(ctx, network, destination.String()) + var cDialer C.Dialer = d.dialer + if len(d.proxyName) > 0 { + pd, err := proxydialer.NewByName(d.proxyName, d.dialer) + if err != nil { + return nil, err + } + cDialer = pd + } + return cDialer.DialContext(ctx, network, destination.String()) } func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return d.dialer.ListenPacket(ctx, "udp", "", destination.AddrPort()) + var cDialer C.Dialer = d.dialer + if len(d.proxyName) > 0 { + pd, err := proxydialer.NewByName(d.proxyName, d.dialer) + if err != nil { + return nil, err + } + cDialer = pd + } + return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort()) } type wgNetDialer struct { @@ -130,7 +149,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), }, - dialer: &wgSingDialer{dialer: dialer.NewDialer()}, + dialer: &wgSingDialer{dialer: dialer.NewDialer(), proxyName: option.DialerProxy}, } runtime.SetFinalizer(outbound, closeWireGuard) diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index e17ae132..74c2f73b 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -3,13 +3,9 @@ package outboundgroup import ( "context" "encoding/json" - "net" - "net/netip" - "strings" - "github.com/Dreamacro/clash/adapter/outbound" - N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/proxydialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" ) @@ -18,36 +14,6 @@ type Relay struct { *GroupBase } -type proxyDialer struct { - proxy C.Proxy - dialer C.Dialer -} - -func (p proxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { - currentMeta, err := addrToMetadata(address) - if err != nil { - return nil, err - } - if strings.Contains(network, "udp") { // should not support this operation - currentMeta.NetWork = C.UDP - pc, err := p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta) - if err != nil { - return nil, err - } - return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil - } - return p.proxy.DialContextWithDialer(ctx, p.dialer, currentMeta) -} - -func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) { - currentMeta, err := addrToMetadata(rAddrPort.String()) - if err != nil { - return nil, err - } - currentMeta.NetWork = C.UDP - return p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta) -} - // DialContext implements C.ProxyAdapter func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { proxies, chainProxies := r.proxies(metadata, true) @@ -61,10 +27,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d var d C.Dialer d = dialer.NewDialer(r.Base.DialOptions(opts...)...) for _, proxy := range proxies[:len(proxies)-1] { - d = proxyDialer{ - proxy: proxy, - dialer: d, - } + d = proxydialer.New(proxy, d) } last := proxies[len(proxies)-1] conn, err := last.DialContextWithDialer(ctx, d, metadata) @@ -95,10 +58,7 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o var d C.Dialer d = dialer.NewDialer(r.Base.DialOptions(opts...)...) for _, proxy := range proxies[:len(proxies)-1] { - d = proxyDialer{ - proxy: proxy, - dialer: d, - } + d = proxydialer.New(proxy, d) } last := proxies[len(proxies)-1] pc, err := last.ListenPacketWithDialer(ctx, d, metadata) diff --git a/adapter/outboundgroup/util.go b/adapter/outboundgroup/util.go index 578011f8..adcda1aa 100644 --- a/adapter/outboundgroup/util.go +++ b/adapter/outboundgroup/util.go @@ -1,37 +1,10 @@ package outboundgroup import ( - "fmt" "net" - "net/netip" "time" - - C "github.com/Dreamacro/clash/constant" ) -func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) { - host, port, err := net.SplitHostPort(rawAddress) - if err != nil { - err = fmt.Errorf("addrToMetadata failed: %w", err) - return - } - - if ip, err := netip.ParseAddr(host); err != nil { - addr = &C.Metadata{ - Host: host, - DstPort: port, - } - } else { - addr = &C.Metadata{ - Host: "", - DstIP: ip.Unmap(), - DstPort: port, - } - } - - return -} - func tcpKeepAlive(c net.Conn) { if tcp, ok := c.(*net.TCPConn); ok { _ = tcp.SetKeepAlive(true) diff --git a/component/proxydialer/proxydialer.go b/component/proxydialer/proxydialer.go new file mode 100644 index 00000000..83428d59 --- /dev/null +++ b/component/proxydialer/proxydialer.go @@ -0,0 +1,78 @@ +package proxydialer + +import ( + "context" + "fmt" + "net" + "net/netip" + "strings" + + N "github.com/Dreamacro/clash/common/net" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/tunnel" +) + +type proxyDialer struct { + proxy C.Proxy + dialer C.Dialer +} + +func New(proxy C.Proxy, dialer C.Dialer) C.Dialer { + return proxyDialer{proxy: proxy, dialer: dialer} +} + +func NewByName(proxyName string, dialer C.Dialer) (C.Dialer, error) { + proxies := tunnel.Proxies() + if proxy, ok := proxies[proxyName]; ok { + return New(proxy, dialer), nil + } + return nil, fmt.Errorf("proxyName[%s] not found", proxyName) +} + +func (p proxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { + currentMeta, err := addrToMetadata(address) + if err != nil { + return nil, err + } + if strings.Contains(network, "udp") { // using in wireguard outbound + currentMeta.NetWork = C.UDP + pc, err := p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta) + if err != nil { + return nil, err + } + return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil + } + return p.proxy.DialContextWithDialer(ctx, p.dialer, currentMeta) +} + +func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) { + currentMeta, err := addrToMetadata(rAddrPort.String()) + if err != nil { + return nil, err + } + currentMeta.NetWork = C.UDP + return p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta) +} + +func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) { + host, port, err := net.SplitHostPort(rawAddress) + if err != nil { + err = fmt.Errorf("addrToMetadata failed: %w", err) + return + } + + if ip, err := netip.ParseAddr(host); err != nil { + addr = &C.Metadata{ + Host: host, + DstPort: port, + } + } else { + addr = &C.Metadata{ + Host: "", + DstIP: ip.Unmap(), + DstPort: port, + } + } + + return +} diff --git a/docs/config.yaml b/docs/config.yaml index 66eff537..11891478 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -629,6 +629,8 @@ proxies: # socks5 reserved: "U4An" # 数组格式也是合法的 # reserved: [209,98,59] + # 一个出站代理的标识。当值不为空时,将使用指定的 proxy 发出连接 + # dialer-proxy: "ss1" # 如果peers不为空,该段落中的allowed_ips不可为空;前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定 # peers: # - server: 162.159.192.1