企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] ## socket协议版本 Socket协议分为Socket4和Socket5两个版本,他们最明显的区别是Socket5同时支持TCP和UDP两个协议,而SOcket4只支持TCP。目前大部分使用的是Socket5,我们这里只简单的介绍Socket5协议。 ## Socket5协议之授权认证 Socket6协议的实现细节和规范 ### 首先客户端会给服务端发送验证信息 | VER | NMETHODS |METHODS| | --- | --- |---| | 1 | 1 |1 to 255| 1. 第一个字段VER代表Socket的版本,Soket5默认为0x05,其固定长度为1个字节 2. 第二个字段NMETHODS表示第三个字段METHODS的长度,它的长度也是1个字节 3. 第三个METHODS表示客户端支持的验证方式,可以有多种,他的尝试是1-255个字节。 目前支持的验证方式一共有: ``` 1. X’00’ NO AUTHENTICATION REQUIRED(不需要验证) 2. X’01’ GSSAPI 3. X’02’ USERNAME/PASSWORD(用户名密码) 4. X’03’ to X’7F’ IANA ASSIGNED 5. X’80’ to X’FE’ RESERVED FOR PRIVATE METHODS 6. X’FF’ NO ACCEPTABLE METHODS(都不支持,没法连接了) ``` 以上的都是十六进制常量,比如X’00’表示十六进制0x00。 ### 服务器回应客户端 服务端收到客户端的验证信息之后,就要回应客户端,服务端需要客户端提供哪种验证方式的信息。服务端的回应同样非常简洁。 | VER | NMETHODS| | --- | --- | | 1 | 1 | 1. 一个字段VER代表Socket的版本,Soket5默认为0x05,其值长度为1个字节 2. 第二个字段METHOD代表需要服务端需要客户端按照此验证方式提供验证信息,其值长度为1个字节,选择为上面的六种验证方式。 举例说明,比如服务端不需要验证的话,可以这么回应客户端: | VER | NMETHODS| | --- | --- | | 0x05 | 0x00 | 我们使用Go实现代码如下: ``` var b [1024]byte n, err := client.Read(b[:]) if err != nil { log.Println(err) return } if b[0] == 0x05 { //只处理Socket5协议 //客户端回应:Socket服务端不需要验证方式 client.Write([]byte{0x05, 0x00}) n, err = client.Read(b[:]) } ``` ## Socket5协议之建立连接 Socket5的客户端和服务端进行双方授权验证通过之后,就开始建立连接了。连接由客户端发起,告诉Sokcet服务端客户端需要访问哪个远程服务器,其中包含,远程服务器的地址和端口,地址可以是IP4,IP6,也可以是域名。 | VER | CMD | RSV| ATYP | DST.ADDR | DST.PORT| | --- | --- |--- |--- |--- |--- | | 1 | 1 |X’00’|1|Variable |2| 1. VER代表Socket协议的版本,Soket5默认为0x05,其值长度为1个字节 2. CMD代表客户端请求的类型,值长度也是1个字节,有三种类型 * CONNECT X’01’ * BIND X’02’ * UDP ASSOCIATE X’03’ 3. RSV保留字,值长度为1个字节 4. ATYP代表请求的远程服务器地址类型,值长度1个字节,有三种类型 * IP V4 address: X’01’ * DOMAINNAME: X’03’ * IP V6 address: X’04’ 5. DST.ADDR代表远程服务器的地址,根据ATYP进行解析,值长度不定。 6. DST.PORT代表远程服务器的端口,要访问哪个端口的意思,值长度2个字节 从协议里解析我们需要的远程服务器信息,Go代码实现如下: ``` var host,port string switch b[3] { case 0x01://IP V4 host = net.IPv4(b[4],b[5],b[6],b[7]).String() case 0x03://域名 host = string(b[5:n-2])//b[4]表示域名的长度 case 0x04://IP V6 host = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String() } port = strconv.Itoa(int(b[n-2])<<8|int(b[n-1])) ``` 现在客户端把要请求的远程服务器的信息都告诉Socket5代理服务器了,那么Socket5代理服务器就可以和远程服务器建立连接了,不管连接是否成功等,都要给客户端回应,其回应格式为: | VER | REP| RSV| ATYP | BND.ADDR | BND.PORT| | --- | --- |--- |--- |--- |--- | | 1 | 1 |X’00’|1|Variable |2| 1. VER代表Socket协议的版本,Soket5默认为0x05,其值长度为1个字节 2. REP代表响应状态码,值长度也是1个字节,有以下几种类型 * X’00’ succeeded * X’01’ general SOCKS server failure * X’02’ connection not allowed by ruleset * X’03’ Network unreachable * X’04’ Host unreachable * X’05’ Connection refused * X’06’ TTL expired * X’07’ Command not supported * X’08’ Address type not supported * X’09’ to X’FF’ unassigned 3. RSV保留字,值长度为1个字节 4. ATYP代表请求的远程服务器地址类型,值长度1个字节,有三种类型 * IP V4 address: X’01’ * DOMAINNAME: X’03’ * IP V6 address: X’04’ 5. BND.ADDR表示绑定地址,值长度不定。 6. BND.PORT表示绑定端口,值长度2个字节 Go实现的连接和应答如下: ``` server, err := net.Dial("tcp", net.JoinHostPort(host, port)) if err != nil { log.Println(err) return } defer server.Close() client.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) //响应客户端连接成功 ``` ## 数据转发 建立好连接之后,就是数据传递转发,TCP协议可以直接转发。UDP的话需要特殊处理,具体参考协议定义,其实就是一个特殊格式的回应,和上面的一问一答差不多,更多协议细节 [http://www.ietf.org/rfc/rfc1928.txt](http://www.ietf.org/rfc/rfc1928.txt)。 TCP直接转发非常简单: ``` //进行转发 go io.Copy(server, client) io.Copy(client, server) ``` ## 完整代码实现 以下是完成的代码实现: ``` package main import ( "io" "log" "net" "strconv" ) func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) l, err := net.Listen("tcp", ":8081") if err != nil { log.Panic(err) } for { client, err := l.Accept() if err != nil { log.Panic(err) } go handleClientRequest(client) } } func handleClientRequest(client net.Conn) { if client == nil { return } defer client.Close() var b [1024]byte n, err := client.Read(b[:]) if err != nil { log.Println(err) return } if b[0] == 0x05 { //只处理Socket5协议 //客户端回应:Socket服务端不需要验证方式 client.Write([]byte{0x05, 0x00}) n, err = client.Read(b[:]) var host, port string switch b[3] { case 0x01: //IP V4 host = net.IPv4(b[4], b[5], b[6], b[7]).String() case 0x03: //域名 host = string(b[5 : n-2]) //b[4]表示域名的长度 case 0x04: //IP V6 host = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String() } port = strconv.Itoa(int(b[n-2])<<8 | int(b[n-1])) server, err := net.Dial("tcp", net.JoinHostPort(host, port)) if err != nil { log.Println(err) return } defer server.Close() client.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) //响应客户端连接成功 //进行转发 go io.Copy(server, client) io.Copy(client, server) } } ```