Mult Port + TCP Calibration (SSH DEAD)
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"dragontcp/internal/protocol"
|
||||
)
|
||||
|
||||
const (
|
||||
socksVersion5 = 5
|
||||
socksCmdConnect = 1
|
||||
socksCmdUDPAssociate = 3
|
||||
socksAtypIPv4 = 1
|
||||
socksAtypDomain = 3
|
||||
socksAtypIPv6 = 4
|
||||
)
|
||||
|
||||
func startSOCKS5Proxy(listenAddr string, manager *sshTunnelManager, maxConnections int, tcpBuffer int) (net.Listener, error) {
|
||||
ln, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if maxConnections < 1 {
|
||||
maxConnections = 1
|
||||
}
|
||||
slots := make(chan struct{}, maxConnections)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case slots <- struct{}{}:
|
||||
go func(c net.Conn) {
|
||||
defer func() { <-slots; _ = c.Close() }()
|
||||
protocol.TuneTCP(c)
|
||||
protocol.TuneTCPBuffer(c, tcpBuffer)
|
||||
_ = handleSOCKS5(c, manager)
|
||||
}(conn)
|
||||
default:
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func handleSOCKS5(conn net.Conn, manager *sshTunnelManager) error {
|
||||
br := bufio.NewReaderSize(conn, 4096)
|
||||
if err := socksNegotiate(br, conn); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd, host, port, err := socksReadRequest(br)
|
||||
if err != nil {
|
||||
_ = socksReply(conn, 1, nil)
|
||||
return err
|
||||
}
|
||||
switch cmd {
|
||||
case socksCmdConnect:
|
||||
remote, err := manager.DialTCP(host, port)
|
||||
if err != nil {
|
||||
_ = socksReply(conn, 5, nil)
|
||||
return err
|
||||
}
|
||||
defer remote.Close()
|
||||
if err := socksReply(conn, 0, remote.LocalAddr()); err != nil {
|
||||
return err
|
||||
}
|
||||
protocol.RelayRaw(conn, remote)
|
||||
return nil
|
||||
case socksCmdUDPAssociate:
|
||||
return handleSOCKSUDPAssociate(conn, br, manager)
|
||||
default:
|
||||
_ = socksReply(conn, 7, nil)
|
||||
return fmt.Errorf("SOCKS command %d unsupported", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func socksNegotiate(br *bufio.Reader, w io.Writer) error {
|
||||
header := make([]byte, 2)
|
||||
if _, err := io.ReadFull(br, header); err != nil {
|
||||
return err
|
||||
}
|
||||
if header[0] != socksVersion5 || header[1] == 0 {
|
||||
return errors.New("invalid SOCKS5 greeting")
|
||||
}
|
||||
methods := make([]byte, int(header[1]))
|
||||
if _, err := io.ReadFull(br, methods); err != nil {
|
||||
return err
|
||||
}
|
||||
noAuth := false
|
||||
for _, method := range methods {
|
||||
if method == 0 {
|
||||
noAuth = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !noAuth {
|
||||
_, _ = w.Write([]byte{5, 0xff})
|
||||
return errors.New("SOCKS5 client does not support no-auth")
|
||||
}
|
||||
_, err := w.Write([]byte{5, 0})
|
||||
return err
|
||||
}
|
||||
|
||||
func socksReadRequest(br *bufio.Reader) (cmd byte, host string, port int, err error) {
|
||||
header := make([]byte, 4)
|
||||
if _, err = io.ReadFull(br, header); err != nil {
|
||||
return
|
||||
}
|
||||
if header[0] != 5 || header[2] != 0 {
|
||||
err = errors.New("invalid SOCKS5 request")
|
||||
return
|
||||
}
|
||||
cmd = header[1]
|
||||
host, err = socksReadHost(br, header[3])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var portBuf [2]byte
|
||||
if _, err = io.ReadFull(br, portBuf[:]); err != nil {
|
||||
return
|
||||
}
|
||||
port = int(binary.BigEndian.Uint16(portBuf[:]))
|
||||
// CONNECT requires a real destination port. UDP ASSOCIATE commonly uses
|
||||
// 0.0.0.0:0 to ask the proxy to choose the relay endpoint, which is exactly
|
||||
// what the Android VPN adapter sends.
|
||||
if cmd == socksCmdConnect && port < 1 {
|
||||
err = errors.New("invalid SOCKS5 port")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func socksReadHost(r io.Reader, atyp byte) (string, error) {
|
||||
switch atyp {
|
||||
case socksAtypIPv4:
|
||||
b := make([]byte, 4)
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return net.IP(b).String(), nil
|
||||
case socksAtypIPv6:
|
||||
b := make([]byte, 16)
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return net.IP(b).String(), nil
|
||||
case socksAtypDomain:
|
||||
var n [1]byte
|
||||
if _, err := io.ReadFull(r, n[:]); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if n[0] == 0 {
|
||||
return "", errors.New("empty SOCKS domain")
|
||||
}
|
||||
b := make([]byte, int(n[0]))
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported SOCKS address type %d", atyp)
|
||||
}
|
||||
}
|
||||
|
||||
func socksReply(w io.Writer, rep byte, addr net.Addr) error {
|
||||
ip := net.IPv4zero
|
||||
port := 0
|
||||
if tcpAddr, ok := addr.(*net.TCPAddr); ok {
|
||||
if v4 := tcpAddr.IP.To4(); v4 != nil {
|
||||
ip = v4
|
||||
}
|
||||
port = tcpAddr.Port
|
||||
} else if udpAddr, ok := addr.(*net.UDPAddr); ok {
|
||||
if v4 := udpAddr.IP.To4(); v4 != nil {
|
||||
ip = v4
|
||||
}
|
||||
port = udpAddr.Port
|
||||
}
|
||||
out := []byte{5, rep, 0, socksAtypIPv4, 0, 0, 0, 0, 0, 0}
|
||||
copy(out[4:8], ip.To4())
|
||||
binary.BigEndian.PutUint16(out[8:10], uint16(port))
|
||||
_, err := w.Write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func handleSOCKSUDPAssociate(control net.Conn, br *bufio.Reader, manager *sshTunnelManager) error {
|
||||
udp, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer udp.Close()
|
||||
if err := socksReply(control, 0, udp.LocalAddr()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gw, err := manager.DialUDPGW()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gw.Close()
|
||||
_ = gw.SetDeadline(time.Time{})
|
||||
|
||||
done := make(chan struct{})
|
||||
var closeOnce sync.Once
|
||||
closeAll := func() { closeOnce.Do(func() { close(done); _ = udp.Close(); _ = gw.Close() }) }
|
||||
defer closeAll()
|
||||
|
||||
var clientMu sync.RWMutex
|
||||
var clientAddr *net.UDPAddr
|
||||
writeMu := sync.Mutex{}
|
||||
|
||||
go func() {
|
||||
defer closeAll()
|
||||
reader := bufio.NewReaderSize(gw, 32*1024)
|
||||
for {
|
||||
payload, err := readUDPGWFrame(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(payload) < 9 {
|
||||
continue
|
||||
}
|
||||
srcIP := payload[3:7]
|
||||
srcPort := binary.BigEndian.Uint16(payload[7:9])
|
||||
data := payload[9:]
|
||||
packet := make([]byte, 10+len(data))
|
||||
packet[0], packet[1], packet[2], packet[3] = 0, 0, 0, socksAtypIPv4
|
||||
copy(packet[4:8], srcIP)
|
||||
binary.BigEndian.PutUint16(packet[8:10], srcPort)
|
||||
copy(packet[10:], data)
|
||||
clientMu.RLock()
|
||||
to := clientAddr
|
||||
clientMu.RUnlock()
|
||||
if to != nil {
|
||||
_, _ = udp.WriteToUDP(packet, to)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer closeAll()
|
||||
// The UDP association lifetime is the TCP control connection lifetime.
|
||||
buf := make([]byte, 1)
|
||||
for {
|
||||
if _, err := br.Read(buf); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
buf := make([]byte, 65535)
|
||||
for {
|
||||
n, from, err := udp.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
clientMu.Lock()
|
||||
clientAddr = from
|
||||
clientMu.Unlock()
|
||||
ip, port, payload, err := parseSOCKSUDPDatagram(buf[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
frame := buildUDPGWRequest(1, 0, ip, uint16(port), payload)
|
||||
writeMu.Lock()
|
||||
_, err = gw.Write(frame)
|
||||
writeMu.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseSOCKSUDPDatagram(packet []byte) ([4]byte, int, []byte, error) {
|
||||
var out [4]byte
|
||||
if len(packet) < 10 || packet[0] != 0 || packet[1] != 0 || packet[2] != 0 {
|
||||
return out, 0, nil, errors.New("invalid SOCKS5 UDP packet")
|
||||
}
|
||||
pos := 3
|
||||
atyp := packet[pos]
|
||||
pos++
|
||||
switch atyp {
|
||||
case socksAtypIPv4:
|
||||
if len(packet) < pos+4+2 {
|
||||
return out, 0, nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
copy(out[:], packet[pos:pos+4])
|
||||
pos += 4
|
||||
case socksAtypDomain:
|
||||
if len(packet) <= pos {
|
||||
return out, 0, nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
n := int(packet[pos])
|
||||
pos++
|
||||
if len(packet) < pos+n+2 {
|
||||
return out, 0, nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
return out, 0, nil, errors.New("SOCKS UDP domain destinations are disabled to avoid local DNS leakage; use an IPv4 destination")
|
||||
case socksAtypIPv6:
|
||||
return out, 0, nil, errors.New("UDPGW supports IPv4 only")
|
||||
default:
|
||||
return out, 0, nil, errors.New("unsupported SOCKS UDP address type")
|
||||
}
|
||||
port := int(binary.BigEndian.Uint16(packet[pos : pos+2]))
|
||||
pos += 2
|
||||
return out, port, packet[pos:], nil
|
||||
}
|
||||
|
||||
func buildUDPGWRequest(connID uint16, x byte, ip [4]byte, port uint16, data []byte) []byte {
|
||||
payloadLen := 9 + len(data)
|
||||
frame := make([]byte, 2+payloadLen)
|
||||
binary.LittleEndian.PutUint16(frame[0:2], uint16(payloadLen))
|
||||
binary.BigEndian.PutUint16(frame[2:4], connID)
|
||||
frame[4] = x
|
||||
copy(frame[5:9], ip[:])
|
||||
binary.BigEndian.PutUint16(frame[9:11], port)
|
||||
copy(frame[11:], data)
|
||||
return frame
|
||||
}
|
||||
|
||||
func readUDPGWFrame(r *bufio.Reader) ([]byte, error) {
|
||||
var lenBuf [2]byte
|
||||
if _, err := io.ReadFull(r, lenBuf[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n := int(binary.LittleEndian.Uint16(lenBuf[:]))
|
||||
if n < 9 || n > 65535 {
|
||||
return nil, fmt.Errorf("invalid UDPGW frame %d", n)
|
||||
}
|
||||
payload := make([]byte, n)
|
||||
if _, err := io.ReadFull(r, payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
Reference in New Issue
Block a user