diff --git a/common/net/tls.go b/common/net/tls.go index 65391666..4f5263b8 100644 --- a/common/net/tls.go +++ b/common/net/tls.go @@ -4,7 +4,8 @@ import ( "crypto/tls" "fmt" ) -func ParseCert(certificate,privateKey string) (tls.Certificate, error) { + +func ParseCert(certificate, privateKey string) (tls.Certificate, error) { cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey)) if painTextErr == nil { return cert, nil @@ -12,7 +13,7 @@ func ParseCert(certificate,privateKey string) (tls.Certificate, error) { cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey) if loadErr != nil { - return tls.Certificate{}, fmt.Errorf("parse certificate failed,maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) + return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) } return cert, nil -} \ No newline at end of file +} diff --git a/docs/config.yaml b/docs/config.yaml index 9507bbb8..e58746e0 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -695,3 +695,18 @@ listeners: listen: 0.0.0.0 # udp: false # 默认 true # rule: sub-rule + + - name: tuic-in-1 + type: tuic + port: 10813 + listen: 0.0.0.0 + # token: + # - TOKEN + # certificate: ./server.crt + # private-key: ./server.key + # congestion-controller: bbr + # max-idle-time: 15000 + # authentication-timeout: 1000 + # alpn: + # - h3 + # max-udp-relay-packet-size: 1500 diff --git a/listener/inbound/tuic.go b/listener/inbound/tuic.go new file mode 100644 index 00000000..e64d53bf --- /dev/null +++ b/listener/inbound/tuic.go @@ -0,0 +1,86 @@ +package inbound + +import ( + C "github.com/Dreamacro/clash/constant" + LC "github.com/Dreamacro/clash/listener/config" + "github.com/Dreamacro/clash/listener/tuic" + "github.com/Dreamacro/clash/log" +) + +type TuicOption struct { + BaseOption + Token []string `inbound:"token"` + Certificate string `inbound:"certificate"` + PrivateKey string `inbound:"private-key"` + CongestionController string `inbound:"congestion-controllerr,omitempty"` + MaxIdleTime int `inbound:"max-idle-timer,omitempty"` + AuthenticationTimeout int `inbound:"authentication-timeoutr,omitempty"` + ALPN []string `inbound:"alpnr,omitempty"` + MaxUdpRelayPacketSize int `inbound:"max-udp-relay-packet-sizer,omitempty"` +} + +func (o TuicOption) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) +} + +type Tuic struct { + *Base + config *TuicOption + l *tuic.Listener +} + +func NewTuic(options *TuicOption) (*Tuic, error) { + base, err := NewBase(&options.BaseOption) + if err != nil { + return nil, err + } + return &Tuic{ + Base: base, + config: options, + }, nil + +} + +// Config implements constant.InboundListener +func (t *Tuic) Config() C.InboundConfig { + return t.config +} + +// Address implements constant.InboundListener +func (t *Tuic) Address() string { + if t.l != nil { + for _, addr := range t.l.AddrList() { + return addr.String() + } + } + return "" +} + +// Listen implements constant.InboundListener +func (t *Tuic) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { + var err error + t.l, err = tuic.New(LC.TuicServer{ + Enable: true, + Listen: t.RawAddress(), + Token: t.config.Token, + Certificate: t.config.Certificate, + PrivateKey: t.config.PrivateKey, + CongestionController: t.config.CongestionController, + MaxIdleTime: t.config.MaxIdleTime, + AuthenticationTimeout: t.config.AuthenticationTimeout, + ALPN: t.config.ALPN, + MaxUdpRelayPacketSize: t.config.MaxUdpRelayPacketSize, + }, tcpIn, udpIn) + if err != nil { + return err + } + log.Infoln("Tuic[%s] proxy listening at: %s", t.Name(), t.Address()) + return nil +} + +// Close implements constant.InboundListener +func (t *Tuic) Close() error { + return t.l.Close() +} + +var _ C.InboundListener = (*Tuic)(nil) diff --git a/listener/listener.go b/listener/listener.go index 4c976b31..18b13b7f 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -371,6 +371,9 @@ func ReCreateTuic(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- tuicListener = listener + for _, addr := range tuicListener.AddrList() { + log.Infoln("Tuic proxy listening at: %s", addr.String()) + } return } diff --git a/listener/parse.go b/listener/parse.go index 04573c71..6270daf1 100644 --- a/listener/parse.go +++ b/listener/parse.go @@ -55,6 +55,18 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) { return nil, err } listener, err = IN.NewMixed(mixedOption) + case "tuic": + tuicOption := &IN.TuicOption{ + MaxIdleTime: 15000, + AuthenticationTimeout: 1000, + ALPN: []string{"h3"}, + MaxUdpRelayPacketSize: 1500, + } + err = decoder.Decode(mapping, tuicOption) + if err != nil { + return nil, err + } + listener, err = IN.NewTuic(tuicOption) default: return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) } diff --git a/listener/tuic/server.go b/listener/tuic/server.go index 82f208cb..1e7209a8 100644 --- a/listener/tuic/server.go +++ b/listener/tuic/server.go @@ -9,6 +9,7 @@ import ( "github.com/metacubex/quic-go" "github.com/Dreamacro/clash/adapter/inbound" + CN "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/sockopt" C "github.com/Dreamacro/clash/constant" LC "github.com/Dreamacro/clash/listener/config" @@ -25,7 +26,11 @@ type Listener struct { } func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) (*Listener, error) { - cert, err := tls.LoadX509KeyPair(config.Certificate, config.PrivateKey) + return NewWithInfos("DEFAULT-TUIC", "", config, tcpIn, udpIn) +} + +func NewWithInfos(name, specialRules string, config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) (*Listener, error) { + cert, err := CN.ParseCert(config.Certificate, config.PrivateKey) if err != nil { return nil, err } @@ -56,12 +61,12 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet option := &tuic.ServerOption{ HandleTcpFn: func(conn net.Conn, addr socks5.Addr) error { - tcpIn <- inbound.NewSocket(addr, conn, C.TUIC) + tcpIn <- inbound.NewSocketWithInfos(addr, conn, C.TUIC, name, specialRules) return nil }, HandleUdpFn: func(addr socks5.Addr, packet C.UDPPacket) error { select { - case udpIn <- inbound.NewPacket(addr, packet, C.TUIC): + case udpIn <- inbound.NewPacketWithInfos(addr, packet, C.TUIC, name, specialRules): default: } return nil @@ -99,7 +104,6 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet sl.servers = append(sl.servers, server) go func() { - log.Infoln("Tuic proxy listening at: %s", ul.LocalAddr().String()) err := server.Serve() if err != nil { if sl.closed { @@ -112,16 +116,32 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet return sl, nil } -func (l *Listener) Close() { +// Close implements C.Listener +func (l *Listener) Close() error { l.closed = true + var retErr error for _, lis := range l.servers { - _ = lis.Close() + err := lis.Close() + if err != nil { + retErr = err + } } for _, lis := range l.udpListeners { - _ = lis.Close() + err := lis.Close() + if err != nil { + retErr = err + } } + return retErr } func (l *Listener) Config() LC.TuicServer { return l.config } + +func (l *Listener) AddrList() (addrList []net.Addr) { + for _, lis := range l.udpListeners { + addrList = append(addrList, lis.LocalAddr()) + } + return +} \ No newline at end of file