anytls: support udp

This commit is contained in:
nadoo 2026-06-25 22:10:53 +08:00
parent 533571f1ec
commit 86f9f078fd
4 changed files with 182 additions and 6 deletions

View File

@ -52,11 +52,11 @@ we can set up local listeners as proxy servers, and forward requests to internet
|HTTP |√| |√| |client & server |HTTP |√| |√| |client & server
|SOCKS5 |√|√|√|√|client & server |SOCKS5 |√|√|√|√|client & server
|SS |√|√|√|√|client & server |SS |√|√|√|√|client & server
|Trojan |√||√|√|client & server |Trojan |√| |√|√|client & server
|Trojanc |√||√|√|trojan cleartext(without tls) |Trojanc |√| |√|√|trojan cleartext(without tls)
|AnyTLS |√| |√| |client & server |AnyTLS |√| |√||client & server
|AnyTLSc |√| |√| |anytls cleartext(without tls) |AnyTLSc |√| |√||anytls cleartext(without tls)
|VLESS |√||√|√|client & server |VLESS |√| |√|√|client & server
|VMess | | |√|√|client only |VMess | | |√|√|client only
|SSR | | |√| |client only |SSR | | |√| |client only
|SSH | | |√| |client only |SSH | | |√| |client only

View File

@ -64,8 +64,43 @@ func (s *AnyTLS) Dial(network, addr string) (net.Conn, error) {
} }
func (s *AnyTLS) DialUDP(network, addr string) (net.PacketConn, error) { func (s *AnyTLS) DialUDP(network, addr string) (net.PacketConn, error) {
if network != "udp" && network != "udp4" && network != "udp6" {
return nil, proxy.ErrNotSupported return nil, proxy.ErrNotSupported
} }
target := socks.ParseAddr(addr)
if target == nil {
return nil, fmt.Errorf("[anytls] invalid target address: %s", addr)
}
raw := socks.ParseAddr(net.JoinHostPort(uotV2MagicHost, "0"))
if raw == nil {
return nil, fmt.Errorf("[anytls] invalid udp-over-tcp target address")
}
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 := writeUOTV2Request(st, target); 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 &clientPacketConn{PacketConn: newUOTPacketConn(st, target), session: ss}, nil
}
func (s *AnyTLS) newClientSession() (*session, error) { func (s *AnyTLS) newClientSession() (*session, error) {
rc, err := s.dialer.Dial("tcp", s.addr) rc, err := s.dialer.Dial("tcp", s.addr)
@ -105,3 +140,14 @@ func (c *clientConn) Close() error {
_ = c.session.Close() _ = c.session.Close()
return err return err
} }
type clientPacketConn struct {
net.PacketConn
session *session
}
func (c *clientPacketConn) Close() error {
err := c.PacketConn.Close()
_ = c.session.Close()
return err
}

95
proxy/anytls/packet.go Normal file
View File

@ -0,0 +1,95 @@
package anytls
import (
"encoding/binary"
"errors"
"io"
"net"
"time"
"github.com/nadoo/glider/pkg/pool"
"github.com/nadoo/glider/pkg/socks"
)
const uotV2MagicHost = "sp.v2.udp-over-tcp.arpa"
// uotPacketConn carries UDP packets over an AnyTLS stream using sing-box
// udp-over-tcp v2 connect format.
type uotPacketConn struct {
net.Conn
target socks.Addr
}
func newUOTPacketConn(c net.Conn, target socks.Addr) *uotPacketConn {
return &uotPacketConn{Conn: c, target: target}
}
func (pc *uotPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
if len(b) < 2 {
return 0, pc.target, errors.New("buf size is not enough")
}
if _, err := io.ReadFull(pc.Conn, b[:2]); err != nil {
return 0, pc.target, err
}
length := int(binary.BigEndian.Uint16(b[:2]))
if len(b) < length {
return 0, pc.target, errors.New("buf size is not enough")
}
n, err := io.ReadFull(pc.Conn, b[:length])
return n, pc.target, err
}
func (pc *uotPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
buf := pool.GetBytesBuffer()
defer pool.PutBytesBuffer(buf)
var head [2]byte
binary.BigEndian.PutUint16(head[:], uint16(len(b)))
buf.Write(head[:])
buf.Write(b)
n, err := pc.Write(buf.Bytes())
if n > 2 {
return n - 2, err
}
return 0, err
}
func (pc *uotPacketConn) SetDeadline(t time.Time) error {
return pc.Conn.SetDeadline(t)
}
func (pc *uotPacketConn) SetReadDeadline(t time.Time) error {
return pc.Conn.SetReadDeadline(t)
}
func (pc *uotPacketConn) SetWriteDeadline(t time.Time) error {
return pc.Conn.SetWriteDeadline(t)
}
func writeUOTV2Request(w io.Writer, target socks.Addr) error {
if target == nil {
return errors.New("invalid target address")
}
buf := pool.GetBytesBuffer()
defer pool.PutBytesBuffer(buf)
buf.WriteByte(1) // connect stream format
buf.Write(target)
_, err := w.Write(buf.Bytes())
return err
}
func readUOTV2Request(r io.Reader) (socks.Addr, error) {
var connect [1]byte
if _, err := io.ReadFull(r, connect[:]); err != nil {
return nil, err
}
if connect[0] != 1 {
return nil, errors.New("udp-over-tcp v2 non-connect format is not supported")
}
return socks.ReadAddr(r)
}

View File

@ -8,6 +8,7 @@ import (
"io" "io"
"net" "net"
"strings" "strings"
"time"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/pkg/socks" "github.com/nadoo/glider/pkg/socks"
@ -127,6 +128,11 @@ func (s *AnyTLS) serveStream(ss *session, st *stream) {
log.F("[anytls] read target error: %v", err) log.F("[anytls] read target error: %v", err)
return return
} }
host, _, err := net.SplitHostPort(target.String())
if err == nil && host == uotV2MagicHost {
s.serveUoT(ss, st)
return
}
rc, dialer, err := s.proxy.Dial("tcp", target.String()) rc, dialer, err := s.proxy.Dial("tcp", target.String())
if err != nil { if err != nil {
@ -145,3 +151,32 @@ func (s *AnyTLS) serveStream(ss *session, st *stream) {
} }
} }
} }
func (s *AnyTLS) serveUoT(ss *session, st *stream) {
target, err := readUOTV2Request(st)
if err != nil {
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id, data: []byte(err.Error())})
log.F("[anytls] read udp-over-tcp request error: %v", err)
return
}
dstPC, dialer, err := s.proxy.DialUDP("udp", target.String())
if err != nil {
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id, data: []byte(err.Error())})
log.F("[anytls] %s <-UoT-> %s via %s, error in dial: %v", st.RemoteAddr(), target, dialer.Addr(), err)
return
}
defer dstPC.Close()
_ = ss.writeFrame(frame{command: cmdSYNACK, streamID: st.id})
pc := newUOTPacketConn(st, target)
log.F("[anytls] %s <-UoT-> %s via %s", st.RemoteAddr(), target, dialer.Addr())
go proxy.CopyUDP(dstPC, nil, pc, 2*time.Minute, 5*time.Second)
if err := proxy.CopyUDP(pc, nil, dstPC, 2*time.Minute, 5*time.Second); err != nil {
log.F("[anytls] %s <-UoT-> %s via %s, relay error: %v", st.RemoteAddr(), target, dialer.Addr(), err)
if d, ok := dialer.(proxy.Dialer); ok && !strings.Contains(err.Error(), s.addr) {
s.proxy.Record(d, false)
}
}
}