mirror of
https://github.com/nadoo/glider.git
synced 2026-06-26 16:40:12 +08:00
anytls: support anytls
This commit is contained in:
parent
bb439c9345
commit
cb6f2d1e1a
@ -14,7 +14,7 @@ builds:
|
||||
- darwin
|
||||
- freebsd
|
||||
goarch:
|
||||
- 386
|
||||
- "386"
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
@ -27,8 +27,8 @@ builds:
|
||||
- v1
|
||||
- v3
|
||||
goarm:
|
||||
- 6
|
||||
- 7
|
||||
- "6"
|
||||
- "7"
|
||||
gomips:
|
||||
- hardfloat
|
||||
- softfloat
|
||||
@ -49,13 +49,13 @@ archives:
|
||||
- systemd/*
|
||||
|
||||
snapshot:
|
||||
version_template: '{{ incpatch .Version }}-dev-{{.ShortCommit}}'
|
||||
version_template: "{{ incpatch .Version }}-dev-{{.ShortCommit}}"
|
||||
|
||||
checksum:
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
|
||||
|
||||
release:
|
||||
prerelease: true
|
||||
prerelease: "true"
|
||||
draft: true
|
||||
|
||||
nfpms:
|
||||
@ -73,8 +73,8 @@ nfpms:
|
||||
dependencies:
|
||||
- libsystemd0
|
||||
bindir: /usr/bin
|
||||
release: 1
|
||||
epoch: 1
|
||||
release: "1"
|
||||
epoch: "1"
|
||||
version_metadata: git
|
||||
section: default
|
||||
priority: extra
|
||||
|
||||
12
README.md
12
README.md
@ -54,6 +54,7 @@ we can set up local listeners as proxy servers, and forward requests to internet
|
||||
|SS |√|√|√|√|client & server
|
||||
|Trojan |√|√|√|√|client & server
|
||||
|Trojanc |√|√|√|√|trojan cleartext(without tls)
|
||||
|AnyTLS |√| |√| |client & server
|
||||
|VLESS |√|√|√|√|client & server
|
||||
|VMess | | |√|√|client only
|
||||
|SSR | | |√| |client only
|
||||
@ -197,8 +198,8 @@ URL:
|
||||
-forward socks5://serverA:1080,socks5://serverB:1080 (proxy chain)
|
||||
|
||||
SCHEME:
|
||||
listen : http kcp mixed pxyproto redir redir6 smux sni socks5 ss tcp tls tproxy trojan trojanc udp unix vless vsock ws wss
|
||||
forward: direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh ssr tcp tls trojan trojanc udp unix vless vmess vsock ws wss
|
||||
listen : anytls http kcp mixed pxyproto redir redir6 smux sni socks5 ss tcp tls tproxy trojan trojanc udp unix vless vsock ws wss
|
||||
forward: anytls direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh ssr tcp tls trojan trojanc udp unix vless vmess vsock ws wss
|
||||
|
||||
Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme.
|
||||
|
||||
@ -334,6 +335,13 @@ Trojan server scheme:
|
||||
trojan://pass@host:port?cert=PATH&key=PATH[&fallback=127.0.0.1]
|
||||
trojanc://pass@host:port[?fallback=127.0.0.1] (cleartext, without TLS)
|
||||
|
||||
--
|
||||
AnyTLS client scheme:
|
||||
anytls://password@host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&synackTimeout=10s]
|
||||
|
||||
AnyTLS server scheme:
|
||||
anytls://password@host:port?cert=PATH&key=PATH
|
||||
|
||||
--
|
||||
Unix domain socket scheme:
|
||||
unix://path
|
||||
|
||||
@ -85,6 +85,9 @@ listen=127.0.0.1:8443
|
||||
# trojanc server (trojan without tls)
|
||||
# listen=trojanc://PASSWORD@:1234?fallback=127.0.0.1
|
||||
|
||||
# anytls server
|
||||
# listen=anytls://PASSWORD@:8443?cert=/path/to/cert&key=/path/to/key
|
||||
|
||||
# FORWARDERS
|
||||
# ----------
|
||||
# Forwarders, we can setup multiple forwarders.
|
||||
@ -127,6 +130,9 @@ listen=127.0.0.1:8443
|
||||
# trojanc as forwarder
|
||||
# forward=trojanc://PASSWORD@1.1.1.1:8080
|
||||
|
||||
# anytls as forwarder
|
||||
# forward=anytls://PASSWORD@1.1.1.1:8443[?serverName=SERVERNAME][&skipVerify=true]
|
||||
|
||||
# vless forwarder
|
||||
# forward=vless://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
// _ "github.com/nadoo/glider/service/xxx"
|
||||
|
||||
// comment out the protocols you don't need to make the compiled binary smaller.
|
||||
_ "github.com/nadoo/glider/proxy/anytls"
|
||||
_ "github.com/nadoo/glider/proxy/http"
|
||||
_ "github.com/nadoo/glider/proxy/kcp"
|
||||
_ "github.com/nadoo/glider/proxy/mixed"
|
||||
|
||||
110
proxy/anytls/anytls.go
Normal file
110
proxy/anytls/anytls.go
Normal file
@ -0,0 +1,110 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
type AnyTLS struct {
|
||||
dialer proxy.Dialer
|
||||
proxy proxy.Proxy
|
||||
|
||||
addr string
|
||||
password string
|
||||
serverName string
|
||||
skipVerify bool
|
||||
certFile string
|
||||
keyFile string
|
||||
tlsConfig *tls.Config
|
||||
|
||||
synackTimeout time.Duration
|
||||
padding paddingScheme
|
||||
}
|
||||
|
||||
func NewAnyTLS(s string, d proxy.Dialer, p proxy.Proxy) (*AnyTLS, error) {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] parse url err: %s", err)
|
||||
}
|
||||
query := u.Query()
|
||||
a := &AnyTLS{
|
||||
dialer: d,
|
||||
proxy: p,
|
||||
addr: u.Host,
|
||||
password: u.User.Username(),
|
||||
serverName: query.Get("serverName"),
|
||||
skipVerify: query.Get("skipVerify") == "true",
|
||||
certFile: query.Get("cert"),
|
||||
keyFile: query.Get("key"),
|
||||
synackTimeout: 10 * time.Second,
|
||||
}
|
||||
if a.password == "" {
|
||||
return nil, errors.New("[anytls] password must be specified")
|
||||
}
|
||||
if a.addr != "" {
|
||||
if _, port, _ := net.SplitHostPort(a.addr); port == "" {
|
||||
a.addr = net.JoinHostPort(a.addr, "443")
|
||||
}
|
||||
if a.serverName == "" {
|
||||
a.serverName = a.addr[:strings.LastIndex(a.addr, ":")]
|
||||
}
|
||||
}
|
||||
if timeout := query.Get("synackTimeout"); timeout != "" {
|
||||
d, err := time.ParseDuration(timeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] invalid synackTimeout: %s", err)
|
||||
}
|
||||
a.synackTimeout = d
|
||||
}
|
||||
if scheme := query.Get("paddingScheme"); scheme != "" {
|
||||
a.padding, err = parsePaddingScheme(scheme)
|
||||
} else {
|
||||
a.padding, err = parsePaddingScheme(defaultPaddingScheme)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] invalid padding scheme: %s", err)
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (s *AnyTLS) Addr() string {
|
||||
if s.addr == "" && s.dialer != nil {
|
||||
return s.dialer.Addr()
|
||||
}
|
||||
return s.addr
|
||||
}
|
||||
|
||||
func loadClientTLSConfig(serverName, certFile string, skipVerify bool) (*tls.Config, error) {
|
||||
conf := &tls.Config{ServerName: serverName, InsecureSkipVerify: skipVerify, MinVersion: tls.VersionTLS12}
|
||||
if certFile != "" {
|
||||
certData, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] read cert file error: %s", err)
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(certData) {
|
||||
return nil, fmt.Errorf("[anytls] can not append cert file: %s", certFile)
|
||||
}
|
||||
conf.RootCAs = certPool
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy.AddUsage("anytls", `
|
||||
AnyTLS client scheme:
|
||||
anytls://password@host:port[?serverName=SERVERNAME][&skipVerify=true][&cert=PATH][&synackTimeout=10s]
|
||||
|
||||
AnyTLS server scheme:
|
||||
anytls://password@host:port?cert=PATH&key=PATH
|
||||
`)
|
||||
}
|
||||
93
proxy/anytls/client.go
Normal file
93
proxy/anytls/client.go
Normal file
@ -0,0 +1,93 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/pkg/socks"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
proxy.RegisterDialer("anytls", NewAnyTLSDialer)
|
||||
}
|
||||
|
||||
func NewAnyTLSDialer(s string, d proxy.Dialer) (proxy.Dialer, error) {
|
||||
a, err := NewAnyTLS(s, d, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] create instance error: %s", err)
|
||||
}
|
||||
a.tlsConfig, err = loadClientTLSConfig(a.serverName, a.certFile, a.skipVerify)
|
||||
return a, err
|
||||
}
|
||||
|
||||
func (s *AnyTLS) Dial(network, addr string) (net.Conn, error) {
|
||||
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
||||
return nil, proxy.ErrNotSupported
|
||||
}
|
||||
raw := socks.ParseAddr(addr)
|
||||
if raw == nil {
|
||||
return nil, fmt.Errorf("[anytls] invalid target address: %s", addr)
|
||||
}
|
||||
ss, err := s.newClientSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := ss.openStream()
|
||||
if err != nil {
|
||||
_ = ss.Close()
|
||||
return nil, err
|
||||
}
|
||||
if _, err := st.Write(raw); err != nil {
|
||||
_ = st.Close()
|
||||
_ = ss.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := ss.waitSYNACK(st.id, s.synackTimeout); err != nil {
|
||||
_ = st.Close()
|
||||
_ = ss.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &clientConn{Conn: st, session: ss}, nil
|
||||
}
|
||||
|
||||
func (s *AnyTLS) DialUDP(network, addr string) (net.PacketConn, error) {
|
||||
return nil, proxy.ErrNotSupported
|
||||
}
|
||||
|
||||
func (s *AnyTLS) newClientSession() (*session, error) {
|
||||
rc, err := s.dialer.Dial("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.F("[anytls] dial to %s error: %s", s.addr, err)
|
||||
return nil, err
|
||||
}
|
||||
tc := tls.Client(rc, s.tlsConfig)
|
||||
if err := tc.Handshake(); err != nil {
|
||||
_ = rc.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := writeAuth(tc, s.password, s.padding.authPaddingLen()); err != nil {
|
||||
_ = tc.Close()
|
||||
return nil, err
|
||||
}
|
||||
ss := newSession(tc)
|
||||
if err := ss.writeFrame(frame{command: cmdSettings, data: clientSettings(s.padding)}); err != nil {
|
||||
_ = tc.Close()
|
||||
return nil, err
|
||||
}
|
||||
ss.start()
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
type clientConn struct {
|
||||
net.Conn
|
||||
session *session
|
||||
}
|
||||
|
||||
func (c *clientConn) Close() error {
|
||||
err := c.Conn.Close()
|
||||
_ = c.session.Close()
|
||||
return err
|
||||
}
|
||||
70
proxy/anytls/padding.go
Normal file
70
proxy/anytls/padding.go
Normal file
@ -0,0 +1,70 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const defaultPaddingScheme = "stop=8\n0=30-30\n1=100-400\n2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000\n3=9-9,500-1000\n4=500-1000\n5=500-1000\n6=500-1000\n7=500-1000"
|
||||
|
||||
type paddingScheme struct {
|
||||
raw string
|
||||
authRange [2]int
|
||||
}
|
||||
|
||||
func parsePaddingScheme(raw string) (paddingScheme, error) {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
raw = defaultPaddingScheme
|
||||
}
|
||||
ps := paddingScheme{raw: raw, authRange: [2]int{0, 0}}
|
||||
for _, line := range strings.Split(raw, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
key, value, ok := strings.Cut(line, "=")
|
||||
if !ok {
|
||||
return ps, fmt.Errorf("invalid padding scheme line %q", line)
|
||||
}
|
||||
if key == "0" {
|
||||
r, err := parseRange(value)
|
||||
if err != nil {
|
||||
return ps, err
|
||||
}
|
||||
ps.authRange = r
|
||||
}
|
||||
}
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func parseRange(s string) ([2]int, error) {
|
||||
part := strings.SplitN(s, ",", 2)[0]
|
||||
lo, hi, ok := strings.Cut(part, "-")
|
||||
if !ok {
|
||||
return [2]int{}, fmt.Errorf("invalid range %q", s)
|
||||
}
|
||||
min, err := strconv.Atoi(lo)
|
||||
if err != nil {
|
||||
return [2]int{}, err
|
||||
}
|
||||
max, err := strconv.Atoi(hi)
|
||||
if err != nil {
|
||||
return [2]int{}, err
|
||||
}
|
||||
if min < 0 || max < min || max > 65535 {
|
||||
return [2]int{}, fmt.Errorf("invalid range %q", s)
|
||||
}
|
||||
return [2]int{min, max}, nil
|
||||
}
|
||||
|
||||
func (p paddingScheme) authPaddingLen() int {
|
||||
return p.authRange[0]
|
||||
}
|
||||
|
||||
func (p paddingScheme) md5() string {
|
||||
sum := md5.Sum([]byte(p.raw))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
97
proxy/anytls/protocol.go
Normal file
97
proxy/anytls/protocol.go
Normal file
@ -0,0 +1,97 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
cmdWaste byte = iota
|
||||
cmdSYN
|
||||
cmdPSH
|
||||
cmdFIN
|
||||
cmdSettings
|
||||
cmdAlert
|
||||
cmdUpdatePaddingScheme
|
||||
cmdSYNACK
|
||||
cmdHeartRequest
|
||||
cmdHeartResponse
|
||||
cmdServerSettings
|
||||
)
|
||||
|
||||
const (
|
||||
protocolVersion = 2
|
||||
maxFrameData = 65535
|
||||
)
|
||||
|
||||
type frame struct {
|
||||
command byte
|
||||
streamID uint32
|
||||
data []byte
|
||||
}
|
||||
|
||||
func passwordHash(password string) [32]byte {
|
||||
return sha256.Sum256([]byte(password))
|
||||
}
|
||||
|
||||
func readAuth(r io.Reader, password string) error {
|
||||
var head [34]byte
|
||||
if _, err := io.ReadFull(r, head[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
want := passwordHash(password)
|
||||
if subtle.ConstantTimeCompare(head[:32], want[:]) != 1 {
|
||||
return errors.New("authentication failed")
|
||||
}
|
||||
paddingLen := binary.BigEndian.Uint16(head[32:34])
|
||||
if paddingLen > 0 {
|
||||
_, err := io.CopyN(io.Discard, r, int64(paddingLen))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeAuth(w io.Writer, password string, paddingLen int) error {
|
||||
if paddingLen < 0 || paddingLen > 65535 {
|
||||
return fmt.Errorf("invalid auth padding length %d", paddingLen)
|
||||
}
|
||||
hash := passwordHash(password)
|
||||
buf := make([]byte, 34+paddingLen)
|
||||
copy(buf, hash[:])
|
||||
binary.BigEndian.PutUint16(buf[32:34], uint16(paddingLen))
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func readFrame(r io.Reader) (frame, error) {
|
||||
var head [7]byte
|
||||
if _, err := io.ReadFull(r, head[:]); err != nil {
|
||||
return frame{}, err
|
||||
}
|
||||
n := binary.BigEndian.Uint16(head[5:7])
|
||||
var data []byte
|
||||
if n > 0 {
|
||||
data = make([]byte, n)
|
||||
if _, err := io.ReadFull(r, data); err != nil {
|
||||
return frame{}, err
|
||||
}
|
||||
}
|
||||
return frame{command: head[0], streamID: binary.BigEndian.Uint32(head[1:5]), data: data}, nil
|
||||
}
|
||||
|
||||
func writeFrame(w io.Writer, f frame) error {
|
||||
if len(f.data) > maxFrameData {
|
||||
return errors.New("frame data too large")
|
||||
}
|
||||
buf := make([]byte, 7+len(f.data))
|
||||
buf[0] = f.command
|
||||
binary.BigEndian.PutUint32(buf[1:5], f.streamID)
|
||||
binary.BigEndian.PutUint16(buf[5:7], uint16(len(f.data)))
|
||||
copy(buf[7:], f.data)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
105
proxy/anytls/server.go
Normal file
105
proxy/anytls/server.go
Normal file
@ -0,0 +1,105 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/nadoo/glider/pkg/log"
|
||||
"github.com/nadoo/glider/pkg/socks"
|
||||
"github.com/nadoo/glider/proxy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
proxy.RegisterServer("anytls", NewAnyTLSServer)
|
||||
}
|
||||
|
||||
func NewAnyTLSServer(s string, p proxy.Proxy) (proxy.Server, error) {
|
||||
a, err := NewAnyTLS(s, nil, p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] create instance error: %s", err)
|
||||
}
|
||||
if a.certFile == "" || a.keyFile == "" {
|
||||
return nil, errors.New("[anytls] cert and key file path must be specified")
|
||||
}
|
||||
cert, err := tls.LoadX509KeyPair(a.certFile, a.keyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[anytls] unable to load cert: %s, key %s, error: %s", a.certFile, a.keyFile, err)
|
||||
}
|
||||
a.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (s *AnyTLS) ListenAndServe() {
|
||||
l, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
log.Fatalf("[anytls] failed to listen on %s: %v", s.addr, err)
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
log.F("[anytls] listening TCP on %s", s.addr)
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.F("[anytls] failed to accept: %v", err)
|
||||
continue
|
||||
}
|
||||
go s.Serve(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AnyTLS) Serve(c net.Conn) {
|
||||
tlsConn := tls.Server(c, s.tlsConfig)
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
_ = tlsConn.Close()
|
||||
log.F("[anytls] error in tls handshake: %s", err)
|
||||
return
|
||||
}
|
||||
if err := readAuth(tlsConn, s.password); err != nil {
|
||||
_ = tlsConn.Close()
|
||||
log.F("[anytls] auth error from %s: %s", c.RemoteAddr(), err)
|
||||
return
|
||||
}
|
||||
|
||||
ss := newSession(tlsConn)
|
||||
ss.start()
|
||||
for {
|
||||
st, err := ss.acceptStream()
|
||||
if err != nil {
|
||||
_ = ss.Close()
|
||||
return
|
||||
}
|
||||
go s.serveStream(ss, st)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AnyTLS) serveStream(ss *session, st *stream) {
|
||||
defer st.Close()
|
||||
|
||||
target, err := socks.ReadAddr(st)
|
||||
if err != nil {
|
||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id, data: []byte(err.Error())})
|
||||
log.F("[anytls] read target error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
rc, dialer, err := s.proxy.Dial("tcp", target.String())
|
||||
if err != nil {
|
||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id, data: []byte(err.Error())})
|
||||
log.F("[anytls] %s <-> %s via %s, error in dial: %v", st.RemoteAddr(), target, dialer.Addr(), err)
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id})
|
||||
log.F("[anytls] %s <-> %s via %s", st.RemoteAddr(), target, dialer.Addr())
|
||||
if err = proxy.Relay(st, rc); err != nil {
|
||||
log.F("[anytls] %s <-> %s via %s, relay error: %v", st.RemoteAddr(), target, dialer.Addr(), err)
|
||||
if !strings.Contains(err.Error(), s.addr) {
|
||||
s.proxy.Record(dialer, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
233
proxy/anytls/session.go
Normal file
233
proxy/anytls/session.go
Normal file
@ -0,0 +1,233 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type session struct {
|
||||
conn net.Conn
|
||||
|
||||
writeMu sync.Mutex
|
||||
mu sync.Mutex
|
||||
streams map[uint32]*stream
|
||||
synack map[uint32]chan synackResult
|
||||
nextID uint32
|
||||
|
||||
incoming chan *stream
|
||||
done chan struct{}
|
||||
closeOnce sync.Once
|
||||
err atomic.Value
|
||||
|
||||
settingsSeen bool
|
||||
}
|
||||
|
||||
type synackResult struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func newSession(conn net.Conn) *session {
|
||||
return &session{
|
||||
conn: conn,
|
||||
streams: map[uint32]*stream{},
|
||||
synack: map[uint32]chan synackResult{},
|
||||
nextID: 1,
|
||||
incoming: make(chan *stream, 32),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) start() {
|
||||
go s.readLoop()
|
||||
}
|
||||
|
||||
func (s *session) acceptStream() (*stream, error) {
|
||||
select {
|
||||
case st, ok := <-s.incoming:
|
||||
if !ok {
|
||||
return nil, s.Err()
|
||||
}
|
||||
return st, nil
|
||||
case <-s.done:
|
||||
return nil, s.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) openStream() (*stream, error) {
|
||||
id := atomic.AddUint32(&s.nextID, 1) - 1
|
||||
st := newStream(id, s)
|
||||
s.mu.Lock()
|
||||
s.streams[id] = st
|
||||
s.synack[id] = make(chan synackResult, 1)
|
||||
s.mu.Unlock()
|
||||
if err := s.writeFrame(frame{command: cmdSYN, streamID: id}); err != nil {
|
||||
s.removeStream(id)
|
||||
return nil, err
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func (s *session) waitSYNACK(id uint32, timeout time.Duration) error {
|
||||
s.mu.Lock()
|
||||
ch := s.synack[id]
|
||||
s.mu.Unlock()
|
||||
if ch == nil {
|
||||
return nil
|
||||
}
|
||||
var timer <-chan time.Time
|
||||
if timeout > 0 {
|
||||
t := time.NewTimer(timeout)
|
||||
defer t.Stop()
|
||||
timer = t.C
|
||||
}
|
||||
select {
|
||||
case r, ok := <-ch:
|
||||
if !ok {
|
||||
return s.Err()
|
||||
}
|
||||
if len(r.data) > 0 {
|
||||
return fmt.Errorf("stream open failed: %s", string(r.data))
|
||||
}
|
||||
return nil
|
||||
case <-timer:
|
||||
return errors.New("timeout waiting for SYNACK")
|
||||
case <-s.done:
|
||||
return s.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) writeFrame(f frame) error {
|
||||
s.writeMu.Lock()
|
||||
defer s.writeMu.Unlock()
|
||||
return writeFrame(s.conn, f)
|
||||
}
|
||||
|
||||
func (s *session) readLoop() {
|
||||
for {
|
||||
f, err := readFrame(s.conn)
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
s.setErr(err)
|
||||
}
|
||||
s.Close()
|
||||
return
|
||||
}
|
||||
if err := s.handleFrame(f); err != nil {
|
||||
s.setErr(err)
|
||||
s.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) handleFrame(f frame) error {
|
||||
switch f.command {
|
||||
case cmdWaste:
|
||||
return nil
|
||||
case cmdHeartRequest:
|
||||
return s.writeFrame(frame{command: cmdHeartResponse, streamID: f.streamID})
|
||||
case cmdHeartResponse:
|
||||
return nil
|
||||
case cmdSettings:
|
||||
m := parseSettings(f.data)
|
||||
s.settingsSeen = true
|
||||
if settingsVersion(m) >= 2 {
|
||||
return s.writeFrame(frame{command: cmdServerSettings, data: serverSettings()})
|
||||
}
|
||||
case cmdServerSettings:
|
||||
return nil
|
||||
case cmdAlert:
|
||||
return errors.New("alert: " + string(f.data))
|
||||
case cmdUpdatePaddingScheme:
|
||||
return nil
|
||||
case cmdSYN:
|
||||
if !s.settingsSeen {
|
||||
_ = s.writeFrame(frame{command: cmdAlert, data: []byte("cmdSYN received before cmdSettings")})
|
||||
return errors.New("cmdSYN received before cmdSettings")
|
||||
}
|
||||
st := newStream(f.streamID, s)
|
||||
s.mu.Lock()
|
||||
s.streams[f.streamID] = st
|
||||
s.mu.Unlock()
|
||||
select {
|
||||
case s.incoming <- st:
|
||||
case <-s.done:
|
||||
}
|
||||
case cmdSYNACK:
|
||||
s.mu.Lock()
|
||||
ch := s.synack[f.streamID]
|
||||
delete(s.synack, f.streamID)
|
||||
s.mu.Unlock()
|
||||
if ch != nil {
|
||||
ch <- synackResult{data: f.data}
|
||||
close(ch)
|
||||
}
|
||||
case cmdPSH:
|
||||
st := s.getStream(f.streamID)
|
||||
if st != nil {
|
||||
st.push(f.data)
|
||||
}
|
||||
case cmdFIN:
|
||||
st := s.getStream(f.streamID)
|
||||
if st != nil {
|
||||
st.closeRead()
|
||||
s.removeStream(f.streamID)
|
||||
}
|
||||
default:
|
||||
return errors.New("unknown command")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *session) getStream(id uint32) *stream {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.streams[id]
|
||||
}
|
||||
|
||||
func (s *session) removeStream(id uint32) {
|
||||
s.mu.Lock()
|
||||
delete(s.streams, id)
|
||||
if ch := s.synack[id]; ch != nil {
|
||||
delete(s.synack, id)
|
||||
close(ch)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *session) setErr(err error) {
|
||||
if err != nil && s.err.Load() == nil {
|
||||
s.err.Store(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) Err() error {
|
||||
if v := s.err.Load(); v != nil {
|
||||
return v.(error)
|
||||
}
|
||||
return net.ErrClosed
|
||||
}
|
||||
|
||||
func (s *session) Close() error {
|
||||
s.closeOnce.Do(func() {
|
||||
close(s.done)
|
||||
_ = s.conn.Close()
|
||||
s.mu.Lock()
|
||||
for _, st := range s.streams {
|
||||
st.closeRead()
|
||||
}
|
||||
s.streams = map[uint32]*stream{}
|
||||
for id, ch := range s.synack {
|
||||
delete(s.synack, id)
|
||||
close(ch)
|
||||
}
|
||||
close(s.incoming)
|
||||
s.mu.Unlock()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
59
proxy/anytls/settings.go
Normal file
59
proxy/anytls/settings.go
Normal file
@ -0,0 +1,59 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func encodeSettings(m map[string]string) []byte {
|
||||
keys := []string{"v", "client", "padding-md5"}
|
||||
lines := make([]string, 0, len(m))
|
||||
seen := map[string]bool{}
|
||||
for _, k := range keys {
|
||||
if v, ok := m[k]; ok {
|
||||
lines = append(lines, k+"="+v)
|
||||
seen[k] = true
|
||||
}
|
||||
}
|
||||
for k, v := range m {
|
||||
if !seen[k] {
|
||||
lines = append(lines, k+"="+v)
|
||||
}
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func parseSettings(data []byte) map[string]string {
|
||||
out := map[string]string{}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
k, v, ok := strings.Cut(line, "=")
|
||||
if ok {
|
||||
out[k] = v
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func settingsVersion(m map[string]string) int {
|
||||
v, err := strconv.Atoi(m["v"])
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func clientSettings(ps paddingScheme) []byte {
|
||||
return encodeSettings(map[string]string{
|
||||
"v": fmt.Sprint(protocolVersion),
|
||||
"client": "glider-anytls",
|
||||
"padding-md5": ps.md5(),
|
||||
})
|
||||
}
|
||||
|
||||
func serverSettings() []byte {
|
||||
return encodeSettings(map[string]string{"v": fmt.Sprint(protocolVersion)})
|
||||
}
|
||||
96
proxy/anytls/stream.go
Normal file
96
proxy/anytls/stream.go
Normal file
@ -0,0 +1,96 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errStreamClosed = errors.New("stream closed")
|
||||
|
||||
type stream struct {
|
||||
id uint32
|
||||
s *session
|
||||
|
||||
in chan []byte
|
||||
readBuf []byte
|
||||
closeIn sync.Once
|
||||
closeOut sync.Once
|
||||
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
func newStream(id uint32, s *session) *stream {
|
||||
return &stream{id: id, s: s, in: make(chan []byte, 32)}
|
||||
}
|
||||
|
||||
func (st *stream) Read(p []byte) (int, error) {
|
||||
for len(st.readBuf) == 0 {
|
||||
b, ok := <-st.in
|
||||
if !ok {
|
||||
return 0, io.EOF
|
||||
}
|
||||
st.readBuf = b
|
||||
}
|
||||
n := copy(p, st.readBuf)
|
||||
st.readBuf = st.readBuf[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (st *stream) Write(p []byte) (int, error) {
|
||||
st.mu.Lock()
|
||||
closed := st.closed
|
||||
st.mu.Unlock()
|
||||
if closed {
|
||||
return 0, errStreamClosed
|
||||
}
|
||||
written := 0
|
||||
for len(p) > 0 {
|
||||
n := len(p)
|
||||
if n > maxFrameData {
|
||||
n = maxFrameData
|
||||
}
|
||||
if err := st.s.writeFrame(frame{command: cmdPSH, streamID: st.id, data: p[:n]}); err != nil {
|
||||
return written, err
|
||||
}
|
||||
written += n
|
||||
p = p[n:]
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
func (st *stream) Close() error {
|
||||
st.mu.Lock()
|
||||
already := st.closed
|
||||
st.closed = true
|
||||
st.mu.Unlock()
|
||||
if !already {
|
||||
st.closeOut.Do(func() {
|
||||
_ = st.s.writeFrame(frame{command: cmdFIN, streamID: st.id})
|
||||
})
|
||||
st.s.removeStream(st.id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *stream) closeRead() {
|
||||
st.closeIn.Do(func() { close(st.in) })
|
||||
}
|
||||
|
||||
func (st *stream) push(data []byte) {
|
||||
cp := make([]byte, len(data))
|
||||
copy(cp, data)
|
||||
select {
|
||||
case st.in <- cp:
|
||||
case <-st.s.done:
|
||||
}
|
||||
}
|
||||
|
||||
func (st *stream) LocalAddr() net.Addr { return st.s.conn.LocalAddr() }
|
||||
func (st *stream) RemoteAddr() net.Addr { return st.s.conn.RemoteAddr() }
|
||||
func (st *stream) SetDeadline(t time.Time) error { return st.s.conn.SetDeadline(t) }
|
||||
func (st *stream) SetReadDeadline(t time.Time) error { return st.s.conn.SetReadDeadline(t) }
|
||||
func (st *stream) SetWriteDeadline(t time.Time) error { return st.s.conn.SetWriteDeadline(t) }
|
||||
Loading…
Reference in New Issue
Block a user