Skip to content

feat: implement anytls client and server #1844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions adapter/outbound/anytls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package outbound

import (
"context"
"crypto/tls"
"errors"
"net"
"runtime"
"strconv"
"time"

CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/uot"
)

type AnyTLS struct {
*Base
client *anytls.Client
dialer proxydialer.SingDialer
option *AnyTLSOption
}

type AnyTLSOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
ALPN []string `proxy:"alpn,omitempty"`
SNI string `proxy:"sni,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
UDP bool `proxy:"udp,omitempty"`
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
}

func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := t.Base.DialOptions(opts...)
t.dialer.SetDialer(dialer.NewDialer(options...))
c, err := t.client.CreateProxy(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}
return NewConn(CN.NewRefConn(c, t), t), nil
}

func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
// create tcp
options := t.Base.DialOptions(opts...)
t.dialer.SetDialer(dialer.NewDialer(options...))
c, err := t.client.CreateProxy(ctx, uot.RequestDestination(2))
if err != nil {
return nil, err
}

// create uot on tcp
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
destination := M.SocksaddrFromNet(metadata.UDPAddr())
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), t), nil
}

// SupportUOT implements C.ProxyAdapter
func (t *AnyTLS) SupportUOT() bool {
return true
}

// ProxyInfo implements C.ProxyAdapter
func (t *AnyTLS) ProxyInfo() C.ProxyInfo {
info := t.Base.ProxyInfo()
info.DialerProxy = t.option.DialerProxy
return info
}

func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))

singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())

tOption := anytls.ClientConfig{
Password: option.Password,
Server: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)),
Dialer: singDialer,
IdleSessionCheckInterval: time.Duration(option.IdleSessionCheckInterval) * time.Second,
IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second,
ClientFingerprint: option.ClientFingerprint,
}
tlsConfig := &tls.Config{
ServerName: option.SNI,
InsecureSkipVerify: option.SkipCertVerify,
NextProtos: option.ALPN,
}
if tlsConfig.ServerName == "" {
tlsConfig.ServerName = "127.0.0.1"
}
tOption.TLSConfig = tlsConfig

if tlsC.HaveGlobalFingerprint() && len(tOption.ClientFingerprint) == 0 {
tOption.ClientFingerprint = tlsC.GetGlobalFingerprint()
}

outbound := &AnyTLS{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.AnyTLS,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
client: anytls.NewClient(context.TODO(), tOption),
option: &option,
dialer: singDialer,
}
runtime.SetFinalizer(outbound, func(o *AnyTLS) {
common.Close(o.client)
})

return outbound, nil
}
7 changes: 7 additions & 0 deletions adapter/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
break
}
proxy, err = outbound.NewMieru(*mieruOption)
case "anytls":
anytlsOption := &outbound.AnyTLSOption{}
err = decoder.Decode(mapping, anytlsOption)
if err != nil {
break
}
proxy, err = outbound.NewAnyTLS(*anytlsOption)
default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
}
Expand Down
3 changes: 3 additions & 0 deletions constant/adapters.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const (
Tuic
Ssh
Mieru
AnyTLS
)

const (
Expand Down Expand Up @@ -229,6 +230,8 @@ func (at AdapterType) String() string {
return "Ssh"
case Mieru:
return "Mieru"
case AnyTLS:
return "AnyTLS"
case Relay:
return "Relay"
case Selector:
Expand Down
5 changes: 5 additions & 0 deletions constant/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
TUN
TUIC
HYSTERIA2
ANYTLS
INNER
)

Expand Down Expand Up @@ -84,6 +85,8 @@ func (t Type) String() string {
return "Tuic"
case HYSTERIA2:
return "Hysteria2"
case ANYTLS:
return "AnyTLS"
case INNER:
return "Inner"
default:
Expand Down Expand Up @@ -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:
Expand Down
28 changes: 28 additions & 0 deletions docs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,22 @@ proxies: # socks5
# 可以使用的值包括 MULTIPLEXING_OFF, MULTIPLEXING_LOW, MULTIPLEXING_MIDDLE, MULTIPLEXING_HIGH。其中 MULTIPLEXING_OFF 会关闭多路复用功能。默认值为 MULTIPLEXING_LOW。
# multiplexing: MULTIPLEXING_LOW

# anytls
- name: anytls
type: anytls
server: 1.2.3.4
port: 443
password: "<your password>"
# client-fingerprint: chrome
udp: true
# idle-session-check-interval: 30 # seconds
# idle-session-timeout: 30 # seconds
# sni: "example.com"
# alpn:
# - h2
# - http/1.1
# skip-cert-verify: true

# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
- name: "dns-out"
type: dns
Expand Down Expand Up @@ -1209,6 +1225,18 @@ listeners:
- test.com
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ###

- name: anytls-in-1
type: anytls
port: 10818
listen: 0.0.0.0
users:
username1: password1
username2: password2
# "certificate" and "private-key" are required
certificate: ./server.crt
private-key: ./server.key
# padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme

- name: tun-in-1
type: tun
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
Expand Down
Loading