diff --git a/adapter/parser.go b/adapter/parser.go index 3ca0c3ee..86fe96f9 100644 --- a/adapter/parser.go +++ b/adapter/parser.go @@ -2,17 +2,13 @@ package adapter import ( "fmt" - "strings" - "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" ) -var keyReplacer = strings.NewReplacer("_", "-") - func ParseProxy(mapping map[string]any) (C.Proxy, error) { - decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true, KeyReplacer: keyReplacer}) + decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true, KeyReplacer: structure.DefaultKeyReplacer}) proxyType, existType := mapping["type"].(string) if !existType { return nil, fmt.Errorf("missing type") diff --git a/common/structure/structure.go b/common/structure/structure.go index 958ad54b..c2ae393d 100644 --- a/common/structure/structure.go +++ b/common/structure/structure.go @@ -16,6 +16,8 @@ type Option struct { KeyReplacer *strings.Replacer } +var DefaultKeyReplacer = strings.NewReplacer("_", "-") + // Decoder is the core of structure type Decoder struct { option *Option diff --git a/config/config.go b/config/config.go index c44ac250..0dc100f4 100644 --- a/config/config.go +++ b/config/config.go @@ -200,7 +200,7 @@ type Config struct { SubRules map[string][]C.Rule Users []auth.AuthUser Proxies map[string]C.Proxy - Listeners map[string]C.NewListener + Listeners map[string]C.InboundListener Providers map[string]providerTypes.ProxyProvider RuleProviders map[string]providerTypes.RuleProvider Tunnels []tunnel.Tunnel @@ -694,8 +694,8 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ return proxies, providersMap, nil } -func parseListeners(cfg *RawConfig) (listeners map[string]C.NewListener, err error) { - listeners = make(map[string]C.NewListener) +func parseListeners(cfg *RawConfig) (listeners map[string]C.InboundListener, err error) { + listeners = make(map[string]C.InboundListener) for index, mapping := range cfg.Listeners { listener, err := L.ParseListener(mapping) if err != nil { diff --git a/constant/listener.go b/constant/listener.go index a6696af4..09ecdb76 100644 --- a/constant/listener.go +++ b/constant/listener.go @@ -14,11 +14,16 @@ type AdvanceListener interface { HandleConn(conn net.Conn, in chan<- ConnContext) } -type NewListener interface { +type InboundListener interface { Name() string Listen(tcpIn chan<- ConnContext, udpIn chan<- PacketAdapter) error Close() error Address() string RawAddress() string - Config() string + Config() InboundConfig +} + +type InboundConfig interface { + Name() string + Equal(config InboundConfig) bool } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 94db8c0f..32737c02 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -135,31 +135,11 @@ func GetGeneral() *config.General { return general } -func updateListeners(listeners map[string]C.NewListener) { +func updateListeners(listeners map[string]C.InboundListener) { tcpIn := tunnel.TCPIn() udpIn := tunnel.UDPIn() - skipNames := map[string]struct{}{} - for name, oldListener := range tunnel.Listeners() { - if newListener, ok := listeners[name]; ok { - if newListener.Config() == oldListener.Config() { - listeners[name] = oldListener - skipNames[name] = struct{}{} - continue - } - } - _ = oldListener.Close() - } - - for name, newListener := range listeners { - if _, ok := skipNames[name]; ok { - continue - } - if err := newListener.Listen(tcpIn, udpIn); err != nil { - log.Errorln("Listener %s listen err: %s", newListener.Name(), err.Error()) - } - } - tunnel.UpdateListeners(listeners) + listener.PatchInboundListeners(listeners, tcpIn, udpIn, true) } func updateExperimental(c *config.Config) { diff --git a/listener/inbound/base.go b/listener/inbound/base.go index ebe65309..eee3cdce 100644 --- a/listener/inbound/base.go +++ b/listener/inbound/base.go @@ -26,7 +26,7 @@ func NewBase(options *BaseOption) (*Base, error) { return nil, err } return &Base{ - name: options.Name, + name: options.Name(), listenAddr: addr, preferRulesName: options.PreferRulesName, port: options.Port, @@ -34,44 +34,54 @@ func NewBase(options *BaseOption) (*Base, error) { }, nil } -// Config implements constant.NewListener -func (b *Base) Config() string { - return optionToString(b.config) +// Config implements constant.InboundListener +func (b *Base) Config() C.InboundConfig { + return b.config } -// Address implements constant.NewListener +// Address implements constant.InboundListener func (b *Base) Address() string { return b.RawAddress() } -// Close implements constant.NewListener +// Close implements constant.InboundListener func (*Base) Close() error { return nil } -// Name implements constant.NewListener +// Name implements constant.InboundListener func (b *Base) Name() string { return b.name } -// RawAddress implements constant.NewListener +// RawAddress implements constant.InboundListener func (b *Base) RawAddress() string { return net.JoinHostPort(b.listenAddr.String(), strconv.Itoa(int(b.port))) } -// Listen implements constant.NewListener +// Listen implements constant.InboundListener func (*Base) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { return nil } +var _ C.InboundListener = (*Base)(nil) + type BaseOption struct { - Name string `inbound:"name"` + NameStr string `inbound:"name"` Listen string `inbound:"listen,omitempty"` Port int `inbound:"port"` PreferRulesName string `inbound:"rule,omitempty"` } -var _ C.NewListener = (*Base)(nil) +func (o BaseOption) Name() string { + return o.NameStr +} + +func (o BaseOption) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) +} + +var _ C.InboundConfig = (*BaseOption)(nil) func optionToString(option any) string { str, _ := json.Marshal(option) diff --git a/listener/inbound/http.go b/listener/inbound/http.go index 6f04b4b6..cf373ed7 100644 --- a/listener/inbound/http.go +++ b/listener/inbound/http.go @@ -9,6 +9,11 @@ import ( type HTTPOption struct { BaseOption } + +func (o HTTPOption) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) +} + type HTTP struct { *Base config *HTTPOption @@ -26,17 +31,17 @@ func NewHTTP(options *HTTPOption) (*HTTP, error) { }, nil } -// Config implements constant.NewListener -func (h *HTTP) Config() string { - return optionToString(h.config) +// Config implements constant.InboundListener +func (h *HTTP) Config() C.InboundConfig { + return h.config } -// Address implements constant.NewListener +// Address implements constant.InboundListener func (h *HTTP) Address() string { return h.l.Address() } -// Listen implements constant.NewListener +// Listen implements constant.InboundListener func (h *HTTP) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { var err error h.l, err = http.NewWithInfos(h.RawAddress(), h.name, h.preferRulesName, tcpIn) @@ -47,7 +52,7 @@ func (h *HTTP) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) return nil } -// Close implements constant.NewListener +// Close implements constant.InboundListener func (h *HTTP) Close() error { if h.l != nil { return h.l.Close() @@ -55,4 +60,4 @@ func (h *HTTP) Close() error { return nil } -var _ C.NewListener = (*HTTP)(nil) +var _ C.InboundListener = (*HTTP)(nil) diff --git a/listener/inbound/mixed.go b/listener/inbound/mixed.go index 01120dd7..222de4c5 100644 --- a/listener/inbound/mixed.go +++ b/listener/inbound/mixed.go @@ -12,7 +12,11 @@ import ( type MixedOption struct { BaseOption - UDP *bool `inbound:"udp,omitempty"` + UDP bool `inbound:"udp,omitempty"` +} + +func (o MixedOption) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) } type Mixed struct { @@ -31,21 +35,21 @@ func NewMixed(options *MixedOption) (*Mixed, error) { return &Mixed{ Base: base, config: options, - udp: options.UDP == nil || *options.UDP, + udp: options.UDP, }, nil } -// Config implements constant.NewListener -func (m *Mixed) Config() string { - return optionToString(m.config) +// Config implements constant.InboundListener +func (m *Mixed) Config() C.InboundConfig { + return m.config } -// Address implements constant.NewListener +// Address implements constant.InboundListener func (m *Mixed) Address() string { return m.l.Address() } -// Listen implements constant.NewListener +// Listen implements constant.InboundListener func (m *Mixed) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { var err error m.l, err = mixed.NewWithInfos(m.RawAddress(), m.name, m.preferRulesName, tcpIn) @@ -62,7 +66,7 @@ func (m *Mixed) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) return nil } -// Close implements constant.NewListener +// Close implements constant.InboundListener func (m *Mixed) Close() error { var err error if m.l != nil { @@ -82,4 +86,4 @@ func (m *Mixed) Close() error { return err } -var _ C.NewListener = (*Mixed)(nil) +var _ C.InboundListener = (*Mixed)(nil) diff --git a/listener/inbound/redir.go b/listener/inbound/redir.go index 6d6ca4f0..1809c3bc 100644 --- a/listener/inbound/redir.go +++ b/listener/inbound/redir.go @@ -10,6 +10,10 @@ type RedirOption struct { BaseOption } +func (o RedirOption) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) +} + type Redir struct { *Base config *RedirOption @@ -27,17 +31,17 @@ func NewRedir(options *RedirOption) (*Redir, error) { }, nil } -// Config implements constant.NewListener -func (r *Redir) Config() string { - return optionToString(r.config) +// Config implements constant.InboundListener +func (r *Redir) Config() C.InboundConfig { + return r.config } -// Address implements constant.NewListener +// Address implements constant.InboundListener func (r *Redir) Address() string { return r.l.Address() } -// Listen implements constant.NewListener +// Listen implements constant.InboundListener func (r *Redir) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { var err error r.l, err = redir.NewWithInfos(r.Address(), r.name, r.preferRulesName, tcpIn) @@ -48,7 +52,7 @@ func (r *Redir) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) return nil } -// Close implements constant.NewListener +// Close implements constant.InboundListener func (r *Redir) Close() error { if r.l != nil { r.l.Close() @@ -56,4 +60,4 @@ func (r *Redir) Close() error { return nil } -var _ C.NewListener = (*Redir)(nil) +var _ C.InboundListener = (*Redir)(nil) diff --git a/listener/inbound/socks.go b/listener/inbound/socks.go index 9e826bc3..10df3568 100644 --- a/listener/inbound/socks.go +++ b/listener/inbound/socks.go @@ -9,7 +9,11 @@ import ( type SocksOption struct { BaseOption - UDP *bool `inbound:"udp,omitempty"` + UDP bool `inbound:"udp,omitempty"` +} + +func (o SocksOption) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) } type Socks struct { @@ -28,16 +32,16 @@ func NewSocks(options *SocksOption) (*Socks, error) { return &Socks{ Base: base, config: options, - udp: options.UDP == nil || *options.UDP, + udp: options.UDP, }, nil } -// Config implements constant.NewListener -func (s *Socks) Config() string { - return optionToString(s.config) +// Config implements constant.InboundListener +func (s *Socks) Config() C.InboundConfig { + return s.config } -// Close implements constant.NewListener +// Close implements constant.InboundListener func (s *Socks) Close() error { var err error if s.stl != nil { @@ -58,12 +62,12 @@ func (s *Socks) Close() error { return err } -// Address implements constant.NewListener +// Address implements constant.InboundListener func (s *Socks) Address() string { return s.stl.Address() } -// Listen implements constant.NewListener +// Listen implements constant.InboundListener func (s *Socks) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { var err error if s.stl, err = socks.NewWithInfos(s.RawAddress(), s.name, s.preferRulesName, tcpIn); err != nil { @@ -79,4 +83,4 @@ func (s *Socks) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) return nil } -var _ C.NewListener = (*Socks)(nil) +var _ C.InboundListener = (*Socks)(nil) diff --git a/listener/inbound/tproxy.go b/listener/inbound/tproxy.go index 3a86919c..ff5893be 100644 --- a/listener/inbound/tproxy.go +++ b/listener/inbound/tproxy.go @@ -10,7 +10,11 @@ import ( type TProxyOption struct { BaseOption - UDP *bool `inbound:"udp,omitempty"` + UDP bool `inbound:"udp,omitempty"` +} + +func (o TProxyOption) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) } type TProxy struct { @@ -29,22 +33,22 @@ func NewTProxy(options *TProxyOption) (*TProxy, error) { return &TProxy{ Base: base, config: options, - udp: options.UDP == nil || *options.UDP, + udp: options.UDP, }, nil } -// Config implements constant.NewListener -func (t *TProxy) Config() string { - return optionToString(t.config) +// Config implements constant.InboundListener +func (t *TProxy) Config() C.InboundConfig { + return t.config } -// Address implements constant.NewListener +// Address implements constant.InboundListener func (t *TProxy) Address() string { return t.lTCP.Address() } -// Listen implements constant.NewListener +// Listen implements constant.InboundListener func (t *TProxy) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { var err error t.lTCP, err = tproxy.NewWithInfos(t.RawAddress(), t.name, t.preferRulesName, tcpIn) @@ -64,7 +68,7 @@ func (t *TProxy) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter return nil } -// Close implements constant.NewListener +// Close implements constant.InboundListener func (t *TProxy) Close() error { var tcpErr error var udpErr error @@ -87,4 +91,4 @@ func (t *TProxy) Close() error { return nil } -var _ C.NewListener = (*TProxy)(nil) +var _ C.InboundListener = (*TProxy)(nil) diff --git a/listener/listener.go b/listener/listener.go index 836bfb2a..4c976b31 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -43,6 +43,7 @@ var ( mixedUDPLister *socks.UDPListener tunnelTCPListeners = map[string]*tunnel.Listener{} tunnelUDPListeners = map[string]*tunnel.PacketConn{} + inboundListeners = map[string]C.InboundListener{} tunLister *sing_tun.Listener shadowSocksListener C.AdvanceListener vmessListener *sing_vmess.Listener @@ -58,6 +59,7 @@ var ( tproxyMux sync.Mutex mixedMux sync.Mutex tunnelMux sync.Mutex + inboundMux sync.Mutex tunMux sync.Mutex ssMux sync.Mutex vmessMux sync.Mutex @@ -682,6 +684,35 @@ func PatchTunnel(tunnels []tunnel.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan } } +func PatchInboundListeners(newListenerMap map[string]C.InboundListener, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, dropOld bool) { + inboundMux.Lock() + defer inboundMux.Unlock() + + for name, newListener := range newListenerMap { + if oldListener, ok := inboundListeners[name]; ok { + if !oldListener.Config().Equal(newListener.Config()) { + _ = oldListener.Close() + } else { + continue + } + } + if err := newListener.Listen(tcpIn, udpIn); err != nil { + log.Errorln("Listener %s listen err: %s", name, err.Error()) + continue + } + inboundListeners[name] = newListener + } + + if dropOld { + for name, oldListener := range inboundListeners { + if _, ok := newListenerMap[name]; !ok { + _ = oldListener.Close() + delete(inboundListeners, name) + } + } + } +} + // GetPorts return the ports of proxy servers func GetPorts() *Ports { ports := &Ports{} diff --git a/listener/parse.go b/listener/parse.go index ee85e26c..04573c71 100644 --- a/listener/parse.go +++ b/listener/parse.go @@ -2,29 +2,26 @@ 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}) +func ParseListener(mapping map[string]any) (C.InboundListener, error) { + decoder := structure.NewDecoder(structure.Option{TagName: "inbound", WeaklyTypedInput: true, KeyReplacer: structure.DefaultKeyReplacer}) proxyType, existType := mapping["type"].(string) if !existType { return nil, fmt.Errorf("missing type") } var ( - listener C.NewListener + listener C.InboundListener err error ) switch proxyType { case "socks": - socksOption := &IN.SocksOption{} + socksOption := &IN.SocksOption{UDP: true} err = decoder.Decode(mapping, socksOption) if err != nil { return nil, err @@ -38,7 +35,7 @@ func ParseListener(mapping map[string]any) (C.NewListener, error) { } listener, err = IN.NewHTTP(httpOption) case "tproxy": - tproxyOption := &IN.TProxyOption{} + tproxyOption := &IN.TProxyOption{UDP: true} err = decoder.Decode(mapping, tproxyOption) if err != nil { return nil, err @@ -52,7 +49,7 @@ func ParseListener(mapping map[string]any) (C.NewListener, error) { } listener, err = IN.NewRedir(redirOption) case "mixed": - mixedOption := &IN.MixedOption{} + mixedOption := &IN.MixedOption{UDP: true} err = decoder.Decode(mapping, mixedOption) if err != nil { return nil, err diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 3be2f4aa..943bd917 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -29,7 +29,7 @@ var ( udpQueue = make(chan C.PacketAdapter, 200) natTable = nat.New() rules []C.Rule - listeners = make(map[string]C.NewListener) + listeners = make(map[string]C.InboundListener) subRules map[string][]C.Rule proxies = make(map[string]C.Proxy) providers map[string]provider.ProxyProvider @@ -87,7 +87,7 @@ func Rules() []C.Rule { return rules } -func Listeners() map[string]C.NewListener { +func Listeners() map[string]C.InboundListener { return listeners } @@ -123,7 +123,7 @@ func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provid configMux.Unlock() } -func UpdateListeners(newListeners map[string]C.NewListener) { +func UpdateListeners(newListeners map[string]C.InboundListener) { configMux.Lock() defer configMux.Unlock() listeners = newListeners