mirror of
https://github.com/xiaoqidun/probe.git
synced 2026-01-29 04:58:46 +08:00
294 lines
7.5 KiB
Go
294 lines
7.5 KiB
Go
// Copyright 2026 肖其顿 (XIAO QI DUN)
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// SocksAddr SOCKS5域名地址类型
|
|
type SocksAddr struct {
|
|
Host string
|
|
Port int
|
|
}
|
|
|
|
// Network 返回网络类型
|
|
// 返回: network 网络类型字符串
|
|
func (a *SocksAddr) Network() string { return "udp" }
|
|
|
|
// String 返回地址字符串表示
|
|
// 返回: str 地址字符串
|
|
func (a *SocksAddr) String() string { return net.JoinHostPort(a.Host, strconv.Itoa(a.Port)) }
|
|
|
|
// socks5PacketConn SOCKS5数据包连接实现
|
|
type socks5PacketConn struct {
|
|
tcpConn net.Conn
|
|
udpConn *net.UDPConn
|
|
relayAddr *net.UDPAddr
|
|
targetAddr net.Addr
|
|
}
|
|
|
|
// ReadFrom 从UDP连接读取数据
|
|
// 入参: p 读取缓冲区
|
|
// 返回: n 读取字节数, addr 来源地址, err 读取错误
|
|
func (c *socks5PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
|
buf := make([]byte, 65535)
|
|
n, _, err = c.udpConn.ReadFromUDP(buf)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if n < 10 {
|
|
return 0, nil, errors.New("packet too short")
|
|
}
|
|
atyp := buf[3]
|
|
var rAddr *net.UDPAddr
|
|
var dataOffset int
|
|
switch atyp {
|
|
case 0x01:
|
|
if n < 10 {
|
|
return 0, nil, errors.New("short packet")
|
|
}
|
|
ip := net.IP(buf[4:8])
|
|
port := binary.BigEndian.Uint16(buf[8:10])
|
|
rAddr = &net.UDPAddr{IP: ip, Port: int(port)}
|
|
dataOffset = 10
|
|
case 0x03:
|
|
dlen := int(buf[4])
|
|
if n < 5+dlen+2 {
|
|
return 0, nil, errors.New("short packet")
|
|
}
|
|
domain := string(buf[5 : 5+dlen])
|
|
port := binary.BigEndian.Uint16(buf[5+dlen : 5+dlen+2])
|
|
ipAddr, err := net.ResolveIPAddr("ip", domain)
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("failed to resolve payload domain: %v", err)
|
|
}
|
|
rAddr = &net.UDPAddr{IP: ipAddr.IP, Port: int(port)}
|
|
dataOffset = 5 + dlen + 2
|
|
case 0x04:
|
|
if n < 22 {
|
|
return 0, nil, errors.New("short packet")
|
|
}
|
|
ip := net.IP(buf[4:20])
|
|
port := binary.BigEndian.Uint16(buf[20:22])
|
|
rAddr = &net.UDPAddr{IP: ip, Port: int(port)}
|
|
dataOffset = 22
|
|
default:
|
|
return 0, nil, fmt.Errorf("unknown address type: 0x%x", atyp)
|
|
}
|
|
copy(p, buf[dataOffset:n])
|
|
return n - dataOffset, rAddr, nil
|
|
}
|
|
|
|
// WriteTo 写入数据到目标地址
|
|
// 入参: p 数据内容, addr 目标地址
|
|
// 返回: n 写入字节数, err 写入错误
|
|
func (c *socks5PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|
header := make([]byte, 0, 24)
|
|
header = append(header, 0, 0, 0)
|
|
switch a := addr.(type) {
|
|
case *net.UDPAddr:
|
|
ip4 := a.IP.To4()
|
|
if ip4 != nil {
|
|
header = append(header, 0x01)
|
|
header = append(header, ip4...)
|
|
} else {
|
|
if len(a.IP) == 16 {
|
|
header = append(header, 0x04)
|
|
header = append(header, a.IP...)
|
|
} else {
|
|
return 0, errors.New("unknown ip type")
|
|
}
|
|
}
|
|
portBuf := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(portBuf, uint16(a.Port))
|
|
header = append(header, portBuf...)
|
|
case *SocksAddr:
|
|
header = append(header, 0x03)
|
|
header = append(header, byte(len(a.Host)))
|
|
header = append(header, a.Host...)
|
|
portBuf := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(portBuf, uint16(a.Port))
|
|
header = append(header, portBuf...)
|
|
default:
|
|
return 0, errors.New("unsupported address type")
|
|
}
|
|
finalBuf := append(header, p...)
|
|
_, err = c.udpConn.WriteToUDP(finalBuf, c.relayAddr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
// Close 关闭连接
|
|
// 返回: err 关闭错误
|
|
func (c *socks5PacketConn) Close() error {
|
|
err1 := c.tcpConn.Close()
|
|
err2 := c.udpConn.Close()
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
return err2
|
|
}
|
|
|
|
// LocalAddr 获取本地地址
|
|
// 返回: addr 本地地址
|
|
func (c *socks5PacketConn) LocalAddr() net.Addr {
|
|
return c.udpConn.LocalAddr()
|
|
}
|
|
|
|
// SetDeadline 设置读写截止时间
|
|
// 入参: t 截止时间
|
|
// 返回: err 设置错误
|
|
func (c *socks5PacketConn) SetDeadline(t time.Time) error {
|
|
return c.udpConn.SetDeadline(t)
|
|
}
|
|
|
|
// SetReadDeadline 设置读取截止时间
|
|
// 入参: t 截止时间
|
|
// 返回: err 设置错误
|
|
func (c *socks5PacketConn) SetReadDeadline(t time.Time) error {
|
|
return c.udpConn.SetReadDeadline(t)
|
|
}
|
|
|
|
// SetWriteDeadline 设置写入截止时间
|
|
// 入参: t 截止时间
|
|
// 返回: err 设置错误
|
|
func (c *socks5PacketConn) SetWriteDeadline(t time.Time) error {
|
|
return c.udpConn.SetWriteDeadline(t)
|
|
}
|
|
|
|
// DialSocks5UDP 建立SOCKS5 UDP关联
|
|
// 入参: proxyAddr 代理服务器地址, network 网络协议(udp/udp4/udp6), timeout 超时时间
|
|
// 返回: conn 数据包连接, err 连接错误
|
|
func DialSocks5UDP(proxyAddr, network string, timeout time.Duration) (net.PacketConn, error) {
|
|
var host string
|
|
if strings.Contains(proxyAddr, "://") {
|
|
u, err := url.Parse(proxyAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
host = u.Host
|
|
} else {
|
|
host = proxyAddr
|
|
}
|
|
tcpNetwork := "tcp"
|
|
switch network {
|
|
case "udp4":
|
|
tcpNetwork = "tcp4"
|
|
case "udp6":
|
|
tcpNetwork = "tcp6"
|
|
}
|
|
conn, err := net.DialTimeout(tcpNetwork, host, timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
conn.SetDeadline(time.Now().Add(timeout))
|
|
_, err = conn.Write([]byte{0x05, 0x01, 0x00})
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
buf := make([]byte, 2)
|
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
if buf[0] != 0x05 || buf[1] != 0x00 {
|
|
conn.Close()
|
|
return nil, errors.New("socks5 handshake failed")
|
|
}
|
|
req := []byte{0x05, 0x03, 0x00, 0x01, 0, 0, 0, 0, 0, 0}
|
|
if _, err := conn.Write(req); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
header := make([]byte, 4)
|
|
if _, err := io.ReadFull(conn, header); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
if header[1] != 0x00 {
|
|
conn.Close()
|
|
return nil, fmt.Errorf("socks5 udp associate failed: 0x%x", header[1])
|
|
}
|
|
var relayIP net.IP
|
|
var relayPort int
|
|
switch header[3] {
|
|
case 0x01:
|
|
b := make([]byte, 4)
|
|
if _, err := io.ReadFull(conn, b); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
relayIP = net.IP(b)
|
|
case 0x03:
|
|
lenBuf := make([]byte, 1)
|
|
if _, err := io.ReadFull(conn, lenBuf); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
domainBuf := make([]byte, int(lenBuf[0]))
|
|
if _, err := io.ReadFull(conn, domainBuf); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
addr, err := net.ResolveIPAddr("ip", string(domainBuf))
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
relayIP = addr.IP
|
|
case 0x04:
|
|
b := make([]byte, 16)
|
|
if _, err := io.ReadFull(conn, b); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
relayIP = net.IP(b)
|
|
default:
|
|
conn.Close()
|
|
return nil, fmt.Errorf("unsupported address type: 0x%x", header[3])
|
|
}
|
|
pb := make([]byte, 2)
|
|
if _, err := io.ReadFull(conn, pb); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
relayPort = int(binary.BigEndian.Uint16(pb))
|
|
if relayIP.IsUnspecified() {
|
|
if remoteAddr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
|
relayIP = remoteAddr.IP
|
|
}
|
|
}
|
|
conn.SetDeadline(time.Time{})
|
|
relayAddr := &net.UDPAddr{IP: relayIP, Port: relayPort}
|
|
lConn, err := net.ListenUDP(network, nil)
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
return &socks5PacketConn{tcpConn: conn, udpConn: lConn, relayAddr: relayAddr}, nil
|
|
}
|