Files
v2ray_simple/proxy/socks5/server.go
hahahrfool fa1c5db1ef 上一个commit修改了函数签名,不好,太笨,还是不改好
proxy.Server返回的 *bytes.Buffer只被用于Fallback,所以直接放到Fallback里就行
2022-03-16 08:19:09 +08:00

327 lines
7.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package socks5
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"math/rand"
"net"
"net/url"
"time"
"github.com/hahahrfool/v2ray_simple/common"
"github.com/hahahrfool/v2ray_simple/proxy"
)
func init() {
proxy.RegisterServer(Name, NewSocks5Server)
}
type Server struct {
user string
password string
proxy.ProxyCommonStruct
}
func NewSocks5Server(url *url.URL) (proxy.Server, error) {
addr := url.Host
// TODO: Support Auth
user := url.User.Username()
password, _ := url.User.Password()
s := &Server{
ProxyCommonStruct: proxy.ProxyCommonStruct{Addr: addr},
user: user,
password: password,
}
s.ProxyCommonStruct.InitFromUrl(url)
return s, nil
}
func (s *Server) Name() string { return Name }
func (s *Server) CanFallback() bool {
return false
}
//English: https://www.ietf.org/rfc/rfc1928.txt
//中文: https://aber.sh/articles/Socks5/
// 参考 https://studygolang.com/articles/31404
func (s *Server) Handshake(underlay net.Conn) (io.ReadWriter, *proxy.Addr, error) {
// Set handshake timeout 4 seconds
if err := underlay.SetReadDeadline(time.Now().Add(time.Second * 4)); err != nil {
return nil, nil, err
}
defer underlay.SetReadDeadline(time.Time{})
buf := common.GetPacket()
defer common.PutPacket(buf)
// Read hello message
// 一般握手包发来的是 [5 1 0]
n, err := underlay.Read(buf)
if err != nil || n == 0 {
return nil, nil, fmt.Errorf("failed to read hello: %w", err)
}
version := buf[0]
if version != Version5 {
return nil, nil, fmt.Errorf("unsupported socks version %v", version)
}
// Write hello response [5 0]
// TODO: Support Auth
_, err = underlay.Write([]byte{Version5, AuthNone})
if err != nil {
return nil, nil, fmt.Errorf("failed to write hello response: %w", err)
}
// Read command message
n, err = underlay.Read(buf)
if err != nil || n < 7 { // Shortest length is 7
return nil, nil, fmt.Errorf("read socks5 failed, msgTooShort: %w", err)
}
// 一般可以为 5 1 0 3 n3表示域名n是域名长度然后域名很可能是 119 119 119 46 开头,表示 www.
// 比如百度就是 [5 1 0 3 13 119 119 119 46 98]
cmd := buf[1]
switch cmd {
case CmdBind:
return nil, nil, fmt.Errorf("unsuppoted command %v", cmd)
case CmdUDPAssociate:
}
l := 2
off := 4
var theIP net.IP
switch buf[3] {
case ATypIP4:
l += net.IPv4len
theIP = make(net.IP, net.IPv4len)
case ATypIP6:
l += net.IPv6len
theIP = make(net.IP, net.IPv6len)
case ATypDomain:
l += int(buf[4])
off = 5
default:
return nil, nil, fmt.Errorf("unknown address type %v", buf[3])
}
if len(buf[off:]) < l {
return nil, nil, errors.New("short command request")
}
var theName string
if theIP != nil {
copy(theIP, buf[off:])
} else {
theName = string(buf[off : off+l-2])
}
var thePort int
thePort = int(buf[off+l-2])<<8 | int(buf[off+l-1])
//根据 socks5标准“使用UDP ASSOCIATE时客户端的请求包中(DST.ADDR, DST.PORT)不再是目标的地址而是客户端指定本身用于发送UDP数据包的地址和端口”
//然后服务器会传回专门适用于客户端的 一个 服务器的 udp的ip和地址然后之后客户端再专门 向udp地址发送连接此tcp连接就已经没用。
//总之UDP Associate方法并不是 UDP over TCP完全不同而且过程中握手用tcp传输用udp使用到了两个连接。
if cmd == CmdUDPAssociate {
clientFutureAddr := &proxy.Addr{
IP: theIP,
Name: theName,
Port: thePort,
IsUDP: true,
}
serverAtyp, serverAddr, _, err := proxy.ParseStrToAddr(s.Addr)
if serverAtyp != proxy.AtypIP4 { //暂时先只支持ipv4为了简单起见
if err != nil {
return nil, nil, errors.New("UDPAssociate: can't listen an domain, must be ip")
}
}
//随机生成一个端口专门用于处理该客户端。这是我的想法。
bindPort := 1024 + rand.Intn(50000)
udpPreparedAddr := &net.UDPAddr{
IP: serverAddr,
Port: bindPort,
}
udpRC, err := net.ListenUDP("udp", udpPreparedAddr)
if err != nil {
return nil, nil, errors.New("UDPAssociate: unable to listen udp")
}
//ver5, rep0表示成功, rsv0, atyp(1, 即ipv4), BND.ADDR(4字节的ipv4) , BND.PORT(2字节)
reply := []byte{Version5, 0x00, 0x00, 0x01, serverAddr[0], serverAddr[1], serverAddr[2], serverAddr[3], byte(int16(bindPort) >> 8), byte(int16(bindPort) << 8 >> 8)}
// Write command response
_, err = underlay.Write(reply)
if err != nil {
return nil, nil, fmt.Errorf("failed to write command response: %w", err)
}
uc := &UDPConn{
clientSupposedAddr: clientFutureAddr,
UDPConn: udpRC,
}
return uc, clientFutureAddr, nil
} else {
addr := &proxy.Addr{
IP: theIP,
Name: theName,
Port: thePort,
}
// 解读如下:
//ver5, rep0表示成功, rsv0, atyp(1, 即ipv4), BND.ADDR ipv4(0,0,0,0), BND.PORT(0, 2字节)
reply := []byte{Version5, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
//这个 BND.ADDR和port 按理说不应该传0的不过如果只作为本地tcp代理的话应该不影响
_, err = underlay.Write(reply)
if err != nil {
return nil, nil, fmt.Errorf("failed to write command response: %w", err)
}
return underlay, addr, nil
}
}
func (s *Server) Stop() {
// Nothing to stop or close
}
type UDPConn struct {
*net.UDPConn
clientSupposedAddr *proxy.Addr //客户端指定的客户端自己未来将使用的公网UDP的Addr
clientSupposedAddrIsNothing bool
}
// 阻塞
// 从 udpPutter.GetNewUDPResponse 循环阅读 所有需要发送给客户端的 数据,然后发送给客户端
// 这些响应数据是 由其它设施 写入 udpProxy的
func (u *UDPConn) StartPushResponse(udpPutter proxy.UDP_Putter) {
for {
raddr, bs, err := udpPutter.GetNewUDPResponse()
if err != nil {
break
}
buf := &bytes.Buffer{}
buf.WriteByte(0) //rsv
buf.WriteByte(0) //rsv
buf.WriteByte(0) //frag
var atyp byte = ATypIP4
if len(raddr.IP) > 4 {
atyp = ATypIP6
}
buf.WriteByte(atyp)
buf.Write(raddr.IP)
buf.WriteByte(byte(int16(raddr.Port) >> 8))
buf.WriteByte(byte(int16(raddr.Port) << 8 >> 8))
buf.Write(bs)
_, err = u.UDPConn.Write(buf.Bytes())
if err != nil {
break
}
}
}
// 阻塞
// 监听 与客户端的udp连接循环查看客户端发来的请求信息; 然后将该请求 用 udpPutter.WriteUDPRequest 发送给 udpPutter
// 至于fullcone与否它是不管的。
func (u *UDPConn) StartReadRequest(udpPutter proxy.UDP_Putter) {
if len(u.clientSupposedAddr.IP) < 3 || u.clientSupposedAddr.IP.IsUnspecified() {
u.clientSupposedAddrIsNothing = true
}
bs := make([]byte, proxy.MaxUDP_packetLen)
for {
n, addr, err := u.UDPConn.ReadFromUDP(bs)
if err != nil {
log.Println("UDPConn read err", err)
continue
}
if n < 6 {
log.Println("UDPConn short read err", n)
continue
}
if !u.clientSupposedAddrIsNothing && !addr.IP.Equal(u.clientSupposedAddr.IP) || addr.Port != u.clientSupposedAddr.Port {
//just random attack message.
continue
}
atyp := bs[3]
l := 2 //supposed Minimum Remain Data Lenth
off := 4 //offset from which the addr data really starts
var theIP net.IP
switch atyp {
case ATypIP4:
l += net.IPv4len
theIP = make(net.IP, net.IPv4len)
case ATypIP6:
l += net.IPv6len
theIP = make(net.IP, net.IPv6len)
case ATypDomain:
l += int(bs[4])
off = 5
default:
log.Println("UDPConn unknown address type ", atyp)
continue
}
if len(bs[off:]) < l {
log.Println("UDPConn short command request ", atyp)
continue
}
var theName string
if theIP != nil {
copy(theIP, bs[off:])
} else {
theName = string(bs[off : off+l-2])
}
var thePort int
thePort = int(bs[off+l-2])<<8 | int(bs[off+l-1])
newStart := off + l
thisaddr := &proxy.Addr{
IP: theIP,
Name: theName,
Port: thePort,
IsUDP: true,
}
udpPutter.WriteUDPRequest(thisaddr.ToUDPAddr(), bs[newStart:n-newStart])
}
}