Feature: add tunnels
This commit is contained in:
parent
de264c42a8
commit
5b07d7b776
10 changed files with 416 additions and 35 deletions
|
@ -22,6 +22,7 @@ import (
|
|||
R "github.com/Dreamacro/clash/rule"
|
||||
T "github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
@ -98,6 +99,7 @@ type Config struct {
|
|||
Users []auth.AuthUser
|
||||
Proxies map[string]C.Proxy
|
||||
Providers map[string]providerTypes.ProxyProvider
|
||||
Tunnels []Tunnel
|
||||
}
|
||||
|
||||
type RawDNS struct {
|
||||
|
@ -122,6 +124,64 @@ type RawFallbackFilter struct {
|
|||
Domain []string `yaml:"domain"`
|
||||
}
|
||||
|
||||
type tunnel struct {
|
||||
Network []string `yaml:"network"`
|
||||
Address string `yaml:"address"`
|
||||
Target string `yaml:"target"`
|
||||
Proxy string `yaml:"proxy"`
|
||||
}
|
||||
|
||||
type Tunnel tunnel
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler
|
||||
func (t *Tunnel) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var tp string
|
||||
if err := unmarshal(&tp); err != nil {
|
||||
var inner tunnel
|
||||
if err := unmarshal(&inner); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = Tunnel(inner)
|
||||
return nil
|
||||
}
|
||||
|
||||
// parse udp/tcp,address,target,proxy
|
||||
parts := lo.Map(strings.Split(tp, ","), func(s string, _ int) string {
|
||||
return strings.TrimSpace(s)
|
||||
})
|
||||
if len(parts) != 4 {
|
||||
return fmt.Errorf("invalid tunnel config %s", tp)
|
||||
}
|
||||
network := strings.Split(parts[0], "/")
|
||||
|
||||
// validate network
|
||||
for _, n := range network {
|
||||
switch n {
|
||||
case "tcp", "udp":
|
||||
default:
|
||||
return fmt.Errorf("invalid tunnel network %s", n)
|
||||
}
|
||||
}
|
||||
|
||||
// validate address and target
|
||||
address := parts[1]
|
||||
target := parts[2]
|
||||
for _, addr := range []string{address, target} {
|
||||
if _, _, err := net.SplitHostPort(addr); err != nil {
|
||||
return fmt.Errorf("invalid tunnel target or address %s", addr)
|
||||
}
|
||||
}
|
||||
|
||||
*t = Tunnel(tunnel{
|
||||
Network: network,
|
||||
Address: address,
|
||||
Target: target,
|
||||
Proxy: parts[3],
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
type RawConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
SocksPort int `yaml:"socks-port"`
|
||||
|
@ -139,6 +199,7 @@ type RawConfig struct {
|
|||
Secret string `yaml:"secret"`
|
||||
Interface string `yaml:"interface-name"`
|
||||
RoutingMark int `yaml:"routing-mark"`
|
||||
Tunnels []Tunnel `yaml:"tunnels"`
|
||||
|
||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||
Hosts map[string]string `yaml:"hosts"`
|
||||
|
@ -237,6 +298,14 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||
|
||||
config.Users = parseAuthentication(rawCfg.Authentication)
|
||||
|
||||
config.Tunnels = rawCfg.Tunnels
|
||||
// verify tunnels
|
||||
for _, t := range config.Tunnels {
|
||||
if _, ok := config.Proxies[t.Proxy]; !ok {
|
||||
return nil, fmt.Errorf("tunnel proxy %s not found", t.Proxy)
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ const (
|
|||
SOCKS5
|
||||
REDIR
|
||||
TPROXY
|
||||
TUNNEL
|
||||
)
|
||||
|
||||
type NetWork int
|
||||
|
@ -61,15 +62,16 @@ func (t Type) MarshalJSON() ([]byte, error) {
|
|||
|
||||
// Metadata is used to store connection address
|
||||
type Metadata struct {
|
||||
NetWork NetWork `json:"network"`
|
||||
Type Type `json:"type"`
|
||||
SrcIP net.IP `json:"sourceIP"`
|
||||
DstIP net.IP `json:"destinationIP"`
|
||||
SrcPort string `json:"sourcePort"`
|
||||
DstPort string `json:"destinationPort"`
|
||||
Host string `json:"host"`
|
||||
DNSMode DNSMode `json:"dnsMode"`
|
||||
ProcessPath string `json:"processPath"`
|
||||
NetWork NetWork `json:"network"`
|
||||
Type Type `json:"type"`
|
||||
SrcIP net.IP `json:"sourceIP"`
|
||||
DstIP net.IP `json:"destinationIP"`
|
||||
SrcPort string `json:"sourcePort"`
|
||||
DstPort string `json:"destinationPort"`
|
||||
Host string `json:"host"`
|
||||
DNSMode DNSMode `json:"dnsMode"`
|
||||
ProcessPath string `json:"processPath"`
|
||||
SpecialProxy string `json:"specialProxy"`
|
||||
}
|
||||
|
||||
func (m *Metadata) RemoteAddress() string {
|
||||
|
|
3
go.mod
3
go.mod
|
@ -12,6 +12,7 @@ require (
|
|||
github.com/mdlayher/netlink v1.6.2
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/oschwald/geoip2-golang v1.8.0
|
||||
github.com/samber/lo v1.35.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
|
@ -29,11 +30,11 @@ require (
|
|||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/josharian/native v1.0.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mdlayher/socket v0.2.3 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
|
|
11
go.sum
11
go.sum
|
@ -1,6 +1,5 @@
|
|||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -34,9 +33,7 @@ github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGu
|
|||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
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=
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
|
@ -50,6 +47,7 @@ github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM
|
|||
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
|
||||
github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||
|
@ -57,6 +55,8 @@ github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYx
|
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/samber/lo v1.35.0 h1:GlT8CV1GE+v97Y7MLF1wXvX6mjoxZ+hi61tj/ZcQwY0=
|
||||
github.com/samber/lo v1.35.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
|
@ -70,6 +70,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
|
@ -84,6 +85,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.1.1-0.20221024173537-a3485e174077 h1:t5bjOfJPQfaG9NV1imLZM5E2uzaLGs5/NtyMtRNVjQ4=
|
||||
golang.org/x/crypto v0.1.1-0.20221024173537-a3485e174077/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
|
||||
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
|
@ -152,7 +155,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/constant/provider"
|
||||
"github.com/Dreamacro/clash/dns"
|
||||
P "github.com/Dreamacro/clash/listener"
|
||||
"github.com/Dreamacro/clash/listener"
|
||||
authStore "github.com/Dreamacro/clash/listener/auth"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
@ -75,10 +75,11 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||
updateGeneral(cfg.General, force)
|
||||
updateDNS(cfg.DNS)
|
||||
updateExperimental(cfg)
|
||||
updateTunnels(cfg.Tunnels)
|
||||
}
|
||||
|
||||
func GetGeneral() *config.General {
|
||||
ports := P.GetPorts()
|
||||
ports := listener.GetPorts()
|
||||
authenticator := []string{}
|
||||
if auth := authStore.Authenticator(); auth != nil {
|
||||
authenticator = auth.Users()
|
||||
|
@ -92,8 +93,8 @@ func GetGeneral() *config.General {
|
|||
TProxyPort: ports.TProxyPort,
|
||||
MixedPort: ports.MixedPort,
|
||||
Authentication: authenticator,
|
||||
AllowLan: P.AllowLan(),
|
||||
BindAddress: P.BindAddress(),
|
||||
AllowLan: listener.AllowLan(),
|
||||
BindAddress: listener.BindAddress(),
|
||||
},
|
||||
Mode: tunnel.Mode(),
|
||||
LogLevel: log.Level(),
|
||||
|
@ -161,6 +162,10 @@ func updateRules(rules []C.Rule) {
|
|||
tunnel.UpdateRules(rules)
|
||||
}
|
||||
|
||||
func updateTunnels(tunnels []config.Tunnel) {
|
||||
listener.PatchTunnel(tunnels, tunnel.TCPIn(), tunnel.UDPIn())
|
||||
}
|
||||
|
||||
func updateGeneral(general *config.General, force bool) {
|
||||
log.SetLevel(general.LogLevel)
|
||||
tunnel.SetMode(general.Mode)
|
||||
|
@ -176,19 +181,19 @@ func updateGeneral(general *config.General, force bool) {
|
|||
}
|
||||
|
||||
allowLan := general.AllowLan
|
||||
P.SetAllowLan(allowLan)
|
||||
listener.SetAllowLan(allowLan)
|
||||
|
||||
bindAddress := general.BindAddress
|
||||
P.SetBindAddress(bindAddress)
|
||||
listener.SetBindAddress(bindAddress)
|
||||
|
||||
tcpIn := tunnel.TCPIn()
|
||||
udpIn := tunnel.UDPIn()
|
||||
|
||||
P.ReCreateHTTP(general.Port, tcpIn)
|
||||
P.ReCreateSocks(general.SocksPort, tcpIn, udpIn)
|
||||
P.ReCreateRedir(general.RedirPort, tcpIn, udpIn)
|
||||
P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn)
|
||||
P.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
||||
listener.ReCreateHTTP(general.Port, tcpIn)
|
||||
listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn)
|
||||
listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn)
|
||||
listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn)
|
||||
listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
||||
}
|
||||
|
||||
func updateUsers(users []auth.AuthUser) {
|
||||
|
|
|
@ -1,34 +1,41 @@
|
|||
package proxy
|
||||
package listener
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/config"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/listener/http"
|
||||
"github.com/Dreamacro/clash/listener/mixed"
|
||||
"github.com/Dreamacro/clash/listener/redir"
|
||||
"github.com/Dreamacro/clash/listener/socks"
|
||||
"github.com/Dreamacro/clash/listener/tproxy"
|
||||
"github.com/Dreamacro/clash/listener/tunnel"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var (
|
||||
allowLan = false
|
||||
bindAddress = "*"
|
||||
|
||||
socksListener *socks.Listener
|
||||
socksUDPListener *socks.UDPListener
|
||||
httpListener *http.Listener
|
||||
redirListener *redir.Listener
|
||||
redirUDPListener *tproxy.UDPListener
|
||||
tproxyListener *tproxy.Listener
|
||||
tproxyUDPListener *tproxy.UDPListener
|
||||
mixedListener *mixed.Listener
|
||||
mixedUDPLister *socks.UDPListener
|
||||
socksListener *socks.Listener
|
||||
socksUDPListener *socks.UDPListener
|
||||
httpListener *http.Listener
|
||||
redirListener *redir.Listener
|
||||
redirUDPListener *tproxy.UDPListener
|
||||
tproxyListener *tproxy.Listener
|
||||
tproxyUDPListener *tproxy.UDPListener
|
||||
mixedListener *mixed.Listener
|
||||
mixedUDPLister *socks.UDPListener
|
||||
tunnelTCPListeners = map[string]*tunnel.Listener{}
|
||||
tunnelUDPListeners = map[string]*tunnel.PacketConn{}
|
||||
|
||||
// lock for recreate function
|
||||
socksMux sync.Mutex
|
||||
|
@ -36,6 +43,7 @@ var (
|
|||
redirMux sync.Mutex
|
||||
tproxyMux sync.Mutex
|
||||
mixedMux sync.Mutex
|
||||
tunnelMux sync.Mutex
|
||||
)
|
||||
|
||||
type Ports struct {
|
||||
|
@ -301,6 +309,95 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
|
|||
log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address())
|
||||
}
|
||||
|
||||
func PatchTunnel(tunnels []config.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
|
||||
tunnelMux.Lock()
|
||||
defer tunnelMux.Unlock()
|
||||
|
||||
type addrProxy struct {
|
||||
network string
|
||||
addr string
|
||||
target string
|
||||
proxy string
|
||||
}
|
||||
|
||||
tcpOld := lo.Map(
|
||||
lo.Keys(tunnelTCPListeners),
|
||||
func(key string, _ int) addrProxy {
|
||||
parts := strings.Split(key, "/")
|
||||
return addrProxy{
|
||||
network: "tcp",
|
||||
addr: parts[0],
|
||||
target: parts[1],
|
||||
proxy: parts[2],
|
||||
}
|
||||
},
|
||||
)
|
||||
udpOld := lo.Map(
|
||||
lo.Keys(tunnelUDPListeners),
|
||||
func(key string, _ int) addrProxy {
|
||||
parts := strings.Split(key, "/")
|
||||
return addrProxy{
|
||||
network: "udp",
|
||||
addr: parts[0],
|
||||
target: parts[1],
|
||||
proxy: parts[2],
|
||||
}
|
||||
},
|
||||
)
|
||||
oldElm := lo.Union(tcpOld, udpOld)
|
||||
|
||||
newElm := lo.FlatMap(
|
||||
tunnels,
|
||||
func(tunnel config.Tunnel, _ int) []addrProxy {
|
||||
return lo.Map(
|
||||
tunnel.Network,
|
||||
func(network string, _ int) addrProxy {
|
||||
return addrProxy{
|
||||
network: network,
|
||||
addr: tunnel.Address,
|
||||
target: tunnel.Target,
|
||||
proxy: tunnel.Proxy,
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
needClose, needCreate := lo.Difference(oldElm, newElm)
|
||||
|
||||
for _, elm := range needClose {
|
||||
key := fmt.Sprintf("%s/%s/%s", elm.addr, elm.target, elm.proxy)
|
||||
if elm.network == "tcp" {
|
||||
tunnelTCPListeners[key].Close()
|
||||
delete(tunnelTCPListeners, key)
|
||||
} else {
|
||||
tunnelUDPListeners[key].Close()
|
||||
delete(tunnelUDPListeners, key)
|
||||
}
|
||||
}
|
||||
|
||||
for _, elm := range needCreate {
|
||||
key := fmt.Sprintf("%s/%s/%s", elm.addr, elm.target, elm.proxy)
|
||||
if elm.network == "tcp" {
|
||||
l, err := tunnel.New(elm.addr, elm.target, elm.proxy, tcpIn)
|
||||
if err != nil {
|
||||
log.Errorln("Start tunnel %s error: %w", elm.target, err)
|
||||
continue
|
||||
}
|
||||
tunnelTCPListeners[key] = l
|
||||
log.Infoln("Tunnel(tcp/%s) proxy %s listening at: %s", elm.target, elm.proxy, tunnelTCPListeners[key].Address())
|
||||
} else {
|
||||
l, err := tunnel.NewUDP(elm.addr, elm.target, elm.proxy, udpIn)
|
||||
if err != nil {
|
||||
log.Errorln("Start tunnel %s error: %w", elm.target, err)
|
||||
continue
|
||||
}
|
||||
tunnelUDPListeners[key] = l
|
||||
log.Infoln("Tunnel(udp/%s) proxy %s listening at: %s", elm.target, elm.proxy, tunnelUDPListeners[key].Address())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetPorts return the ports of proxy servers
|
||||
func GetPorts() *Ports {
|
||||
ports := &Ports{}
|
||||
|
|
31
listener/tunnel/packet.go
Normal file
31
listener/tunnel/packet.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package tunnel
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
)
|
||||
|
||||
type packet struct {
|
||||
pc net.PacketConn
|
||||
rAddr net.Addr
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func (c *packet) Data() []byte {
|
||||
return c.payload
|
||||
}
|
||||
|
||||
// WriteBack write UDP packet with source(ip, port) = `addr`
|
||||
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
return c.pc.WriteTo(b, c.rAddr)
|
||||
}
|
||||
|
||||
// LocalAddr returns the source IP/Port of UDP Packet
|
||||
func (c *packet) LocalAddr() net.Addr {
|
||||
return c.rAddr
|
||||
}
|
||||
|
||||
func (c *packet) Drop() {
|
||||
pool.Put(c.payload)
|
||||
}
|
75
listener/tunnel/tcp.go
Normal file
75
listener/tunnel/tcp.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package tunnel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
listener net.Listener
|
||||
addr string
|
||||
target socks5.Addr
|
||||
proxy string
|
||||
closed bool
|
||||
}
|
||||
|
||||
// RawAddress implements C.Listener
|
||||
func (l *Listener) RawAddress() string {
|
||||
return l.addr
|
||||
}
|
||||
|
||||
// Address implements C.Listener
|
||||
func (l *Listener) Address() string {
|
||||
return l.listener.Addr().String()
|
||||
}
|
||||
|
||||
// Close implements C.Listener
|
||||
func (l *Listener) Close() error {
|
||||
l.closed = true
|
||||
return l.listener.Close()
|
||||
}
|
||||
|
||||
func (l *Listener) handleTCP(conn net.Conn, in chan<- C.ConnContext) {
|
||||
conn.(*net.TCPConn).SetKeepAlive(true)
|
||||
ctx := inbound.NewSocket(l.target, conn, C.TUNNEL)
|
||||
ctx.Metadata().SpecialProxy = l.proxy
|
||||
in <- ctx
|
||||
}
|
||||
|
||||
func New(addr, target, proxy string, in chan<- C.ConnContext) (*Listener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetAddr := socks5.ParseAddr(target)
|
||||
if targetAddr == nil {
|
||||
return nil, fmt.Errorf("invalid target address %s", target)
|
||||
}
|
||||
|
||||
rl := &Listener{
|
||||
listener: l,
|
||||
target: targetAddr,
|
||||
proxy: proxy,
|
||||
addr: addr,
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
if rl.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
go rl.handleTCP(c, in)
|
||||
}
|
||||
}()
|
||||
|
||||
return rl, nil
|
||||
}
|
85
listener/tunnel/udp.go
Normal file
85
listener/tunnel/udp.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package tunnel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/pool"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/transport/socks5"
|
||||
)
|
||||
|
||||
type PacketConn struct {
|
||||
conn net.PacketConn
|
||||
addr string
|
||||
target socks5.Addr
|
||||
proxy string
|
||||
closed bool
|
||||
}
|
||||
|
||||
// RawAddress implements C.Listener
|
||||
func (l *PacketConn) RawAddress() string {
|
||||
return l.addr
|
||||
}
|
||||
|
||||
// Address implements C.Listener
|
||||
func (l *PacketConn) Address() string {
|
||||
return l.conn.LocalAddr().String()
|
||||
}
|
||||
|
||||
// Close implements C.Listener
|
||||
func (l *PacketConn) Close() error {
|
||||
l.closed = true
|
||||
return l.conn.Close()
|
||||
}
|
||||
|
||||
func NewUDP(addr, target, proxy string, in chan<- *inbound.PacketAdapter) (*PacketConn, error) {
|
||||
l, err := net.ListenPacket("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetAddr := socks5.ParseAddr(target)
|
||||
if targetAddr == nil {
|
||||
return nil, fmt.Errorf("invalid target address %s", target)
|
||||
}
|
||||
|
||||
sl := &PacketConn{
|
||||
conn: l,
|
||||
target: targetAddr,
|
||||
proxy: proxy,
|
||||
addr: addr,
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
buf := pool.Get(pool.UDPBufferSize)
|
||||
n, remoteAddr, err := l.ReadFrom(buf)
|
||||
if err != nil {
|
||||
pool.Put(buf)
|
||||
if sl.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
sl.handleUDP(l, in, buf[:n], remoteAddr)
|
||||
}
|
||||
}()
|
||||
|
||||
return sl, nil
|
||||
}
|
||||
|
||||
func (l *PacketConn) handleUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) {
|
||||
packet := &packet{
|
||||
pc: pc,
|
||||
rAddr: addr,
|
||||
payload: buf,
|
||||
}
|
||||
|
||||
ctx := inbound.NewPacket(l.target, packet, C.TUNNEL)
|
||||
ctx.Metadata().SpecialProxy = l.proxy
|
||||
select {
|
||||
case in <- ctx:
|
||||
default:
|
||||
}
|
||||
}
|
|
@ -147,6 +147,15 @@ func preHandleMetadata(metadata *C.Metadata) error {
|
|||
}
|
||||
|
||||
func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) {
|
||||
if metadata.SpecialProxy != "" {
|
||||
var exist bool
|
||||
proxy, exist = proxies[metadata.SpecialProxy]
|
||||
if !exist {
|
||||
err = fmt.Errorf("proxy %s not found", metadata.SpecialProxy)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case Direct:
|
||||
proxy = proxies["DIRECT"]
|
||||
|
@ -249,6 +258,8 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
|
|||
pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule)
|
||||
|
||||
switch true {
|
||||
case metadata.SpecialProxy != "":
|
||||
log.Infoln("[UDP] %s --> %s using %s", metadata.SourceAddress(), metadata.RemoteAddress(), metadata.SpecialProxy)
|
||||
case rule != nil:
|
||||
log.Infoln(
|
||||
"[UDP] %s --> %s match %s(%s) using %s",
|
||||
|
@ -320,6 +331,8 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||
defer remoteConn.Close()
|
||||
|
||||
switch true {
|
||||
case metadata.SpecialProxy != "":
|
||||
log.Infoln("[TCP] %s --> %s using %s", metadata.SourceAddress(), metadata.RemoteAddress(), metadata.SpecialProxy)
|
||||
case rule != nil:
|
||||
log.Infoln(
|
||||
"[TCP] %s --> %s match %s(%s) using %s",
|
||||
|
|
Loading…
Reference in a new issue