diff --git a/docs/config.yaml b/docs/config.yaml index c20228db..1df28909 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -671,11 +671,13 @@ listeners: #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 @@ -729,3 +731,12 @@ listeners: # alpn: # - h3 # max-udp-relay-packet-size: 1500 + + - name: tunnel-in-1 + type: tunnel + port: 10816 + listen: 0.0.0.0 + # rule: sub-rule + network: [ tcp, udp ] + target: target.com + diff --git a/listener/inbound/tunnel.go b/listener/inbound/tunnel.go new file mode 100644 index 00000000..683f58c9 --- /dev/null +++ b/listener/inbound/tunnel.go @@ -0,0 +1,92 @@ +package inbound + +import ( + "fmt" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/listener/tunnel" + "github.com/Dreamacro/clash/log" +) + +type TunnelOption struct { + BaseOption + Network []string `inbound:"network"` + Target string `inbound:"target"` + Proxy string `inbound:"proxy,omitempty"` +} + +func (o TunnelOption) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) +} + +type Tunnel struct { + *Base + config *TunnelOption + ttl *tunnel.Listener + tul *tunnel.PacketConn +} + +func NewTunnel(options *TunnelOption) (*Tunnel, error) { + base, err := NewBase(&options.BaseOption) + if err != nil { + return nil, err + } + return &Tunnel{ + Base: base, + config: options, + }, nil +} + +// Config implements constant.InboundListener +func (t *Tunnel) Config() C.InboundConfig { + return t.config +} + +// Close implements constant.InboundListener +func (t *Tunnel) Close() error { + var err error + if t.ttl != nil { + if tcpErr := t.ttl.Close(); tcpErr != nil { + err = tcpErr + } + } + if t.tul != nil { + if udpErr := t.tul.Close(); udpErr != nil { + if err == nil { + err = udpErr + } else { + return fmt.Errorf("close tcp err: %t, close udp err: %t", err.Error(), udpErr.Error()) + } + } + } + + return err +} + +// Address implements constant.InboundListener +func (t *Tunnel) Address() string { + return t.ttl.Address() +} + +// Listen implements constant.InboundListener +func (t *Tunnel) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { + var err error + for _, network := range t.config.Network { + switch network { + case "tcp": + if t.ttl, err = tunnel.New(t.RawAddress(), t.config.Target, t.config.Proxy, tcpIn, t.Additions()...); err != nil { + return err + } + case "udp": + if t.tul, err = tunnel.NewUDP(t.RawAddress(), t.config.Target, t.config.Proxy, udpIn, t.Additions()...); err != nil { + return err + } + default: + return fmt.Errorf("unknow network type: %s", network) + } + log.Infoln("Tunnel[%s](%s/%s)proxy listening at: %s", t.Name(), network, t.config.Target, t.Address()) + } + return nil +} + +var _ C.InboundListener = (*Tunnel)(nil) diff --git a/listener/parse.go b/listener/parse.go index eef1df16..deb4e10d 100644 --- a/listener/parse.go +++ b/listener/parse.go @@ -55,6 +55,13 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) { return nil, err } listener, err = IN.NewMixed(mixedOption) + case "tunnel": + tunnelOption := &IN.TunnelOption{} + err = decoder.Decode(mapping, tunnelOption) + if err != nil { + return nil, err + } + listener, err = IN.NewTunnel(tunnelOption) case "shadowsocks": shadowsocksOption := &IN.ShadowSocksOption{} err = decoder.Decode(mapping, shadowsocksOption) diff --git a/listener/tunnel/tcp.go b/listener/tunnel/tcp.go index 4ae5865c..bf278c1c 100644 --- a/listener/tunnel/tcp.go +++ b/listener/tunnel/tcp.go @@ -33,14 +33,14 @@ func (l *Listener) Close() error { return l.listener.Close() } -func (l *Listener) handleTCP(conn net.Conn, in chan<- C.ConnContext) { +func (l *Listener) handleTCP(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { conn.(*net.TCPConn).SetKeepAlive(true) - ctx := inbound.NewSocket(l.target, conn, C.TUNNEL) + ctx := inbound.NewSocket(l.target, conn, C.TUNNEL, additions...) ctx.Metadata().SpecialProxy = l.proxy in <- ctx } -func New(addr, target, proxy string, in chan<- C.ConnContext) (*Listener, error) { +func New(addr, target, proxy string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err @@ -67,7 +67,7 @@ func New(addr, target, proxy string, in chan<- C.ConnContext) (*Listener, error) } continue } - go rl.handleTCP(c, in) + go rl.handleTCP(c, in, additions...) } }() diff --git a/listener/tunnel/udp.go b/listener/tunnel/udp.go index 506e990f..0795084c 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<- C.PacketAdapter) (*PacketConn, error) { +func NewUDP(addr, target, proxy string, in chan<- C.PacketAdapter, additions ...inbound.Addition) (*PacketConn, error) { l, err := net.ListenPacket("udp", addr) if err != nil { return nil, err @@ -62,21 +62,21 @@ func NewUDP(addr, target, proxy string, in chan<- C.PacketAdapter) (*PacketConn, } continue } - sl.handleUDP(l, in, buf[:n], remoteAddr) + sl.handleUDP(l, in, buf[:n], remoteAddr, additions...) } }() return sl, nil } -func (l *PacketConn) handleUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, addr net.Addr) { +func (l *PacketConn) handleUDP(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, addr net.Addr, additions ...inbound.Addition) { packet := &packet{ pc: pc, rAddr: addr, payload: buf, } - ctx := inbound.NewPacket(l.target, packet, C.TUNNEL) + ctx := inbound.NewPacket(l.target, packet, C.TUNNEL, additions...) ctx.Metadata().SpecialProxy = l.proxy select { case in <- ctx: