diff --git a/adapter/inbound/http.go b/adapter/inbound/http.go index 7ef880d164..4557d47073 100644 --- a/adapter/inbound/http.go +++ b/adapter/inbound/http.go @@ -10,9 +10,15 @@ import ( // NewHTTP receive normal http request and return HTTPContext func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn) *context.ConnContext { + return NewHTTPWithInfos(target, source, conn, "", "") +} + +func NewHTTPWithInfos(target socks5.Addr, source net.Addr, conn net.Conn, inName, preferRulesName string) *context.ConnContext { metadata := parseSocksAddr(target) metadata.NetWork = C.TCP metadata.Type = C.HTTP + metadata.InName = inName + metadata.PreferRulesName = preferRulesName if ip, port, err := parseAddr(source.String()); err == nil { metadata.SrcIP = ip metadata.SrcPort = port diff --git a/adapter/inbound/https.go b/adapter/inbound/https.go index 53ad46b263..882802ef44 100644 --- a/adapter/inbound/https.go +++ b/adapter/inbound/https.go @@ -10,8 +10,14 @@ import ( // NewHTTPS receive CONNECT request and return ConnContext func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext { + return NewHTTPSWithInfos(request, conn, "", "") +} + +func NewHTTPSWithInfos(request *http.Request, conn net.Conn, inName, preferRulesName string) *context.ConnContext { metadata := parseHTTPAddr(request) metadata.Type = C.HTTPS + metadata.PreferRulesName = preferRulesName + metadata.InName = inName if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { metadata.SrcIP = ip metadata.SrcPort = port diff --git a/adapter/inbound/packet.go b/adapter/inbound/packet.go index 687e10894a..1b377d369e 100644 --- a/adapter/inbound/packet.go +++ b/adapter/inbound/packet.go @@ -5,22 +5,14 @@ import ( "github.com/Dreamacro/clash/transport/socks5" ) -// PacketAdapter is a UDP Packet adapter for socks/redir/tun -type PacketAdapter struct { - C.UDPPacket - metadata *C.Metadata -} -// Metadata returns destination metadata -func (s *PacketAdapter) Metadata() *C.Metadata { - return s.metadata -} -// NewPacket is PacketAdapter generator -func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *PacketAdapter { +func NewPacketWithInfos(target socks5.Addr, packet C.UDPPacket, source C.Type, inName , preferRulesName string) *C.PacketAdapter { metadata := parseSocksAddr(target) metadata.NetWork = C.UDP metadata.Type = source + metadata.InName = inName + metadata.PreferRulesName = preferRulesName if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil { metadata.SrcIP = ip metadata.SrcPort = port @@ -32,8 +24,13 @@ func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *PacketAda } } - return &PacketAdapter{ - UDPPacket: packet, - metadata: metadata, - } + return C.NewPacketAdapter( + packet, + metadata, + ) +} + +// NewPacket is PacketAdapter generator +func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *C.PacketAdapter { + return NewPacketWithInfos(target, packet, source, "", "") } diff --git a/adapter/inbound/socket.go b/adapter/inbound/socket.go index 6323161150..6bcdbced18 100644 --- a/adapter/inbound/socket.go +++ b/adapter/inbound/socket.go @@ -9,12 +9,14 @@ import ( "github.com/Dreamacro/clash/transport/socks5" ) -// NewSocket receive TCP inbound and return ConnContext -func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext { +func NewSocketWithInfos(target socks5.Addr, conn net.Conn, source C.Type, inName , preferRulesName string) *context.ConnContext { metadata := parseSocksAddr(target) metadata.NetWork = C.TCP metadata.Type = source + metadata.PreferRulesName = preferRulesName + metadata.InName = inName remoteAddr := conn.RemoteAddr() + // Filter when net.Addr interface is nil if remoteAddr != nil { if ip, port, err := parseAddr(remoteAddr.String()); err == nil { @@ -34,6 +36,11 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnCo return context.NewConnContext(conn, metadata) } +// NewSocket receive TCP inbound and return ConnContext +func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext { + return NewSocketWithInfos(target, conn, source, "", "") +} + func NewInner(conn net.Conn, dst string, host string) *context.ConnContext { metadata := &C.Metadata{} metadata.NetWork = C.TCP diff --git a/config/config.go b/config/config.go index 1d80a3a55c..5ac357b2f4 100644 --- a/config/config.go +++ b/config/config.go @@ -15,9 +15,11 @@ import ( "time" "github.com/Dreamacro/clash/common/utils" + "github.com/Dreamacro/clash/listener/sing_tun" + "github.com/Dreamacro/clash/listener/tunnel" R "github.com/Dreamacro/clash/rules" RP "github.com/Dreamacro/clash/rules/provider" - + L "github.com/Dreamacro/clash/listener" "github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outboundgroup" @@ -30,13 +32,11 @@ import ( "github.com/Dreamacro/clash/component/trie" C "github.com/Dreamacro/clash/constant" providerTypes "github.com/Dreamacro/clash/constant/provider" - "github.com/Dreamacro/clash/constant/sniffer" snifferTypes "github.com/Dreamacro/clash/constant/sniffer" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" T "github.com/Dreamacro/clash/tunnel" - "github.com/samber/lo" "gopkg.in/yaml.v3" ) @@ -149,11 +149,11 @@ type Tun struct { RedirectToTun []string `yaml:"-" json:"-"` MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` - Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"` - Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"` + Inet4Address []sing_tun.ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"` + Inet6Address []sing_tun.ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` - Inet4RouteAddress []ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` - Inet6RouteAddress []ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` + Inet4RouteAddress []sing_tun.ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` + Inet6RouteAddress []sing_tun.ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"` IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` @@ -165,56 +165,6 @@ type Tun struct { UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` } -type ListenPrefix netip.Prefix - -func (p ListenPrefix) MarshalJSON() ([]byte, error) { - prefix := netip.Prefix(p) - if !prefix.IsValid() { - return json.Marshal(nil) - } - return json.Marshal(prefix.String()) -} - -func (p ListenPrefix) MarshalYAML() (interface{}, error) { - prefix := netip.Prefix(p) - if !prefix.IsValid() { - return nil, nil - } - return prefix.String(), nil -} - -func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error { - var value string - err := json.Unmarshal(bytes, &value) - if err != nil { - return err - } - prefix, err := netip.ParsePrefix(value) - if err != nil { - return err - } - *p = ListenPrefix(prefix) - return nil -} - -func (p *ListenPrefix) UnmarshalYAML(node *yaml.Node) error { - var value string - err := node.Decode(&value) - if err != nil { - return err - } - prefix, err := netip.ParsePrefix(value) - if err != nil { - return err - } - *p = ListenPrefix(prefix) - return nil -} - -func (p ListenPrefix) Build() netip.Prefix { - return netip.Prefix(p) -} - // IPTables config type IPTables struct { Enable bool `yaml:"enable" json:"enable"` @@ -224,7 +174,7 @@ type IPTables struct { type Sniffer struct { Enable bool - Sniffers []sniffer.Type + Sniffers []snifferTypes.Type Reverses *trie.DomainTrie[struct{}] ForceDomain *trie.DomainTrie[struct{}] SkipDomain *trie.DomainTrie[struct{}] @@ -233,7 +183,6 @@ type Sniffer struct { ParsePureIp bool } - // Experimental config type Experimental struct { Fingerprints []string `yaml:"fingerprints"` @@ -248,12 +197,13 @@ type Config struct { Hosts *trie.DomainTrie[netip.Addr] Profile *Profile Rules []C.Rule - SubRules *map[string][]C.Rule + SubRules map[string][]C.Rule Users []auth.AuthUser Proxies map[string]C.Proxy + Listeners map[string]C.NewListener Providers map[string]providerTypes.ProxyProvider RuleProviders map[string]providerTypes.RuleProvider - Tunnels []Tunnel + Tunnels []tunnel.Tunnel Sniffer *Sniffer TLS *TLS } @@ -294,10 +244,10 @@ type RawTun struct { MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` //Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"` - Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"` + Inet6Address []sing_tun.ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"` - Inet4RouteAddress []ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"` - Inet6RouteAddress []ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"` + Inet4RouteAddress []sing_tun.ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"` + Inet6RouteAddress []sing_tun.ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"` IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"` IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"` ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"` @@ -322,65 +272,6 @@ type RawTuicServer struct { MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"` } -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) != 3 && 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, - }) - if len(parts) == 4 { - t.Proxy = parts[3] - } - return nil -} type RawConfig struct { Port int `yaml:"port"` @@ -404,7 +295,7 @@ type RawConfig struct { Secret string `yaml:"secret"` Interface string `yaml:"interface-name"` RoutingMark int `yaml:"routing-mark"` - Tunnels []Tunnel `yaml:"tunnels"` + Tunnels []tunnel.Tunnel `yaml:"tunnels"` GeodataMode bool `yaml:"geodata-mode"` GeodataLoader string `yaml:"geodata-loader"` TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` @@ -426,7 +317,8 @@ type RawConfig struct { ProxyGroup []map[string]any `yaml:"proxy-groups"` Rule []string `yaml:"rules"` SubRules map[string][]string `yaml:"sub-rules"` - RawTLS TLS `yaml:"tls"` + RawTLS TLS `yaml:"tls"` + Listeners []map[string]any `yaml:"listeners"` } type RawGeoXUrl struct { @@ -492,7 +384,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query AutoRoute: true, AutoDetectInterface: true, - Inet6Address: []ListenPrefix{ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126"))}, + Inet6Address: []sing_tun.ListenPrefix{sing_tun.ListenPrefix(netip.MustParsePrefix("fdfe:dcba:9876::1/126"))}, }, TuicServer: RawTuicServer{ Enable: false, @@ -576,7 +468,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config.Experimental = &rawCfg.Experimental config.Profile = &rawCfg.Profile config.IPTables = &rawCfg.IPTables - config.TLS=&rawCfg.RawTLS + config.TLS = &rawCfg.RawTLS general, err := parseGeneral(rawCfg) if err != nil { @@ -585,7 +477,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config.General = general dialer.DefaultInterface.Store(config.General.Interface) - proxies, providers, err := parseProxies(rawCfg) if err != nil { return nil, err @@ -593,6 +484,12 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config.Proxies = proxies config.Providers = providers + listener, err := parseListeners(rawCfg) + if err != nil { + return nil, err + } + config.Listeners = listener + subRules, ruleProviders, err := parseSubRules(rawCfg, proxies) if err != nil { return nil, err @@ -645,7 +542,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { return nil, err } - elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm @@ -677,9 +573,9 @@ func parseGeneral(cfg *RawConfig) (*General, error) { InboundTfo: cfg.InboundTfo, }, Controller: Controller{ - ExternalController: cfg.ExternalController, - ExternalUI: cfg.ExternalUI, - Secret: cfg.Secret, + ExternalController: cfg.ExternalController, + ExternalUI: cfg.ExternalUI, + Secret: cfg.Secret, ExternalControllerTLS: cfg.ExternalControllerTLS, }, UnifiedDelay: cfg.UnifiedDelay, @@ -799,9 +695,27 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ return proxies, providersMap, nil } -func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules *map[string][]C.Rule, ruleProviders map[string]providerTypes.RuleProvider, err error) { +func parseListeners(cfg *RawConfig) (listeners map[string]C.NewListener, err error) { + listeners = make(map[string]C.NewListener) + for index, mapping := range cfg.Listeners { + listener, err := L.ParseListener(mapping) + if err != nil { + return nil, fmt.Errorf("proxy %d: %w", index, err) + } + + if _, exist := mapping[listener.Name()]; exist { + return nil, fmt.Errorf("listener %s is the duplicate name", listener.Name()) + } + + listeners[listener.Name()] = listener + + } + return +} + +func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules map[string][]C.Rule, ruleProviders map[string]providerTypes.RuleProvider, err error) { ruleProviders = map[string]providerTypes.RuleProvider{} - subRules = &map[string][]C.Rule{} + subRules = map[string][]C.Rule{} log.Infoln("Geodata Loader mode: %s", geodata.LoaderName()) // parse rule provider for name, mapping := range cfg.RuleProvider { @@ -815,6 +729,9 @@ func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules *map[st } for name, rawRules := range cfg.SubRules { + if len(name) == 0 { + return nil, nil, fmt.Errorf("sub-rule name is empty") + } var rules []C.Rule for idx, line := range rawRules { rawRule := trimArr(strings.Split(line, ",")) @@ -860,7 +777,7 @@ func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules *map[st rules = append(rules, parsed) } - (*subRules)[name] = rules + subRules[name] = rules } if err = verifySubRule(subRules); err != nil { @@ -870,8 +787,8 @@ func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules *map[st return } -func verifySubRule(subRules *map[string][]C.Rule) error { - for name := range *subRules { +func verifySubRule(subRules map[string][]C.Rule) error { + for name := range subRules { err := verifySubRuleCircularReferences(name, subRules, []string{}) if err != nil { return err @@ -880,7 +797,7 @@ func verifySubRule(subRules *map[string][]C.Rule) error { return nil } -func verifySubRuleCircularReferences(n string, subRules *map[string][]C.Rule, arr []string) error { +func verifySubRuleCircularReferences(n string, subRules map[string][]C.Rule, arr []string) error { isInArray := func(v string, array []string) bool { for _, c := range array { if v == c { @@ -891,9 +808,9 @@ func verifySubRuleCircularReferences(n string, subRules *map[string][]C.Rule, ar } arr = append(arr, n) - for i, rule := range (*subRules)[n] { + for i, rule := range subRules[n] { if rule.RuleType() == C.SubRules { - if _, ok := (*subRules)[rule.Adapter()]; !ok { + if _, ok := subRules[rule.Adapter()]; !ok { return fmt.Errorf("sub-rule[%d:%s] error: [%s] not found", i, n, rule.Adapter()) } if isInArray(rule.Adapter(), arr) { @@ -909,7 +826,7 @@ func verifySubRuleCircularReferences(n string, subRules *map[string][]C.Rule, ar return nil } -func parseRules(cfg *RawConfig, proxies map[string]C.Proxy, subRules *map[string][]C.Rule) ([]C.Rule, error) { +func parseRules(cfg *RawConfig, proxies map[string]C.Proxy, subRules map[string][]C.Rule) ([]C.Rule, error) { var rules []C.Rule rulesConfig := cfg.Rule @@ -948,7 +865,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy, subRules *map[string if _, ok := proxies[target]; !ok { if ruleName != "SUB-RULE" { return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target) - } else if _, ok = (*subRules)[target]; !ok { + } else if _, ok = subRules[target]; !ok { return nil, fmt.Errorf("rules[%d] [%s] error: sub-rule [%s] not found", idx, line, target) } } @@ -1315,7 +1232,7 @@ func parseTun(rawTun RawTun, general *General) error { RedirectToTun: rawTun.RedirectToTun, MTU: rawTun.MTU, - Inet4Address: []ListenPrefix{ListenPrefix(tunAddressPrefix)}, + Inet4Address: []sing_tun.ListenPrefix{sing_tun.ListenPrefix(tunAddressPrefix)}, Inet6Address: rawTun.Inet6Address, StrictRoute: rawTun.StrictRoute, Inet4RouteAddress: rawTun.Inet4RouteAddress, diff --git a/constant/listener.go b/constant/listener.go index 08a590bd14..370bf892f8 100644 --- a/constant/listener.go +++ b/constant/listener.go @@ -13,3 +13,29 @@ type AdvanceListener interface { Config() string HandleConn(conn net.Conn, in chan<- ConnContext) } + +type NewListener interface { + Name() string + ReCreate(tcpIn chan<- ConnContext,udpIn chan<-*PacketAdapter) error + Close() error + Address() string + RawAddress() string +} + +// PacketAdapter is a UDP Packet adapter for socks/redir/tun +type PacketAdapter struct { + UDPPacket + metadata *Metadata +} + +func NewPacketAdapter(udppacket UDPPacket,metadata *Metadata)*PacketAdapter{ +return &PacketAdapter{ + udppacket, + metadata, +} +} + +// Metadata returns destination metadata +func (s *PacketAdapter) Metadata() *Metadata { + return s.metadata +} \ No newline at end of file diff --git a/constant/metadata.go b/constant/metadata.go index 4ab99f6bea..0102ba747f 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -117,21 +117,23 @@ 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 netip.Addr `json:"sourceIP"` - DstIP netip.Addr `json:"destinationIP"` - SrcPort string `json:"sourcePort"` - DstPort string `json:"destinationPort"` - InIP netip.Addr `json:"inboundIP"` - InPort string `json:"inboundPort"` - Host string `json:"host"` - DNSMode DNSMode `json:"dnsMode"` - Uid *uint32 `json:"uid"` - Process string `json:"process"` - ProcessPath string `json:"processPath"` - SpecialProxy string `json:"specialProxy"` - RemoteDst string `json:"remoteDestination"` + NetWork NetWork `json:"network"` + Type Type `json:"type"` + SrcIP netip.Addr `json:"sourceIP"` + DstIP netip.Addr `json:"destinationIP"` + SrcPort string `json:"sourcePort"` + DstPort string `json:"destinationPort"` + InIP netip.Addr `json:"inboundIP"` + InPort string `json:"inboundPort"` + Host string `json:"host"` + DNSMode DNSMode `json:"dnsMode"` + Uid *uint32 `json:"uid"` + Process string `json:"process"` + ProcessPath string `json:"processPath"` + SpecialProxy string `json:"specialProxy"` + RemoteDst string `json:"remoteDestination"` + InName string `jsson:"-"` + PreferRulesName string } func (m *Metadata) RemoteAddress() string { diff --git a/docs/config.yaml b/docs/config.yaml index e4bb542706..9507bbb83f 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -662,3 +662,36 @@ sub-rules: tls: certificate: string # 证书 PEM 格式,或者 证书的路径 private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径 + +# 流量入站 +listeners: + - name: socks5-in-1 + type: socks + port: 10808 + #listen: 0.0.0.0 # 默认监听 0.0.0.0 + # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules + # udp: false # 默认 true + - name: http-in-1 + type: http + port: 10809 + listen: 0.0.0.0 + # rule: sub-rule + - name: mixed-in-1 + type: mixed # HTTP(S) 和 SOCKS 代理混合 + port: 10810 + listen: 0.0.0.0 + # rule: sub-rule + # udp: false # 默认 true + + - name: reidr-in-1 + type: redir + port: 10811 + listen: 0.0.0.0 + # rule: sub-rule + + - name: tproxy-in-1 + type: tproxy + port: 10812 + listen: 0.0.0.0 + # udp: false # 默认 true + # rule: sub-rule diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 650242afe8..4a18b3c4c2 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -8,7 +8,6 @@ import ( "sync" "github.com/Dreamacro/clash/adapter" - "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/dialer" @@ -26,8 +25,12 @@ import ( "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/listener" authStore "github.com/Dreamacro/clash/listener/auth" + "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/listener/inner" + "github.com/Dreamacro/clash/listener/sing_tun" "github.com/Dreamacro/clash/listener/tproxy" + "github.com/Dreamacro/clash/listener/tuic" + T "github.com/Dreamacro/clash/listener/tunnel" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" ) @@ -77,7 +80,7 @@ func ApplyConfig(cfg *config.Config, force bool) { preUpdateExperimental(cfg) updateUsers(cfg.Users) updateProxies(cfg.Proxies, cfg.Providers) - updateRules(cfg.Rules, cfg.RuleProviders) + updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders) updateSniffer(cfg.Sniffer) updateHosts(cfg.Hosts) initInnerTcp() @@ -86,6 +89,7 @@ func ApplyConfig(cfg *config.Config, force bool) { updateProfile(cfg) loadRuleProvider(cfg.RuleProviders) updateGeneral(cfg.General, force) + updateListeners(cfg.Listeners) updateIPTables(cfg) updateTun(cfg.General) updateExperimental(cfg) @@ -122,8 +126,8 @@ func GetGeneral() *config.General { LogLevel: log.Level(), IPv6: !resolver.DisableIPv6, GeodataLoader: G.LoaderName(), - Tun: listener.GetTunConf(), - TuicServer: listener.GetTuicConf(), + Tun: config.Tun(listener.GetTunConf()), + TuicServer: config.TuicServer(listener.GetTuicConf()), Interface: dialer.DefaultInterface.Load(), Sniffing: tunnel.IsSniffing(), TCPConcurrent: dialer.GetDial(), @@ -132,6 +136,16 @@ func GetGeneral() *config.General { return general } +func updateListeners(listeners map[string]C.NewListener) { + tcpIn := tunnel.TCPIn() + udpIn := tunnel.UDPIn() + for _, listener := range listeners { + if err := listener.ReCreate(tcpIn, udpIn); err != nil { + log.Errorln("Listener %s listen err: %s", listener.Name(), err.Error()) + } + } +} + func updateExperimental(c *config.Config) { runtime.GC() } @@ -203,8 +217,8 @@ func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.Pro tunnel.UpdateProxies(proxies, providers) } -func updateRules(rules []C.Rule, ruleProviders map[string]provider.RuleProvider) { - tunnel.UpdateRules(rules, ruleProviders) +func updateRules(rules []C.Rule, subRules map[string][]C.Rule, ruleProviders map[string]provider.RuleProvider) { + tunnel.UpdateRules(rules, subRules, ruleProviders) } func loadProvider(pv provider.Provider) { @@ -267,7 +281,7 @@ func updateTun(general *config.General) { if general == nil { return } - listener.ReCreateTun(general.Tun, tunnel.TCPIn(), tunnel.UDPIn()) + listener.ReCreateTun(sing_tun.Tun(general.Tun), tunnel.TCPIn(), tunnel.UDPIn()) listener.ReCreateRedirToTun(general.Tun.RedirectToTun) } @@ -294,7 +308,7 @@ func updateSniffer(sniffer *config.Sniffer) { } } -func updateTunnels(tunnels []config.Tunnel) { +func updateTunnels(tunnels []T.Tunnel) { listener.PatchTunnel(tunnels, tunnel.TCPIn(), tunnel.UDPIn()) } @@ -353,7 +367,7 @@ func updateGeneral(general *config.General, force bool) { listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn) listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn) listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn) - listener.ReCreateTuic(general.TuicServer, tcpIn, udpIn) + listener.ReCreateTuic(tuic.TuicServer(general.TuicServer), tcpIn, udpIn) } func updateUsers(users []auth.AuthUser) { diff --git a/hub/route/configs.go b/hub/route/configs.go index 26b6eb6d7f..4b3ca32870 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -13,6 +13,8 @@ import ( C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/hub/executor" P "github.com/Dreamacro/clash/listener" + "github.com/Dreamacro/clash/listener/sing_tun" + "github.com/Dreamacro/clash/listener/tuic" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" @@ -67,10 +69,10 @@ type tunSchema struct { MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"` //Inet4Address *[]config.ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"` - Inet6Address *[]config.ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"` + Inet6Address *[]sing_tun.ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"` StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"` - Inet4RouteAddress *[]config.ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` - Inet6RouteAddress *[]config.ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` + Inet4RouteAddress *[]sing_tun.ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` + Inet6RouteAddress *[]sing_tun.ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"` IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` @@ -116,7 +118,7 @@ func pointerOrDefaultString(p *string, def string) string { return def } -func pointerOrDefaultTun(p *tunSchema, def config.Tun) config.Tun { +func pointerOrDefaultTun(p *tunSchema, def sing_tun.Tun) sing_tun.Tun { if p != nil { def.Enable = p.Enable if p.Device != nil { @@ -174,7 +176,7 @@ func pointerOrDefaultTun(p *tunSchema, def config.Tun) config.Tun { return def } -func pointerOrDefaultTuicServer(p *tuicServerSchema, def config.TuicServer) config.TuicServer { +func pointerOrDefaultTuicServer(p *tuicServerSchema, def tuic.TuicServer) tuic.TuicServer { if p != nil { def.Enable = p.Enable if p.Listen != nil { diff --git a/listener/autoredir/tcp.go b/listener/autoredir/tcp.go index efcd668bc1..936bffb5fd 100644 --- a/listener/autoredir/tcp.go +++ b/listener/autoredir/tcp.go @@ -14,6 +14,8 @@ type Listener struct { listener net.Listener addr string closed bool + name string + preferRulesName string lookupFunc func(netip.AddrPort) (socks5.Addr, error) } @@ -56,10 +58,14 @@ func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) { _ = conn.(*net.TCPConn).SetKeepAlive(true) - in <- inbound.NewSocket(target, conn, C.REDIR) + in <- inbound.NewSocketWithInfos(target, conn, C.REDIR,l.name,l.preferRulesName) } func New(addr string, in chan<- C.ConnContext) (*Listener, error) { + return NewWithInfos(addr,"DEFAULT-REDIR","",in) +} + +func NewWithInfos(addr ,name,preferRulesName string, in chan<- C.ConnContext) (*Listener, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err @@ -67,6 +73,8 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) { rl := &Listener{ listener: l, addr: addr, + name:name, + preferRulesName: preferRulesName, } go func() { diff --git a/listener/http/client.go b/listener/http/client.go index 873a9a3c9b..8b21f98bf3 100644 --- a/listener/http/client.go +++ b/listener/http/client.go @@ -12,7 +12,7 @@ import ( "github.com/Dreamacro/clash/transport/socks5" ) -func newClient(source net.Addr, in chan<- C.ConnContext) *http.Client { +func newClient(source net.Addr,name,preferRulesName string, in chan<- C.ConnContext) *http.Client { return &http.Client{ Transport: &http.Transport{ // from http.DefaultTransport @@ -32,7 +32,7 @@ func newClient(source net.Addr, in chan<- C.ConnContext) *http.Client { left, right := net.Pipe() - in <- inbound.NewHTTP(dstAddr, source, right) + in <- inbound.NewHTTPWithInfos(dstAddr, source, right,name,preferRulesName) return left, nil }, diff --git a/listener/http/proxy.go b/listener/http/proxy.go index 1c10732b94..74d9d77e91 100644 --- a/listener/http/proxy.go +++ b/listener/http/proxy.go @@ -14,8 +14,8 @@ import ( "github.com/Dreamacro/clash/log" ) -func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool]) { - client := newClient(c.RemoteAddr(), in) +func HandleConn(name, preferRulesName string, c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool]) { + client := newClient(c.RemoteAddr(), name, preferRulesName, in) defer client.CloseIdleConnections() conn := N.NewBufferedConn(c) @@ -48,7 +48,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin break // close connection } - in <- inbound.NewHTTPS(request, conn) + in <- inbound.NewHTTPSWithInfos(request, conn, name, preferRulesName) return // hijack connection } @@ -61,7 +61,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[strin request.RequestURI = "" if isUpgradeRequest(request) { - handleUpgrade(conn, request, in) + handleUpgrade(name, preferRulesName, conn, request, in) return // hijack connection } diff --git a/listener/http/server.go b/listener/http/server.go index cf7948dd7e..fceafb68a2 100644 --- a/listener/http/server.go +++ b/listener/http/server.go @@ -12,6 +12,8 @@ type Listener struct { listener net.Listener addr string closed bool + name string + preferRulesName string } // RawAddress implements C.Listener @@ -31,10 +33,14 @@ func (l *Listener) Close() error { } func New(addr string, in chan<- C.ConnContext) (*Listener, error) { - return NewWithAuthenticate(addr, in, true) + return NewWithAuthenticate(addr,"DEFAULT-HTTP","", in, true) } -func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool) (*Listener, error) { +func NewWithInfos(addr ,name ,preferRulesName string,in chan<-C.ConnContext)(*Listener,error){ + return NewWithAuthenticate(addr,name,preferRulesName,in,true) +} + +func NewWithAuthenticate(addr,name,preferRulesName string, in chan<- C.ConnContext, authenticate bool) (*Listener, error) { l, err := inbound.Listen("tcp", addr) if err != nil { @@ -48,6 +54,8 @@ func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool hl := &Listener{ listener: l, + name: name, + preferRulesName: preferRulesName, addr: addr, } go func() { @@ -59,7 +67,7 @@ func NewWithAuthenticate(addr string, in chan<- C.ConnContext, authenticate bool } continue } - go HandleConn(conn, in, c) + go HandleConn(hl.name,hl.preferRulesName,conn, in, c) } }() diff --git a/listener/http/upgrade.go b/listener/http/upgrade.go index 9032166c97..b0f1e38981 100644 --- a/listener/http/upgrade.go +++ b/listener/http/upgrade.go @@ -25,7 +25,7 @@ func isUpgradeRequest(req *http.Request) bool { return false } -func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext) { +func handleUpgrade(name,preferRulesName string,conn net.Conn, request *http.Request, in chan<- C.ConnContext) { defer conn.Close() removeProxyHeaders(request.Header) @@ -43,7 +43,7 @@ func handleUpgrade(conn net.Conn, request *http.Request, in chan<- C.ConnContext left, right := net.Pipe() - in <- inbound.NewHTTP(dstAddr, conn.RemoteAddr(), right) + in <- inbound.NewHTTPWithInfos(dstAddr, conn.RemoteAddr(), right,name,preferRulesName) var bufferedLeft *N.BufferedConn if request.TLS != nil { diff --git a/listener/inbound/base.go b/listener/inbound/base.go new file mode 100644 index 0000000000..9fec28cd1e --- /dev/null +++ b/listener/inbound/base.go @@ -0,0 +1,66 @@ +package inbound + +import ( + "net" + "net/netip" + "strconv" + + C "github.com/Dreamacro/clash/constant" +) + +type Base struct { + name string + preferRulesName string + listenAddr netip.Addr + port int +} + +func NewBase(options *BaseOption) (*Base, error) { + if options.Listen == "" { + options.Listen = "0.0.0.0" + } + addr, err := netip.ParseAddr(options.Listen) + if err != nil { + return nil, err + } + return &Base{ + name: options.Name, + listenAddr: addr, + preferRulesName: options.PreferRulesName, + port: options.Port, + }, nil +} + +// Address implements constant.NewListener +func (b *Base) Address() string { + return b.RawAddress() +} + +// Close implements constant.NewListener +func (*Base) Close() error { + return nil +} + +// Name implements constant.NewListener +func (b *Base) Name() string { + return b.name +} + +// RawAddress implements constant.NewListener +func (b *Base) RawAddress() string { + return net.JoinHostPort(b.listenAddr.String(), strconv.Itoa(int(b.port))) +} + +// ReCreate implements constant.NewListener +func (*Base) ReCreate(tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) error { + return nil +} + +type BaseOption struct { + Name string `inbound:"name"` + Listen string `inbound:"listen,omitempty"` + Port int `inbound:"port"` + PreferRulesName string `inbound:"rule,omitempty"` +} + +var _ C.NewListener = (*Base)(nil) diff --git a/listener/inbound/http.go b/listener/inbound/http.go new file mode 100644 index 0000000000..1902109fe2 --- /dev/null +++ b/listener/inbound/http.go @@ -0,0 +1,52 @@ +package inbound + +import ( + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/listener/http" + "github.com/Dreamacro/clash/log" +) + +type HTTPOption struct { + BaseOption +} +type HTTP struct { + *Base + l *http.Listener +} + +func NewHTTP(options *HTTPOption) (*HTTP, error) { + base, err := NewBase(&options.BaseOption) + if err != nil { + return nil, err + } + return &HTTP{ + Base: base, + }, nil +} + +// Address implements constant.NewListener +func (h *HTTP) Address() string { + return h.l.Address() +} + +// ReCreate implements constant.NewListener +func (h *HTTP) ReCreate(tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) error { + var err error + _ = h.Close() + h.l, err = http.NewWithInfos(h.RawAddress(), h.name, h.preferRulesName, tcpIn) + if err != nil { + return err + } + log.Infoln("HTTP[%s] proxy listening at: %s", h.Name(), h.Address()) + return nil +} + +// Close implements constant.NewListener +func (h *HTTP) Close() error { + if h.l != nil { + return h.l.Close() + } + return nil +} + +var _ C.NewListener = (*HTTP)(nil) diff --git a/listener/inbound/mixed.go b/listener/inbound/mixed.go new file mode 100644 index 0000000000..691b25034d --- /dev/null +++ b/listener/inbound/mixed.go @@ -0,0 +1,79 @@ +package inbound + +import ( + "fmt" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + + "github.com/Dreamacro/clash/listener/mixed" + "github.com/Dreamacro/clash/listener/socks" +) + +type MixedOption struct { + BaseOption + UDP *bool `inbound:"udp,omitempty"` +} + +type Mixed struct { + *Base + l *mixed.Listener + lUDP *socks.UDPListener + udp bool +} + +func NewMixed(options *MixedOption) (*Mixed, error) { + base, err := NewBase(&options.BaseOption) + if err != nil { + return nil, err + } + return &Mixed{ + Base: base, + udp: options.UDP == nil || *options.UDP, + }, nil +} + +// Address implements constant.NewListener +func (m *Mixed) Address() string { + return m.l.Address() +} + +// ReCreate implements constant.NewListener +func (m *Mixed) ReCreate(tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) error { + var err error + _ = m.Close() + m.l, err = mixed.NewWithInfos(m.RawAddress(), m.name, m.preferRulesName, tcpIn) + if err != nil { + return err + } + if m.udp { + m.lUDP, err = socks.NewUDPWithInfos(m.Address(), m.name, m.preferRulesName, udpIn) + if err != nil { + return err + } + } + log.Infoln("Mixed(http+socks)[%s] proxy listening at: %s", m.Name(), m.Address()) + return nil +} + +// Close implements constant.NewListener +func (m *Mixed) Close() error { + var err error + if m.l != nil { + if tcpErr := m.l.Close(); tcpErr != nil { + err = tcpErr + } + } + if m.udp && m.lUDP != nil { + if udpErr := m.lUDP.Close(); udpErr != nil { + if err == nil { + err = udpErr + } else { + return fmt.Errorf("close tcp err: %s, close udp err: %s", err.Error(), udpErr.Error()) + } + } + } + return err +} + +var _ C.NewListener = (*Mixed)(nil) diff --git a/listener/inbound/redir.go b/listener/inbound/redir.go new file mode 100644 index 0000000000..baf6c0f7fb --- /dev/null +++ b/listener/inbound/redir.go @@ -0,0 +1,53 @@ +package inbound + +import ( + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/listener/redir" + "github.com/Dreamacro/clash/log" +) + +type RedirOption struct { + BaseOption +} + +type Redir struct { + *Base + l *redir.Listener +} + +func NewRedir(options *RedirOption) (*Redir, error) { + base, err := NewBase(&options.BaseOption) + if err != nil { + return nil, err + } + return &Redir{ + Base: base, + }, nil +} + +// Address implements constant.NewListener +func (r *Redir) Address() string { + return r.l.Address() +} + +// ReCreate implements constant.NewListener +func (r *Redir) ReCreate(tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) error { + var err error + _ = r.Close() + r.l, err = redir.NewWithInfos(r.Address(), r.name, r.preferRulesName, tcpIn) + if err != nil { + return err + } + log.Infoln("Redir[%s] proxy listening at: %s", r.Name(), r.Address()) + return nil +} + +// Close implements constant.NewListener +func (r *Redir) Close() error { + if r.l != nil { + r.l.Close() + } + return nil +} + +var _ C.NewListener = (*Redir)(nil) diff --git a/listener/inbound/socks.go b/listener/inbound/socks.go new file mode 100644 index 0000000000..35e1129776 --- /dev/null +++ b/listener/inbound/socks.go @@ -0,0 +1,81 @@ +package inbound + +import ( + "fmt" + "sync" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/listener/socks" + "github.com/Dreamacro/clash/log" +) + +type SocksOption struct { + BaseOption + UDP *bool `inbound:"udp,omitempty"` +} + +type Socks struct { + *Base + mux sync.Mutex + udp bool + stl *socks.Listener + sul *socks.UDPListener +} + +func NewSocks(options *SocksOption) (*Socks, error) { + base, err := NewBase(&options.BaseOption) + if err != nil { + return nil, err + } + return &Socks{ + Base: base, + udp: options.UDP == nil || *options.UDP, + }, nil +} + +// Close implements constant.NewListener +func (s *Socks) Close() error { + var err error + if s.stl != nil { + if tcpErr := s.stl.Close(); tcpErr != nil { + err = tcpErr + } + } + if s.udp && s.sul != nil { + if udpErr := s.sul.Close(); udpErr != nil { + if err == nil { + err = udpErr + } else { + return fmt.Errorf("close tcp err: %s, close udp err: %s", err.Error(), udpErr.Error()) + } + } + } + + return err +} + +// Address implements constant.NewListener +func (s *Socks) Address() string { + return s.stl.Address() +} + +// ReCreate implements constant.NewListener +func (s *Socks) ReCreate(tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) error { + s.mux.Lock() + defer s.mux.Unlock() + var err error + _ = s.Close() + if s.stl, err = socks.NewWithInfos(s.RawAddress(), s.name, s.preferRulesName, tcpIn); err != nil { + return err + } + if s.udp { + if s.sul, err = socks.NewUDPWithInfos(s.RawAddress(), s.name, s.preferRulesName, udpIn); err != nil { + return err + } + } + + log.Infoln("SOCKS[%s] proxy listening at: %s", s.Name(), s.Address()) + return nil +} + +var _ C.NewListener = (*Socks)(nil) diff --git a/listener/inbound/tproxy.go b/listener/inbound/tproxy.go new file mode 100644 index 0000000000..acca5c8ec0 --- /dev/null +++ b/listener/inbound/tproxy.go @@ -0,0 +1,84 @@ +package inbound + +import ( + "fmt" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/listener/tproxy" + "github.com/Dreamacro/clash/log" +) + +type TProxyOption struct { + BaseOption + UDP *bool `inbound:"udp,omitempty"` +} + +type TProxy struct { + *Base + lUDP *tproxy.UDPListener + lTCP *tproxy.Listener + udp bool +} + +func NewTProxy(options *TProxyOption) (*TProxy, error) { + base, err := NewBase(&options.BaseOption) + if err != nil { + return nil, err + } + return &TProxy{ + Base: base, + udp: options.UDP == nil || *options.UDP, + }, nil + +} + +// Address implements constant.NewListener +func (t *TProxy) Address() string { + return t.lTCP.Address() +} + +// ReCreate implements constant.NewListener +func (t *TProxy) ReCreate(tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) error { + var err error + _ = t.Close() + t.lTCP, err = tproxy.NewWithInfos(t.RawAddress(), t.name, t.preferRulesName, tcpIn) + if err != nil { + return err + } + if t.udp { + if t.lUDP != nil { + t.lUDP, err = tproxy.NewUDPWithInfos(t.Address(), t.name, t.preferRulesName, udpIn) + if err != nil { + return err + } + } + + } + log.Infoln("TProxy[%s] proxy listening at: %s", t.Name(), t.Address()) + return nil +} + +// Close implements constant.NewListener +func (t *TProxy) Close() error { + var tcpErr error + var udpErr error + if t.lTCP != nil { + tcpErr = t.lTCP.Close() + } + if t.lUDP != nil { + udpErr = t.lUDP.Close() + } + + if tcpErr != nil && udpErr != nil { + return fmt.Errorf("tcp close err: %s and udp close err: %s", tcpErr, udpErr) + } + if tcpErr != nil { + return tcpErr + } + if udpErr != nil { + return udpErr + } + return nil +} + +var _ C.NewListener = (*TProxy)(nil) diff --git a/listener/listener.go b/listener/listener.go index 4601b44338..4bf8236915 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -9,9 +9,7 @@ import ( "strings" "sync" - "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/component/ebpf" - "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/listener/autoredir" "github.com/Dreamacro/clash/listener/http" @@ -66,8 +64,8 @@ var ( autoRedirMux sync.Mutex tcMux sync.Mutex - LastTunConf config.Tun - LastTuicConf config.TuicServer + LastTunConf sing_tun.Tun + LastTuicConf tuic.TuicServer ) type Ports struct { @@ -80,18 +78,18 @@ type Ports struct { VmessConfig string `json:"vmess-config"` } -func GetTunConf() config.Tun { +func GetTunConf() sing_tun.Tun { if tunLister == nil { - return config.Tun{ + return sing_tun.Tun{ Enable: false, } } return tunLister.Config() } -func GetTuicConf() config.TuicServer { +func GetTuicConf() tuic.TuicServer { if tuicListener == nil { - return config.TuicServer{Enable: false} + return tuic.TuicServer{Enable: false} } return tuicListener.Config() } @@ -146,7 +144,7 @@ func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) { log.Infoln("HTTP proxy listening at: %s", httpListener.Address()) } -func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) { +func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) { socksMux.Lock() defer socksMux.Unlock() @@ -205,7 +203,7 @@ func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P log.Infoln("SOCKS proxy listening at: %s", socksListener.Address()) } -func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) { +func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) { redirMux.Lock() defer redirMux.Unlock() @@ -251,7 +249,7 @@ func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P log.Infoln("Redirect proxy listening at: %s", redirListener.Address()) } -func ReCreateShadowSocks(shadowSocksConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) { +func ReCreateShadowSocks(shadowSocksConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) { ssMux.Lock() defer ssMux.Unlock() @@ -291,7 +289,7 @@ func ReCreateShadowSocks(shadowSocksConfig string, tcpIn chan<- C.ConnContext, u return } -func ReCreateVmess(vmessConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) { +func ReCreateVmess(vmessConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) { vmessMux.Lock() defer vmessMux.Unlock() @@ -331,7 +329,7 @@ func ReCreateVmess(vmessConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- return } -func ReCreateTuic(config config.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) { +func ReCreateTuic(config tuic.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) { tuicMux.Lock() defer func() { LastTuicConf = config @@ -373,7 +371,7 @@ func ReCreateTuic(config config.TuicServer, tcpIn chan<- C.ConnContext, udpIn ch return } -func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) { +func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) { tproxyMux.Lock() defer tproxyMux.Unlock() @@ -419,7 +417,7 @@ func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound. log.Infoln("TProxy server listening at: %s", tproxyListener.Address()) } -func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) { +func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) { mixedMux.Lock() defer mixedMux.Unlock() @@ -474,7 +472,7 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address()) } -func ReCreateTun(tunConf config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) { +func ReCreateTun(tunConf sing_tun.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) { tunMux.Lock() defer func() { LastTunConf = tunConf @@ -538,7 +536,7 @@ func ReCreateRedirToTun(ifaceNames []string) { log.Infoln("Attached tc ebpf program to interfaces %v", tcProgram.RawNICs()) } -func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<- *inbound.PacketAdapter) { +func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<- *C.PacketAdapter) { autoRedirMux.Lock() defer autoRedirMux.Unlock() @@ -594,7 +592,7 @@ func ReCreateAutoRedir(ifaceNames []string, tcpIn chan<- C.ConnContext, _ chan<- log.Infoln("Auto redirect proxy listening at: %s, attached tc ebpf program to interfaces %v", autoRedirListener.Address(), autoRedirProgram.RawNICs()) } -func PatchTunnel(tunnels []config.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) { +func PatchTunnel(tunnels []tunnel.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) { tunnelMux.Lock() defer tunnelMux.Unlock() @@ -633,7 +631,7 @@ func PatchTunnel(tunnels []config.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan newElm := lo.FlatMap( tunnels, - func(tunnel config.Tunnel, _ int) []addrProxy { + func(tunnel tunnel.Tunnel, _ int) []addrProxy { return lo.Map( tunnel.Network, func(network string, _ int) addrProxy { @@ -747,7 +745,7 @@ func genAddr(host string, port int, allowLan bool) string { return fmt.Sprintf("127.0.0.1:%d", port) } -func hasTunConfigChange(tunConf *config.Tun) bool { +func hasTunConfigChange(tunConf *sing_tun.Tun) bool { if LastTunConf.Enable != tunConf.Enable || LastTunConf.Device != tunConf.Device || LastTunConf.Stack != tunConf.Stack || @@ -835,5 +833,5 @@ func Cleanup(wait bool) { tunLister.Close() tunLister = nil } - LastTunConf = config.Tun{} + LastTunConf = sing_tun.Tun{} } diff --git a/listener/mixed/mixed.go b/listener/mixed/mixed.go index 0ee50ba43b..fe42a14ef4 100644 --- a/listener/mixed/mixed.go +++ b/listener/mixed/mixed.go @@ -14,11 +14,12 @@ import ( ) type Listener struct { - listener net.Listener - addr string - - cache *cache.LruCache[string, bool] - closed bool + listener net.Listener + addr string + name string + preferRulesName string + cache *cache.LruCache[string, bool] + closed bool } // RawAddress implements C.Listener @@ -38,15 +39,21 @@ func (l *Listener) Close() error { } func New(addr string, in chan<- C.ConnContext) (*Listener, error) { + return NewWithInfos(addr, "DEFAULT-MIXED", "", in) +} + +func NewWithInfos(addr, name, preferRulesName string, in chan<- C.ConnContext) (*Listener, error) { l, err := inbound.Listen("tcp", addr) if err != nil { return nil, err } ml := &Listener{ - listener: l, - addr: addr, - cache: cache.New[string, bool](cache.WithAge[string, bool](30)), + listener: l, + addr: addr, + name: name, + preferRulesName: preferRulesName, + cache: cache.New[string, bool](cache.WithAge[string, bool](30)), } go func() { for { @@ -57,14 +64,14 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) { } continue } - go handleConn(c, in, ml.cache) + go handleConn(ml.name, ml.preferRulesName, c, in, ml.cache) } }() return ml, nil } -func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool]) { +func handleConn(name, preferRulesName string, conn net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool]) { conn.(*net.TCPConn).SetKeepAlive(true) bufConn := N.NewBufferedConn(conn) @@ -75,10 +82,10 @@ func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[st switch head[0] { case socks4.Version: - socks.HandleSocks4(bufConn, in) + socks.HandleSocks4(name, preferRulesName, bufConn, in) case socks5.Version: - socks.HandleSocks5(bufConn, in) + socks.HandleSocks5(name, preferRulesName, bufConn, in) default: - http.HandleConn(bufConn, in, cache) + http.HandleConn(name, preferRulesName, bufConn, in, cache) } } diff --git a/listener/parse.go b/listener/parse.go new file mode 100644 index 0000000000..026c7df61f --- /dev/null +++ b/listener/parse.go @@ -0,0 +1,50 @@ +package listener + +import ( + "fmt" + "strings" + + "github.com/Dreamacro/clash/common/structure" + C "github.com/Dreamacro/clash/constant" + IN "github.com/Dreamacro/clash/listener/inbound" +) + +var keyReplacer = strings.NewReplacer("_", "-") + +func ParseListener(mapping map[string]any) (C.NewListener, error) { + decoder := structure.NewDecoder(structure.Option{TagName: "inbound", WeaklyTypedInput: true, KeyReplacer: keyReplacer}) + proxyType, existType := mapping["type"].(string) + if !existType { + return nil, fmt.Errorf("missing type") + } + + var ( + listener C.NewListener + err error + ) + switch proxyType { + case "socks": + socksOption := &IN.SocksOption{} + decoder.Decode(mapping, socksOption) + listener, err = IN.NewSocks(socksOption) + case "http": + httpOption := &IN.HTTPOption{} + decoder.Decode(mapping, httpOption) + listener, err = IN.NewHTTP(httpOption) + case "tproxy": + tproxyOption := &IN.TProxyOption{} + decoder.Decode(mapping, tproxyOption) + listener, err = IN.NewTProxy(tproxyOption) + case "redir": + redirOption := &IN.RedirOption{} + decoder.Decode(mapping, redirOption) + listener, err = IN.NewRedir(redirOption) + case "mixed": + mixedOption := &IN.MixedOption{} + decoder.Decode(mapping, mixedOption) + listener, err = IN.NewMixed(mixedOption) + default: + return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) + } + return listener, err +} diff --git a/listener/redir/tcp.go b/listener/redir/tcp.go index 15c98a8f3a..d55897cb74 100644 --- a/listener/redir/tcp.go +++ b/listener/redir/tcp.go @@ -11,6 +11,8 @@ type Listener struct { listener net.Listener addr string closed bool + name string + preferRulesName string } // RawAddress implements C.Listener @@ -30,6 +32,10 @@ func (l *Listener) Close() error { } func New(addr string, in chan<- C.ConnContext) (*Listener, error) { + return NewWithInfos(addr,"DEFAULT-REDIR","",in) +} + +func NewWithInfos(addr,name,preferRulesName string, in chan<- C.ConnContext) (*Listener, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err @@ -37,6 +43,8 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) { rl := &Listener{ listener: l, addr: addr, + name: name, + preferRulesName: preferRulesName, } go func() { @@ -48,19 +56,18 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) { } continue } - go handleRedir(c, in) + go handleRedir(rl.name,rl.preferRulesName,c, in) } }() return rl, nil } - -func handleRedir(conn net.Conn, in chan<- C.ConnContext) { +func handleRedir(name,preferRulesName string,conn net.Conn, in chan<- C.ConnContext) { target, err := parserPacket(conn) if err != nil { conn.Close() return } conn.(*net.TCPConn).SetKeepAlive(true) - in <- inbound.NewSocket(target, conn, C.REDIR) + in <- inbound.NewSocketWithInfos(target, conn, C.REDIR,name,preferRulesName) } diff --git a/listener/shadowsocks/tcp.go b/listener/shadowsocks/tcp.go index c37892bb80..74ffca5953 100644 --- a/listener/shadowsocks/tcp.go +++ b/listener/shadowsocks/tcp.go @@ -21,7 +21,7 @@ type Listener struct { var _listener *Listener -func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (*Listener, error) { +func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) (*Listener, error) { addr, cipher, password, err := ParseSSURL(config) if err != nil { return nil, err diff --git a/listener/shadowsocks/udp.go b/listener/shadowsocks/udp.go index efb29e41fd..5ea17c6b36 100644 --- a/listener/shadowsocks/udp.go +++ b/listener/shadowsocks/udp.go @@ -17,7 +17,7 @@ type UDPListener struct { closed bool } -func NewUDP(addr string, pickCipher core.Cipher, in chan<- *inbound.PacketAdapter) (*UDPListener, error) { +func NewUDP(addr string, pickCipher core.Cipher, in chan<- *C.PacketAdapter) (*UDPListener, error) { l, err := net.ListenPacket("udp", addr) if err != nil { return nil, err @@ -53,7 +53,7 @@ func (l *UDPListener) Close() error { return l.packetConn.Close() } -func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) { +func handleSocksUDP(pc net.PacketConn, in chan<- *C.PacketAdapter, buf []byte, addr net.Addr) { tgtAddr := socks5.SplitAddr(buf) if tgtAddr == nil { // Unresolved UDP packet, return buffer to the pool diff --git a/listener/sing/sing.go b/listener/sing/sing.go index df55f1000b..4ff37f6a4e 100644 --- a/listener/sing/sing.go +++ b/listener/sing/sing.go @@ -24,7 +24,7 @@ const UDPTimeout = 5 * time.Minute type ListenerHandler struct { TcpIn chan<- C.ConnContext - UdpIn chan<- *inbound.PacketAdapter + UdpIn chan<- *C.PacketAdapter Type C.Type } diff --git a/listener/sing_shadowsocks/server.go b/listener/sing_shadowsocks/server.go index b7073e5df5..ecfc1df7d3 100644 --- a/listener/sing_shadowsocks/server.go +++ b/listener/sing_shadowsocks/server.go @@ -32,7 +32,7 @@ type Listener struct { var _listener *Listener -func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (C.AdvanceListener, error) { +func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) (C.AdvanceListener, error) { addr, cipher, password, err := embedSS.ParseSSURL(config) if err != nil { return nil, err diff --git a/listener/sing_tun/server.go b/listener/sing_tun/server.go index 9d010fd404..13437149ab 100644 --- a/listener/sing_tun/server.go +++ b/listener/sing_tun/server.go @@ -8,10 +8,8 @@ import ( "strconv" "strings" - "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/iface" - "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/listener/sing" "github.com/Dreamacro/clash/log" @@ -27,7 +25,7 @@ var InterfaceName = "Meta" type Listener struct { closed bool - options config.Tun + options Tun handler *ListenerHandler tunName string @@ -65,7 +63,7 @@ func CalculateInterfaceName(name string) (tunName string) { return } -func New(options config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (l *Listener, err error) { +func New(options Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) (l *Listener, err error) { tunName := options.Device if tunName == "" { tunName = CalculateInterfaceName(InterfaceName) @@ -163,12 +161,12 @@ func New(options config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P tunOptions := tun.Options{ Name: tunName, MTU: tunMTU, - Inet4Address: common.Map(options.Inet4Address, config.ListenPrefix.Build), - Inet6Address: common.Map(options.Inet6Address, config.ListenPrefix.Build), + Inet4Address: common.Map(options.Inet4Address, ListenPrefix.Build), + Inet6Address: common.Map(options.Inet6Address, ListenPrefix.Build), AutoRoute: options.AutoRoute, StrictRoute: options.StrictRoute, - Inet4RouteAddress: common.Map(options.Inet4RouteAddress, config.ListenPrefix.Build), - Inet6RouteAddress: common.Map(options.Inet6RouteAddress, config.ListenPrefix.Build), + Inet4RouteAddress: common.Map(options.Inet4RouteAddress, ListenPrefix.Build), + Inet6RouteAddress: common.Map(options.Inet6RouteAddress, ListenPrefix.Build), IncludeUID: includeUID, ExcludeUID: excludeUID, IncludeAndroidUser: options.IncludeAndroidUser, @@ -284,6 +282,6 @@ func (l *Listener) Close() { ) } -func (l *Listener) Config() config.Tun { +func (l *Listener) Config() Tun { return l.options } diff --git a/listener/sing_tun/tun.go b/listener/sing_tun/tun.go new file mode 100644 index 0000000000..95ff61ba9e --- /dev/null +++ b/listener/sing_tun/tun.go @@ -0,0 +1,85 @@ +package sing_tun + +import ( + "encoding/json" + "net/netip" + + C "github.com/Dreamacro/clash/constant" + "gopkg.in/yaml.v3" +) + +type ListenPrefix netip.Prefix + +func (p ListenPrefix) MarshalJSON() ([]byte, error) { + prefix := netip.Prefix(p) + if !prefix.IsValid() { + return json.Marshal(nil) + } + return json.Marshal(prefix.String()) +} + +func (p ListenPrefix) MarshalYAML() (interface{}, error) { + prefix := netip.Prefix(p) + if !prefix.IsValid() { + return nil, nil + } + return prefix.String(), nil +} + +func (p *ListenPrefix) UnmarshalJSON(bytes []byte) error { + var value string + err := json.Unmarshal(bytes, &value) + if err != nil { + return err + } + prefix, err := netip.ParsePrefix(value) + if err != nil { + return err + } + *p = ListenPrefix(prefix) + return nil +} + +func (p *ListenPrefix) UnmarshalYAML(node *yaml.Node) error { + var value string + err := node.Decode(&value) + if err != nil { + return err + } + prefix, err := netip.ParsePrefix(value) + if err != nil { + return err + } + *p = ListenPrefix(prefix) + return nil +} + +func (p ListenPrefix) Build() netip.Prefix { + return netip.Prefix(p) +} + +type Tun struct { + Enable bool + Device string + Stack C.TUNStack + DNSHijack []netip.AddrPort + AutoRoute bool + AutoDetectInterface bool + RedirectToTun []string + + MTU uint32 + Inet4Address []ListenPrefix + Inet6Address []ListenPrefix + StrictRoute bool + Inet4RouteAddress []ListenPrefix + Inet6RouteAddress []ListenPrefix + IncludeUID []uint32 + IncludeUIDRange []string + ExcludeUID []uint32 + ExcludeUIDRange []string + IncludeAndroidUser []int + IncludePackage []string + ExcludePackage []string + EndpointIndependentNat bool + UDPTimeout int64 +} diff --git a/listener/sing_vmess/server.go b/listener/sing_vmess/server.go index c02a2c798b..4dd046c1d1 100644 --- a/listener/sing_vmess/server.go +++ b/listener/sing_vmess/server.go @@ -24,7 +24,7 @@ type Listener struct { var _listener *Listener -func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (*Listener, error) { +func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) (*Listener, error) { addr, username, password, err := parseVmessURL(config) if err != nil { return nil, err diff --git a/listener/socks/tcp.go b/listener/socks/tcp.go index 8961c3937d..7ddc71eac3 100644 --- a/listener/socks/tcp.go +++ b/listener/socks/tcp.go @@ -16,6 +16,8 @@ type Listener struct { listener net.Listener addr string closed bool + preferRulesName string + name string } // RawAddress implements C.Listener @@ -35,6 +37,10 @@ func (l *Listener) Close() error { } func New(addr string, in chan<- C.ConnContext) (*Listener, error) { + return NewWithInfos(addr,"DEFAULT-SOCKS","",in) +} + +func NewWithInfos(addr,name,preferRulesName string, in chan<- C.ConnContext) (*Listener, error) { l, err := inbound.Listen("tcp", addr) if err != nil { return nil, err @@ -43,6 +49,8 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) { sl := &Listener{ listener: l, addr: addr, + name: name, + preferRulesName: preferRulesName, } go func() { for { @@ -53,14 +61,14 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) { } continue } - go handleSocks(c, in) + go handleSocks(sl.name,sl.preferRulesName,c, in) } }() return sl, nil } -func handleSocks(conn net.Conn, in chan<- C.ConnContext) { +func handleSocks(name,preferRulesName string,conn net.Conn, in chan<- C.ConnContext) { conn.(*net.TCPConn).SetKeepAlive(true) bufConn := N.NewBufferedConn(conn) head, err := bufConn.Peek(1) @@ -71,24 +79,24 @@ func handleSocks(conn net.Conn, in chan<- C.ConnContext) { switch head[0] { case socks4.Version: - HandleSocks4(bufConn, in) + HandleSocks4(name,preferRulesName,bufConn, in) case socks5.Version: - HandleSocks5(bufConn, in) + HandleSocks5(name,preferRulesName,bufConn, in) default: conn.Close() } } -func HandleSocks4(conn net.Conn, in chan<- C.ConnContext) { +func HandleSocks4(name,preferRulesName string, conn net.Conn, in chan<- C.ConnContext) { addr, _, err := socks4.ServerHandshake(conn, authStore.Authenticator()) if err != nil { conn.Close() return } - in <- inbound.NewSocket(socks5.ParseAddr(addr), conn, C.SOCKS4) + in <- inbound.NewSocketWithInfos(socks5.ParseAddr(addr), conn, C.SOCKS4,name,preferRulesName) } -func HandleSocks5(conn net.Conn, in chan<- C.ConnContext) { +func HandleSocks5(name,preferRulesName string,conn net.Conn, in chan<- C.ConnContext) { target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator()) if err != nil { conn.Close() @@ -99,5 +107,5 @@ func HandleSocks5(conn net.Conn, in chan<- C.ConnContext) { io.Copy(io.Discard, conn) return } - in <- inbound.NewSocket(target, conn, C.SOCKS5) + in <- inbound.NewSocketWithInfos(target, conn, C.SOCKS5,name,preferRulesName) } diff --git a/listener/socks/udp.go b/listener/socks/udp.go index 8bc439fb4c..6adbd2571b 100644 --- a/listener/socks/udp.go +++ b/listener/socks/udp.go @@ -15,6 +15,8 @@ type UDPListener struct { packetConn net.PacketConn addr string closed bool + name string + preferRulesName string } // RawAddress implements C.Listener @@ -33,7 +35,11 @@ func (l *UDPListener) Close() error { return l.packetConn.Close() } -func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) { +func NewUDP(addr string, in chan<- *C.PacketAdapter) (*UDPListener, error) { + return NewUDPWithInfos(addr,"DEFAULT-SOCKS","",in) +} + +func NewUDPWithInfos(addr,name ,preferRulesName string, in chan<- *C.PacketAdapter) (*UDPListener, error) { l, err := net.ListenPacket("udp", addr) if err != nil { return nil, err @@ -46,6 +52,8 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) sl := &UDPListener{ packetConn: l, addr: addr, + preferRulesName: preferRulesName, + name: name, } go func() { for { @@ -58,14 +66,14 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) } continue } - handleSocksUDP(l, in, buf[:n], remoteAddr) + handleSocksUDP(sl.name,sl.preferRulesName,l, in, buf[:n], remoteAddr) } }() return sl, nil } -func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) { +func handleSocksUDP(name,preferRulesName string,pc net.PacketConn, in chan<- *C.PacketAdapter, buf []byte, addr net.Addr) { target, payload, err := socks5.DecodeUDPPacket(buf) if err != nil { // Unresolved UDP packet, return buffer to the pool @@ -79,7 +87,7 @@ func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []b bufRef: buf, } select { - case in <- inbound.NewPacket(target, packet, C.SOCKS5): + case in <- inbound.NewPacketWithInfos(target, packet, C.SOCKS5,name,preferRulesName): default: } } diff --git a/listener/tproxy/tproxy.go b/listener/tproxy/tproxy.go index 1a09f36632..c546008d3e 100644 --- a/listener/tproxy/tproxy.go +++ b/listener/tproxy/tproxy.go @@ -12,6 +12,8 @@ type Listener struct { listener net.Listener addr string closed bool + name string + preferRulesName string } // RawAddress implements C.Listener @@ -30,13 +32,17 @@ func (l *Listener) Close() error { return l.listener.Close() } -func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext) { +func (l *Listener) handleTProxy(name,preferRulesName string ,conn net.Conn, in chan<- C.ConnContext) { target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) conn.(*net.TCPConn).SetKeepAlive(true) - in <- inbound.NewSocket(target, conn, C.TPROXY) + in <- inbound.NewSocketWithInfos(target, conn, C.TPROXY,name,preferRulesName) } func New(addr string, in chan<- C.ConnContext) (*Listener, error) { + return NewWithInfos(addr,"DEFAULT-TPROXY","",in) +} + +func NewWithInfos(addr,name,preferRulesName string, in chan<- C.ConnContext) (*Listener, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err @@ -56,6 +62,8 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) { rl := &Listener{ listener: l, addr: addr, + name: name, + preferRulesName: preferRulesName, } go func() { @@ -67,7 +75,7 @@ func New(addr string, in chan<- C.ConnContext) (*Listener, error) { } continue } - go rl.handleTProxy(c, in) + go rl.handleTProxy(rl.name,rl.preferRulesName,c, in) } }() diff --git a/listener/tproxy/udp.go b/listener/tproxy/udp.go index 90d0a97da3..f34e55256c 100644 --- a/listener/tproxy/udp.go +++ b/listener/tproxy/udp.go @@ -11,9 +11,11 @@ import ( ) type UDPListener struct { - packetConn net.PacketConn - addr string - closed bool + packetConn net.PacketConn + addr string + closed bool + name string + preferRulesName string } // RawAddress implements C.Listener @@ -32,7 +34,11 @@ func (l *UDPListener) Close() error { return l.packetConn.Close() } -func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) { +func NewUDP(addr string, in chan<- *C.PacketAdapter) (*UDPListener, error) { + return NewUDPWithInfos(addr, "DEFAULT-TPROXY", "", in) +} + +func NewUDPWithInfos(addr, name, preferRulesName string, in chan<- *C.PacketAdapter) (*UDPListener, error) { l, err := net.ListenPacket("udp", addr) if err != nil { return nil, err @@ -77,14 +83,14 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error) // try to unmap 4in6 address lAddr = netip.AddrPortFrom(lAddr.Addr().Unmap(), lAddr.Port()) } - handlePacketConn(l, in, buf[:n], lAddr, rAddr) + handlePacketConn(rl.name, rl.preferRulesName, l, in, buf[:n], lAddr, rAddr) } }() return rl, nil } -func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr, rAddr netip.AddrPort) { +func handlePacketConn(name, preferRulesName string, pc net.PacketConn, in chan<- *C.PacketAdapter, buf []byte, lAddr, rAddr netip.AddrPort) { target := socks5.AddrFromStdAddrPort(rAddr) pkt := &packet{ pc: pc, @@ -92,7 +98,7 @@ func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf [ buf: buf, } select { - case in <- inbound.NewPacket(target, pkt, C.TPROXY): + case in <- inbound.NewPacketWithInfos(target, pkt, C.TPROXY, name, preferRulesName): default: } } diff --git a/listener/tuic/server.go b/listener/tuic/server.go index 64ec84896f..565f258e2c 100644 --- a/listener/tuic/server.go +++ b/listener/tuic/server.go @@ -2,29 +2,47 @@ package tuic import ( "crypto/tls" + "encoding/json" "net" "strings" "time" "github.com/metacubex/quic-go" - "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/common/sockopt" - "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/tuic" ) +type TuicServer struct { + Enable bool + Listen string + Token []string + Certificate string + PrivateKey string + CongestionController string + MaxIdleTime int + AuthenticationTimeout int + ALPN []string + MaxUdpRelayPacketSize int +} + +func (t TuicServer) String() string { + b, _ := json.Marshal(t) + return string(b) +} + type Listener struct { closed bool - config config.TuicServer + config TuicServer udpListeners []net.PacketConn servers []*tuic.Server } -func New(config config.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (*Listener, error) { +func New(config TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- *C.PacketAdapter) (*Listener, error) { cert, err := tls.LoadX509KeyPair(config.Certificate, config.PrivateKey) if err != nil { return nil, err @@ -122,6 +140,6 @@ func (l *Listener) Close() { } } -func (l *Listener) Config() config.TuicServer { +func (l *Listener) Config() TuicServer { return l.config } diff --git a/listener/tunnel/tunnel.go b/listener/tunnel/tunnel.go new file mode 100644 index 0000000000..c3fc759c18 --- /dev/null +++ b/listener/tunnel/tunnel.go @@ -0,0 +1,69 @@ +package tunnel + +import ( + "fmt" + "net" + "strings" + + "github.com/samber/lo" +) + +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) != 3 && 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, + }) + if len(parts) == 4 { + t.Proxy = parts[3] + } + return nil +} diff --git a/listener/tunnel/udp.go b/listener/tunnel/udp.go index ee0ecbaf8e..2f8ddeb62b 100644 --- a/listener/tunnel/udp.go +++ b/listener/tunnel/udp.go @@ -34,7 +34,7 @@ func (l *PacketConn) Close() error { return l.conn.Close() } -func NewUDP(addr, target, proxy string, in chan<- *inbound.PacketAdapter) (*PacketConn, error) { +func NewUDP(addr, target, proxy string, in chan<- *C.PacketAdapter) (*PacketConn, error) { l, err := net.ListenPacket("udp", addr) if err != nil { return nil, err @@ -69,7 +69,7 @@ func NewUDP(addr, target, proxy string, in chan<- *inbound.PacketAdapter) (*Pack return sl, nil } -func (l *PacketConn) handleUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) { +func (l *PacketConn) handleUDP(pc net.PacketConn, in chan<- *C.PacketAdapter, buf []byte, addr net.Addr) { packet := &packet{ pc: pc, rAddr: addr, diff --git a/rules/logic/and.go b/rules/logic/and.go index a8fc1badc1..ffd7c26263 100644 --- a/rules/logic/and.go +++ b/rules/logic/and.go @@ -20,7 +20,7 @@ func (A *AND) ShouldFindProcess() bool { } func NewAND(payload string, adapter string, - parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*AND, error) { + parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*AND, error) { and := &AND{Base: &common.Base{}, payload: payload, adapter: adapter} rules, err := ParseRuleByPayload(payload, parse) if err != nil { diff --git a/rules/logic/common.go b/rules/logic/common.go index 080771ba66..1385463b4c 100644 --- a/rules/logic/common.go +++ b/rules/logic/common.go @@ -9,7 +9,7 @@ import ( _ "unsafe" ) -func ParseRuleByPayload(payload string, parseRule func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) ([]C.Rule, error) { +func ParseRuleByPayload(payload string, parseRule func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) ([]C.Rule, error) { regex, err := regexp.Compile("\\(.*\\)") if err != nil { return nil, err @@ -59,7 +59,7 @@ func payloadToRule(subPayload string, parseRule func(tp, payload, target string, return parseRule(tp, param[0], "", param[1:]) } -func parseLogicSubRule(parseRule func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { +func parseLogicSubRule(parseRule func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { return func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { switch tp { case "MATCH", "SUB-RULE": diff --git a/rules/logic/not.go b/rules/logic/not.go index 6a5b34d8fe..c109a2c905 100644 --- a/rules/logic/not.go +++ b/rules/logic/not.go @@ -17,7 +17,7 @@ func (not *NOT) ShouldFindProcess() bool { return false } -func NewNOT(payload string, adapter string, parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*NOT, error) { +func NewNOT(payload string, adapter string, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*NOT, error) { not := &NOT{Base: &common.Base{}, adapter: adapter} rule, err := ParseRuleByPayload(payload, parse) if err != nil { diff --git a/rules/logic/or.go b/rules/logic/or.go index d1aae9acd2..f63bae4bbc 100644 --- a/rules/logic/or.go +++ b/rules/logic/or.go @@ -45,7 +45,7 @@ func (or *OR) ShouldResolveIP() bool { return or.needIP } -func NewOR(payload string, adapter string, parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*OR, error) { +func NewOR(payload string, adapter string, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*OR, error) { or := &OR{Base: &common.Base{}, payload: payload, adapter: adapter} rules, err := ParseRuleByPayload(payload, parse) if err != nil { diff --git a/rules/logic/sub_rules.go b/rules/logic/sub_rules.go index b4ad761372..7585297973 100644 --- a/rules/logic/sub_rules.go +++ b/rules/logic/sub_rules.go @@ -12,13 +12,13 @@ type SubRule struct { payload string payloadRule C.Rule subName string - subRules *map[string][]C.Rule + subRules map[string][]C.Rule shouldFindProcess *bool shouldResolveIP *bool } -func NewSubRule(payload, subName string, sub *map[string][]C.Rule, - parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*SubRule, error) { +func NewSubRule(payload, subName string, sub map[string][]C.Rule, + parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) (*SubRule, error) { payloadRule, err := ParseRuleByPayload(fmt.Sprintf("(%s)", payload), parse) if err != nil { return nil, err @@ -45,8 +45,8 @@ func (r *SubRule) Match(metadata *C.Metadata) (bool, string) { return match(metadata, r.subName, r.subRules) } -func match(metadata *C.Metadata, name string, subRules *map[string][]C.Rule) (bool, string) { - for _, rule := range (*subRules)[name] { +func match(metadata *C.Metadata, name string, subRules map[string][]C.Rule) (bool, string) { + for _, rule := range subRules[name] { if m, a := rule.Match(metadata); m { if rule.RuleType() == C.SubRules { match(metadata, rule.Adapter(), subRules) @@ -61,7 +61,7 @@ func match(metadata *C.Metadata, name string, subRules *map[string][]C.Rule) (bo func (r *SubRule) ShouldResolveIP() bool { if r.shouldResolveIP == nil { s := false - for _, rule := range (*r.subRules)[r.subName] { + for _, rule := range r.subRules[r.subName] { s = s || rule.ShouldResolveIP() } r.shouldResolveIP = &s @@ -73,7 +73,7 @@ func (r *SubRule) ShouldResolveIP() bool { func (r *SubRule) ShouldFindProcess() bool { if r.shouldFindProcess == nil { s := false - for _, rule := range (*r.subRules)[r.subName] { + for _, rule := range r.subRules[r.subName] { s = s || rule.ShouldFindProcess() } r.shouldFindProcess = &s diff --git a/rules/parser.go b/rules/parser.go index 4e1d9044ea..1a33622595 100644 --- a/rules/parser.go +++ b/rules/parser.go @@ -8,7 +8,7 @@ import ( RP "github.com/Dreamacro/clash/rules/provider" ) -func ParseRule(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error) { +func ParseRule(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error) { switch tp { case "DOMAIN": parsed = RC.NewDomain(payload, target) diff --git a/rules/provider/classical_strategy.go b/rules/provider/classical_strategy.go index 727688fcfc..1262327125 100644 --- a/rules/provider/classical_strategy.go +++ b/rules/provider/classical_strategy.go @@ -66,7 +66,7 @@ func ruleParse(ruleRaw string) (string, string, []string) { return "", "", nil } -func NewClassicalStrategy(parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) *classicalStrategy { +func NewClassicalStrategy(parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) *classicalStrategy { return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { switch tp { case "MATCH", "SUB-RULE": diff --git a/rules/provider/parse.go b/rules/provider/parse.go index 86e21a3032..206bef10a4 100644 --- a/rules/provider/parse.go +++ b/rules/provider/parse.go @@ -17,7 +17,7 @@ type ruleProviderSchema struct { Interval int `provider:"interval,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]interface{}, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) (P.RuleProvider, error) { schema := &ruleProviderSchema{} decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) if err := decoder.Decode(mapping, schema); err != nil { diff --git a/rules/provider/provider.go b/rules/provider/provider.go index 9ae125fb94..347bebaa6e 100644 --- a/rules/provider/provider.go +++ b/rules/provider/provider.go @@ -103,7 +103,7 @@ func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) { } func NewRuleSetProvider(name string, behavior P.RuleType, 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 { + parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) P.RuleProvider { rp := &ruleSetProvider{ behavior: behavior, } @@ -126,7 +126,7 @@ func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration return wrapper } -func newStrategy(behavior P.RuleType, parse func(tp, payload, target string, params []string, subRules *map[string][]C.Rule) (parsed C.Rule, parseErr error)) ruleStrategy { +func newStrategy(behavior P.RuleType, parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) ruleStrategy { switch behavior { case P.Domain: strategy := NewDomainStrategy() diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 62a1ef5ad9..6a0af92e8e 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -13,7 +13,6 @@ import ( "github.com/jpillora/backoff" - "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/component/nat" P "github.com/Dreamacro/clash/component/process" "github.com/Dreamacro/clash/component/resolver" @@ -27,9 +26,10 @@ import ( var ( tcpQueue = make(chan C.ConnContext, 200) - udpQueue = make(chan *inbound.PacketAdapter, 200) + udpQueue = make(chan *C.PacketAdapter, 200) natTable = nat.New() rules []C.Rule + subRules map[string][]C.Rule proxies = make(map[string]C.Proxy) providers map[string]provider.ProxyProvider ruleProviders map[string]provider.RuleProvider @@ -77,7 +77,7 @@ func TCPIn() chan<- C.ConnContext { } // UDPIn return fan-in udp queue -func UDPIn() chan<- *inbound.PacketAdapter { +func UDPIn() chan<- *C.PacketAdapter { return udpQueue } @@ -87,10 +87,11 @@ func Rules() []C.Rule { } // UpdateRules handle update rules -func UpdateRules(newRules []C.Rule, rp map[string]provider.RuleProvider) { +func UpdateRules(newRules []C.Rule, newSubRule map[string][]C.Rule, rp map[string]provider.RuleProvider) { configMux.Lock() rules = newRules ruleProviders = rp + subRules = newSubRule configMux.Unlock() } @@ -216,7 +217,7 @@ func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, r return } -func handleUDPConn(packet *inbound.PacketAdapter) { +func handleUDPConn(packet *C.PacketAdapter) { metadata := packet.Metadata() if !metadata.Valid() { log.Warnln("[Metadata] not valid: %#v", metadata) @@ -435,7 +436,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { resolved = true } - for _, rule := range rules { + for _, rule := range getRules(metadata) { if !resolved && shouldResolveIP(rule, metadata) { func() { ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) @@ -495,6 +496,14 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { return proxies["DIRECT"], nil, nil } +func getRules(metadata *C.Metadata) []C.Rule { + if sr, ok := subRules[metadata.PreferRulesName]; ok { + return sr + } else { + return rules + } +} + func retry[T any](ctx context.Context, ft func(context.Context) (T, error), fe func(err error)) (t T, err error) { b := &backoff.Backoff{ Min: 10 * time.Millisecond,