From 5e7d694e885d50dce52a1ab53902339b57ce96d1 Mon Sep 17 00:00:00 2001 From: anytls Date: Mon, 17 Feb 2025 15:24:40 +0900 Subject: [PATCH] feat: implement anytls server --- constant/metadata.go | 5 + listener/anytls/server.go | 181 +++++++++++++++++++++++++++++++++++++ listener/config/anytls.go | 19 ++++ listener/inbound/anytls.go | 79 ++++++++++++++++ listener/parse.go | 7 ++ 5 files changed, 291 insertions(+) create mode 100644 listener/anytls/server.go create mode 100644 listener/config/anytls.go create mode 100644 listener/inbound/anytls.go diff --git a/constant/metadata.go b/constant/metadata.go index e4167845..ac676b04 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -32,6 +32,7 @@ const ( TUN TUIC HYSTERIA2 + ANYTLS INNER ) @@ -84,6 +85,8 @@ func (t Type) String() string { return "Tuic" case HYSTERIA2: return "Hysteria2" + case ANYTLS: + return "AnyTLS" case INNER: return "Inner" default: @@ -120,6 +123,8 @@ func ParseType(t string) (*Type, error) { res = TUIC case "HYSTERIA2": res = HYSTERIA2 + case "ANYTLS": + res = ANYTLS case "INNER": res = INNER default: diff --git a/listener/anytls/server.go b/listener/anytls/server.go new file mode 100644 index 00000000..5d860e8a --- /dev/null +++ b/listener/anytls/server.go @@ -0,0 +1,181 @@ +package anytls + +import ( + "context" + "crypto/sha256" + "crypto/tls" + "encoding/binary" + "errors" + "net" + "strings" + + "github.com/metacubex/mihomo/adapter/inbound" + "github.com/metacubex/mihomo/common/buf" + N "github.com/metacubex/mihomo/common/net" + C "github.com/metacubex/mihomo/constant" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/listener/sing" + "github.com/metacubex/mihomo/transport/anytls/padding" + "github.com/metacubex/mihomo/transport/anytls/session" + "github.com/sagernet/sing/common/atomic" + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" +) + +type Listener struct { + closed bool + config LC.AnyTLSServer + listeners []net.Listener + tlsConfig *tls.Config + userMap map[[32]byte]string + padding atomic.TypedValue[*padding.PaddingFactory] +} + +func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) { + if len(additions) == 0 { + additions = []inbound.Addition{ + inbound.WithInName("DEFAULT-ANYTLS"), + inbound.WithSpecialRules(""), + } + } + + tlsConfig := &tls.Config{} + if config.Certificate != "" && config.PrivateKey != "" { + cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path) + if err != nil { + return nil, err + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + + sl = &Listener{ + config: config, + tlsConfig: tlsConfig, + userMap: make(map[[32]byte]string), + } + + for user, password := range config.Users { + sl.userMap[sha256.Sum256([]byte(password))] = user + } + + if len(config.PaddingScheme) > 0 { + if !padding.UpdatePaddingScheme([]byte(config.PaddingScheme), &sl.padding) { + return nil, errors.New("incorrect padding scheme format") + } + } else { + padding.UpdatePaddingScheme(padding.DefaultPaddingScheme, &sl.padding) + } + + // Using sing handler can automatically handle UoT + h, err := sing.NewListenerHandler(sing.ListenerConfig{ + Tunnel: tunnel, + Type: C.ANYTLS, + Additions: additions, + }) + if err != nil { + return nil, err + } + + for _, addr := range strings.Split(config.Listen, ",") { + addr := addr + + //TCP + l, err := inbound.Listen("tcp", addr) + if err != nil { + return nil, err + } + sl.listeners = append(sl.listeners, l) + + go func() { + for { + c, err := l.Accept() + if err != nil { + if sl.closed { + break + } + continue + } + go sl.HandleConn(c, h) + } + }() + } + + return sl, nil +} + +func (l *Listener) Close() error { + l.closed = true + var retErr error + for _, lis := range l.listeners { + err := lis.Close() + if err != nil { + retErr = err + } + } + return retErr +} + +func (l *Listener) Config() string { + return l.config.String() +} + +func (l *Listener) AddrList() (addrList []net.Addr) { + for _, lis := range l.listeners { + addrList = append(addrList, lis.Addr()) + } + return +} + +func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) { + ctx := context.TODO() + + conn = tls.Server(conn, l.tlsConfig) + defer conn.Close() + + b := buf.NewPacket() + _, err := b.ReadOnceFrom(conn) + if err != nil { + return + } + conn = bufio.NewCachedConn(conn, b) + + by, err := b.ReadBytes(32) + if err != nil { + return + } + var passwordSha256 [32]byte + copy(passwordSha256[:], by) + if user, ok := l.userMap[passwordSha256]; ok { + ctx = auth.ContextWithUser(ctx, user) + } else { + return + } + by, err = b.ReadBytes(2) + if err != nil { + return + } + paddingLen := binary.BigEndian.Uint16(by) + if paddingLen > 0 { + _, err = b.ReadBytes(int(paddingLen)) + if err != nil { + return + } + } + + session := session.NewServerSession(conn, func(stream *session.Stream) { + defer stream.Close() + + destination, err := M.SocksaddrSerializer.ReadAddrPort(stream) + if err != nil { + return + } + + h.NewConnection(ctx, stream, M.Metadata{ + Source: M.SocksaddrFromNet(conn.RemoteAddr()), + Destination: destination, + }) + }, &l.padding) + session.Run(true) + session.Close() +} diff --git a/listener/config/anytls.go b/listener/config/anytls.go new file mode 100644 index 00000000..adbafa60 --- /dev/null +++ b/listener/config/anytls.go @@ -0,0 +1,19 @@ +package config + +import ( + "encoding/json" +) + +type AnyTLSServer struct { + Enable bool `yaml:"enable" json:"enable"` + Listen string `yaml:"listen" json:"listen"` + Users map[string]string `yaml:"users" json:"users,omitempty"` + Certificate string `yaml:"certificate" json:"certificate"` + PrivateKey string `yaml:"private-key" json:"private-key"` + PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"` +} + +func (t AnyTLSServer) String() string { + b, _ := json.Marshal(t) + return string(b) +} diff --git a/listener/inbound/anytls.go b/listener/inbound/anytls.go new file mode 100644 index 00000000..a995bf4f --- /dev/null +++ b/listener/inbound/anytls.go @@ -0,0 +1,79 @@ +package inbound + +import ( + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/listener/anytls" + LC "github.com/metacubex/mihomo/listener/config" + "github.com/metacubex/mihomo/log" +) + +type AnyTLSOption struct { + BaseOption + Users map[string]string `inbound:"users,omitempty"` + Certificate string `inbound:"certificate"` + PrivateKey string `inbound:"private-key"` + PaddingScheme string `inbound:"padding-scheme,omitempty"` +} + +func (o AnyTLSOption) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) +} + +type AnyTLS struct { + *Base + config *AnyTLSOption + l C.MultiAddrListener + vs LC.AnyTLSServer +} + +func NewAnyTLS(options *AnyTLSOption) (*AnyTLS, error) { + base, err := NewBase(&options.BaseOption) + if err != nil { + return nil, err + } + return &AnyTLS{ + Base: base, + config: options, + vs: LC.AnyTLSServer{ + Enable: true, + Listen: base.RawAddress(), + Users: options.Users, + Certificate: options.Certificate, + PrivateKey: options.PrivateKey, + PaddingScheme: options.PaddingScheme, + }, + }, nil +} + +// Config implements constant.InboundListener +func (v *AnyTLS) Config() C.InboundConfig { + return v.config +} + +// Address implements constant.InboundListener +func (v *AnyTLS) Address() string { + if v.l != nil { + for _, addr := range v.l.AddrList() { + return addr.String() + } + } + return "" +} + +// Listen implements constant.InboundListener +func (v *AnyTLS) Listen(tunnel C.Tunnel) error { + var err error + v.l, err = anytls.New(v.vs, tunnel, v.Additions()...) + if err != nil { + return err + } + log.Infoln("AnyTLS[%s] proxy listening at: %s", v.Name(), v.Address()) + return nil +} + +// Close implements constant.InboundListener +func (v *AnyTLS) Close() error { + return v.l.Close() +} + +var _ C.InboundListener = (*AnyTLS)(nil) diff --git a/listener/parse.go b/listener/parse.go index 38082e92..5c5d6c7e 100644 --- a/listener/parse.go +++ b/listener/parse.go @@ -113,6 +113,13 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) { return nil, err } listener, err = IN.NewTuic(tuicOption) + case "anytls": + anytlsOption := &IN.AnyTLSOption{} + err = decoder.Decode(mapping, anytlsOption) + if err != nil { + return nil, err + } + listener, err = IN.NewAnyTLS(anytlsOption) default: return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) }