SOCKS
2021-04-25 14:43:51 阿炯

SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递,SOCKS是"SOCKet Secure"的缩写。

当防火墙后的客户端要访问外部的服务器时,就跟SOCKS代理服务器连接。这个代理服务器控制客户端访问外网的资格,允许的话,就将客户端的请求发往外部的服务器。这个协议最初由David Koblas开发,而后由NEC的Ying-Da Lee将其扩展到SOCKS4。最新协议是SOCKS5,与前一版本相比,增加支持UDP、验证,以及IPv6。

根据OSI模型,SOCKS是会话层的协议,位于表示层与传输层之间,SOCKS协议不提供加密。


与HTTP代理的对比

SOCKS工作在比HTTP代理更低的层次:SOCKS使用握手协议来通知代理软件其客户端试图进行的SOCKS连接,然后尽可能透明地进行操作,而常规代理可能会解释和重写报头例如,使用另一种底层协议,例如FTP;然而,HTTP代理只是将HTTP请求转发到所需的HTTP服务器。虽然HTTP代理有不同的使用模式,HTTP CONNECT方法允许转发TCP连接;然而,SOCKS代理还可以转发UDP流量仅限SOCKS5,而HTTP代理不能。HTTP代理通常更了解HTTP协议,执行更高层次的过滤虽然通常只用于GET和POST方法,而不用于CONNECT方法。


版本分支

SOCKS 4

下面是客户端向SOCKS 4代理服务器,发送的连接请求包的格式(以字节为单位):
VNCDDSTPORTDSTIPUSERIDNULL
1124variable1


VN是SOCK版本,应该是4;
CD是SOCK的命令码,1表示CONNECT请求,2表示BIND请求;
DSTPORT表示目的主机的端口;
DSTIP指目的主机的IP地址;
NULL是0;

代理服务器而后发送回应包(以字节为单位):
VNCDDSTPORTDSTIP
1124


VN是回应码的版本,应该是0;
CD是代理服务器答复,有几种可能:
    90,请求得到允许;
    91,请求被拒绝或失败;
    92,由于SOCKS服务器无法连接到客户端的identd一个验证身份的进程,请求被拒绝;
    93,由于客户端程序与identd报告的用户身份不同,连接被拒绝。
DSTPORT与DSTIP与请求包中的内容相同,但被忽略。

如果请求被拒绝,SOCKS服务器马上与客户端断开连接;如果请求被允许,代理服务器就充当客户端与目的主机之间进行双向传递,对客户端而言,就如同直接在与目的主机相连。


SOCKS 4a

SOCKS 4A是SOCKS 4协议的简单扩展,允许客户端对无法解析域名的目的主机进行访问。

客户端对DSTIP的头三个字节设定为NULL,最后一个字节为非零;对应的IP地址就是0.0.0.x,其中x是非零,这当然不可能是目的主机的地址,这样即使客户端可以解析域名,对此也不会发生冲突。USERID以紧跟的NULL字节作结尾,客户端必须发送目的主机的域名,并以另一个NULL字节作结尾。CONNECT和BIND请求的时候,都要按照这种格式(以字节为单位):
VNCDDSTPORTDSTIP 0.0.0.xUSERIDNULLHOSTNAMENULL
1124variable1variable1


使用4a协议的服务器必须检查请求包里的DSTIP字段,如果表示地址0.0.0.x,x是非零结尾,那么服务器就得读取客户端所发包中的域名字段,然后服务器就得解析这个域名,可以的话,对目的主机进行连接。


SOCKS 5

SOCKS5比SOCKS4a多了验证、IPv6、UDP支持。创建与SOCKS5服务器的TCP连接后客户端需要先发送请求来确认协议版本及认证方式,格式为以字节为单位:
VERNMETHODSMETHODS
111-255


VER是SOCKS版本,这里应该是0x05;
NMETHODS是METHODS部分的长度;
METHODS是客户端支持的认证方式列表,每个方法占1字节。当前的定义是:
    0x00 不需要认证
    0x01 GSSAPI
    0x02 用户名、密码认证
    0x03 - 0x7F由IANA分配(保留)
    0x80 - 0xFE为私人方法保留
    0xFF 无可接受的方法

服务器从客户端提供的方法中选择一个并通过以下消息通知客户端(以字节为单位):
VERMETHOD
11


VER是SOCKS版本,这里应该是0x05;
METHOD是服务端选中的方法。如果返回0xFF表示没有一个认证方法被选中,客户端需要关闭连接。

之后客户端和服务端根据选定的认证方式执行对应的认证,认证结束后客户端就可以发送请求信息。如果认证方法有特殊封装要求,请求必须按照方法所定义的方式进行封装。

SOCKS5请求格式(以字节为单位):
VERCMDRSVATYPDST.ADDRDST.PORT
110x001动态2


VER是SOCKS版本,这里应该是0x05;
CMD是SOCK的命令码
    0x01表示CONNECT请求
    0x02表示BIND请求
    0x03表示UDP转发

RSV 0x00,保留
ATYP DST.ADDR类型
    0x01 IPv4地址,DST.ADDR部分4字节长度
    0x03 域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有\0结尾。
    0x04 IPv6地址,16个字节长度。

DST.ADDR 目的地址
DST.PORT 网络字节序表示的目的端口

服务器按以下格式回应客户端的请求(以字节为单位):
VERREPRSVATYPBND.ADDRBND.PORT
110x001动态2


VER是SOCKS版本,这里应该是0x05;
REP应答字段
    0x00表示成功
    0x01普通SOCKS服务器连接失败
    0x02现有规则不允许连接
    0x03网络不可达
    0x04主机不可达
    0x05连接被拒
    0x06 TTL超时
    0x07不支持的命令
    0x08不支持的地址类型
    0x09 - 0xFF未定义

RSV 0x00,保留
ATYP BND.ADDR类型
    0x01 IPv4地址,DST.ADDR部分4字节长度
    0x03域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有\0结尾。
    0x04 IPv6地址,16个字节长度。

BND.ADDR 服务器绑定的地址
BND.PORT 网络字节序表示的服务器绑定的端口


SOCKS5 用户名密码认证方式

在客户端、服务端协商使用用户名密码认证后,客户端发出用户名密码,格式为(以字节为单位):
鉴定协议版本用户名长度用户名密码长度密码
11动态1动态


鉴定协议版本目前为 0x01 。

服务器鉴定后发出如下回应:
鉴定协议版本鉴定状态
11


其中鉴定状态 0x00 表示成功,0x01 表示失败。


代理服务器常用的端口有:
HTTP代理:80/8080/3128/8081/9080,SOCKS代理:1080

Http和Socks代理的区别1

SOCKS其实是一种网络代理协议,该协议所描述的是一种内部主机使用私有ip地址通过SOCKS服务器获得完全的Internet访问的方法。具体说来是这样一个环境:用一台运行SOCKS的服务器连接内部网和Internet,内部网主机使用的都是私有的ip地址,内部网主机请求访问Internet时,首先和SOCKS服务器建立一个SOCKS通道,然后再将请求通过这个通道发送给SOCKS服务器,SOCKS服务器在收到客户请求后,向客户请求的Internet主机发出请求,得到相应后,SOCKS服务器再通过原先建立的SOCKS通道将数据返回给客户。

当然在建立SOCKS通道的过程中可能有一个用户认证的过程。SOCKS和一般的应用层代理服务器完全不同,一般的应用层代理服务器工作在应用层,并且针对不用的网络应用提供不同的处理方法,比如HTTP、FTP、SMTP等,这样一旦有新的网络应用出现时,应用层代理服务器就不能提供对该应用的代理,因此应用层代理服务器的可扩展性并不好。

与应用层代理服务器不同的是,SOCKS代理服务器旨在提供一种广义代理工作再线路层即应用层和传输层之间,这和单纯工作在网络层或传输层的ip欺骗或者叫做网络地址转换NAT又有所不同,因为SOCKS不能提供网络层网关服务,比如ICMP包。socks4和socks5都属于socks协议,只是由于所支持的具体应用不同而存在差异。socks4代理只支持TCP应用,而socks5代理则可以支持TCP和UDP两种应用。不过由于socks5代理还支持各种身份验证机制,服务器端域名解析等,而socks4代理没有,所以通常对外开放的socks代理都是socks4代理,因此UDP应用通常都不能被支持。

Http和Socks代理的区别2

HTTP代理:能够代理客户机的HTTP访问,主要是代理浏览器访问网页,它的端口一般为80、8080、3128等。

SOCKS代理:SOCKS代理与其他类型的代理不同,它只是简单地传递数据包,而并不关心是何种应用协议,既可以是HTTP请求,所以SOCKS代理服务器比其他类型的代理服务器速度要快得多。SOCKS代理又分为SOCKS4和SOCKS5,二者不同的是SOCKS4代理只支持TCP协议即传输控制协议,而SOCKS5代理则既支持TCP协议又支持UDP协议即用户数据包协议,还支持各种身份验证机制、服务器端域名解析等。SOCK4能做到的SOCKS5都可得到,但SOCKS5能够做到的SOCK4则不一定能做到,比如我们常用的聊天工具QQ在使用代理时就要求用SOCKS5代理,因为它需要使用UDP协议来传输数据。

SOCKS4a extends the SOCKS4 protocol to allow a client to specify a destination domain name rather than an IP address; this is useful when the client itself cannot resolve the destination host's domain name to an IP address.

The SOCKS5 protocol is defined in RFC 1928. It is an extension of the SOCKS4 protocol; it offers more choices for authentication and adds support for IPv6 and UDP, the latter of which can be used for DNS lookups.

SOCKS代理又分为SOCKS4和SOCKS5,二者不同的是SOCKS4代理只支持TCP协议即传输控制协议,而SOCKS5代理则既支持TCP协议又支持UDP协议即用户数据包协议,还支持各种身份验证机制、服务器端域名解析等。SOCK4能做到的SOCKS5都可得到,但SOCKS5能够做到的SOCK4则不一定能做到,比如我们常用的聊天工具QQ在使用代理时就要求用SOCKS5代理,因为它需要使用UDP协议来传输数据。如果是不想让人看到真实IP,用高匿的SOCKS5代理IP就可以的,可在一定程度上避免让别人查到我的真实IP。


初识Socket通信原理

Socket 是什么以及创建过程

一个数据包经由应用程序产生,进入到协议栈中进行各种报文头的包装,然后操作系统调用网卡驱动程序指挥硬件,把数据发送到对端主机。整个过程的大体的图示如下。


协议栈其实是位于操作系统中的一些协议的堆叠,这些协议包括TCP、UDP、ARP、ICMP、IP等。通常某个协议的设计都是为了解决某些问题,比如 TCP 的设计就负责安全可靠的传输数据,UDP 设计就是报文小,传输效率高,ARP 的设计是能够通过 IP 地址查询物理(Mac)地址,ICMP 的设计目的是返回错误报文给主机,IP 设计的目的是为了实现大规模主机的互联互通。

应用程序比如浏览器、电子邮件、文件传输服务器等产生的数据,会通过传输层协议进行传输,而应用程序是不会和传输层直接建立联系的,而是有一个能够连接应用层和传输层之间的套件,这个套件就是 Socket。

在上图中,应用程序包含 Socket 和解析器,解析器的作用就是向 DNS 服务器发起查询,查询目标 IP 地址。

应用程序的下面就是操作系统内部,操作系统内部包括协议栈,协议栈是一系列协议的堆叠。操作系统下面就是网卡驱动程序,网卡驱动程序负责控制网卡硬件,驱动程序驱动网卡硬件完成收发工作。

在操作系统内部有一块用于存放控制信息的存储空间,这块存储空间记录了用于控制通信的控制信息。其实这些控制信息就是 Socket 的实体,或者说存放控制信息的内存空间就是套接字的实体。

这里有可能不太清楚,下面用netstat命令来看一下套接字。

在 Windows 的命令提示符中输入:
netstat -ano
# netstat 用于显示套接字内容 , -ano 是可选选项
# a 不仅显示正在通信的套接字,还显示包括尚未开始通信等状态的所有套接字
# n 显示 IP 地址和端口号
# o 显示套接字的程序 PID

计算机会出现下面的结果


图中的每一行都相当于一个套接字,每一列也被称为一个五元组,所以一个套接字就是五元组(协议、本地地址、外部地址、状态、PID)。有的时候也被叫做四元组,四元组不包括协议。

比如图中的第一行,它的协议就是 TCP,本地地址和远程地址都是 0.0.0.0,这表示通信还没有开始,IP 地址暂时还未确定,而本地端口已知是 135,但是远程端口还未知,此时的状态是 LISTENING,表示应用程序已经打开,正在等待与远程主机建立连接,最后一个元组是 PID,即进程标识符,PID 就像我们的身份证号码,能够精确定位唯一的进程。

现在可能对 Socket 有了一个基本的认识,现在的问题是Socket是如何创建的呢?

Socket 是和应用程序一起创建的。应用程序中有一个 socket 组件,在应用程序启动时,会调用 socket 申请创建套接字,协议栈会根据应用程序的申请创建套接字:首先分配一个套接字所需的内存空间,这一步相当于是为控制信息准备一个容器,但只有容器并没有实际作用,所以还需要向容器中放入控制信息;如果不申请创建套接字所需要的内存空间,创建的控制信息也没有地方存放,所以分配内存空间,放入控制信息缺一不可。至此套接字的创建就已经完成了。

套接字创建完成后,会返回一个套接字描述符给应用程序,这个描述符相当于是区分不同套接字的号码牌。根据这个描述符,应用程序在委托协议栈收发数据时就需要提供这个描述符。

套接字连接

套接字创建完成后,最终还是为数据收发服务的,在数据收发之前,还需要进行一步 connect,也就是建立连接的过程。这个连接并不是真实的连接:用一根水管插在两个电脑之间。


而是应用程序通过 TCP/IP 协议标准从一个主机通过网络介质传输到另一个主机的过程。

套接字刚刚创建完成后,还没有数据,也不知道通信对象。在这种状态下,即使你让客户端应用程序委托协议栈发送数据,它也不知道发送到哪里。所以浏览器需要根据网址来查询服务器的 IP 地址,做这项工作的协议是 DNS,查询到目标主机后,再把目标主机的 IP 告诉协议栈,至此,客户端这边就准备好了。

在服务器上,与客户端一样也需要创建套接字,但是同样的它也不知道通信对象是谁,所以需要让客户端向服务器告知客户端的必要信息:IP 地址和端口号。

现在通信双方建立连接的必要信息已经具备了。通信双方收到数据之后,还需要一块位置来存放,这个位置就是缓冲区,它是内存的一部分,有了缓冲区,就能够进行数据的收发操作了。

OK,现在客户端想要给服务器发送一条数据,该进行哪些操作呢?

首先,客户端应用程序需要调用 Socket 库中的 connect 方法,提供 socket 描述符和服务器 IP 地址、端口号。

connect(<描述符>、<服务器IP地址和端口号>)

这些信息会传递给协议栈中的 TCP 模块,TCP 模块会对请求报文进行封装,再传递给 IP 模块,进行 IP 报文头的封装,然后传递给物理层,进行帧头封装,之后通过网络介质传递给服务器,服务器上会对帧头、IP 模块、TCP 模块的报文头进行解析,从而找到对应的套接字,套接字收到请求后,会写入相应的信息,并且把状态改为正在连接。请求过程完成后,服务器的 TCP 模块会返回响应,这个过程和客户端是一样的(如果大家不太清楚报文头的封装过程,可以阅读一些TCP/IP基础知识总结)。

在一个完整的请求和响应过程中,控制信息起到非常关键的作用(具体的作用后面会说)。

SYN 就是同步的缩写,客户端会首先发送 SYN 数据包,请求服务端建立连接。
ACK 就是响应的意思,它是对发送 SYN 数据包的响应。
FIN 是终止的意思,它表示客户端/服务器想要终止连接。

由于网络环境的复杂多变,经常会存在数据包丢失的情况,所以双方通信时需要相互确认对方的数据包是否已经到达,而判断的标准就是 ACK 的值(通信双方连接的建立会经过三次握手流程,对三次握手详细的介绍可以阅读笔者的这篇文章 TCP 基础知识)。

当所有建立连接的报文都能够正常收发之后,此时套接字就已经进入可收发状态了,此时可以认为用一根管理把两个套接字连接了起来。建立连接之后,协议栈的连接操作就结束了,也就是说 connect 已经执行完毕,控制流程被交回给应用程序。

收发数据

当控制流程从 connect 回到应用程序之后,接下来就会直接进入数据收发阶段,数据收发操作是从应用程序调用 write 将要发送的数据交给协议栈开始的,协议栈收到数据之后执行发送操作。

协议栈不会关心应用程序传输过来的是什么数据,因为这些数据最终都会转换为二进制序列,协议栈在收到数据之后并不会马上把数据发送出去,而是会将数据放在发送缓冲区,再等待应用程序发送下一条数据。

为什么收到数据包不会直接发送出去,而是放在缓冲区中呢?

因为只要一旦收到数据就会发送,就有可能发送大量的小数据包,导致网络效率下降。所以协议栈需要将数据积攒到一定数量才能将其发送出去。至于协议栈会向缓冲区放多少数据,这个不同版本和种类的操作系统有不同的说法,不过,所有的操作系统和种类都会遵循下面这几个标准:

第一个判断要素是每个网络包能够容纳的数据长度,判断的标准是MTU,它表示的是一个网络包的最大长度。最大长度包含头部,所以如果单论数据区的话,就会用 MTU - 包头长度,由此的出来的最大数据长度被称为 MSS。


另一个判断标准是时间,当应用程序产生的数据比较少,协议栈向缓冲区放置数据效率不高时,如果每次都等到 MSS 再发送的话,可能因为等待时间太长造成延迟,在这种情况下,即使数据长度没有到达 MSS,也应该把数据发送出去。

协议栈并没有告诉我们怎样平衡这两个因素,如果数据长度优先,那么效率有可能比较低;如果时间优先,那又会降低网络的效率。

经过了一段时间。。。

假设使用的是长度有限法则,此时缓冲区已满,协议栈要发送数据了,协议栈刚要把数据发送出去,却发现无法一次性传输这么大数据量(相对的)的数据,那怎么办呢?

在这种情况下,发送缓冲区中的数据就会超过 MSS 的长度,发送缓冲区中的数据会以 MSS 大小为一个数据包进行拆分,拆分出来的每块数据都会加上 TCP,IP,以太网头部,然后被放进单独的网络包中。


到现在,网络包已经准备好发往服务器了,但是数据发送操作还没有结束,因为服务器还未确认是否已经收到网络包。因此在客户端发送数据包之后,还需要服务器进行确认。

TCP 模块在拆分数据时,会计算出网络包偏移量,这个偏移量就是相对于数据从头开始计算的第几个字节,并将算好的字节数写在 TCP 头部,TCP 模块还会生成一个网络包的序号(SYN),这个序号是唯一的,这个序号就是用来让服务器进行确认的。

服务器会对客户端发送过来的数据包进行确认,确认无误之后,服务器会生成一个序号和确认号(ACK)并一起发送给客户端,客户端确认之后再发送确认号给服务器。

我们来看一下实际的工作过程。


首先,客户端在连接时需要计算出序号初始值,并将这个值发送给服务器。接下来,服务器通过这个初始值计算出 确认号并返回给客户端。初始值在通信过程中有可能会丢弃,因此当服务器收到初始值后需要返回确认号用于确认。同时,服务器也需要计算出从服务器到客户端方向的序号初始值,并将这个值发送给客户端。然后,客户端也需要根据服务器发来的初始值计算出确认号发送给服务器,至此,连接建立完成,接下来就可以进入数据收发阶段了。

数据收发阶段中,通信双方可以同时发送请求和响应,双方也可以同时对请求进行确认。

请求 - 确认机制非常强大,通过这一机制,我们可以确认接收方有没有收到某个包,如果没有收到则重新发送,这样一来,但凡网络中出现的任何错误,我们都可以即使发现并补救。网卡、集线器、路由器都没有错误补救机制,一旦检测到错误就会直接丢弃数据包,应用程序也没有这种机制,起作用的只是 TCP/IP 模块。

由于网络环境复杂多变,所以数据包会存在丢失情况,因此发送序号和确认号也存在一定规则,TCP 会通过窗口管理确认号,我们这篇文章不再赘述,大家可以阅读笔者的这篇文章 TCP 基础知识 来寻找答案。

断开连接

当通信双方不再需要收发数据时,需要断开连接。不同的应用程序断开连接的时机不同。以 Web 为例,浏览器向 Web 服务器发送请求消息,Web 服务器再返回响应消息,这时收发数据就全部结束了,服务器可能会首先发起断开响应,当然客户端也有可能会首先发起(谁先断开连接是应用程序做出的判断),与协议栈无关。


无论哪一方发起断开连接的请求,都会调用 Socket 库的 close 程序。以服务器断开连接为例,服务器发起断开连接请求,协议栈会生成断开连接的 TCP 头部,其实就是设置 FIN 位,然后委托 IP 模块向客户端发送数据,与此同时,服务器的套接字会记录下断开连接的相关信息。

收到服务器发来 FIN 请求后,客户端协议栈会将套接字标记为断开连接状态,然后,客户端会向服务器返回一个确认号,这是断开连接的第一步,在这一步之后,应用程序还会调用 read 来读取数据。等到服务器数据发送完成后,协议栈会通知客户端应用程序数据已经接收完毕。

只要收到服务器返回的所有数据,客户端就会调用 close 程序来结束收发操作,这时客户端会生成一个 FIN 发送给服务器,一段时间后服务器返回 ACK 号,至此,客户端和服务器的通信就结束了。

删除套接字

通信完成后,用来通信的套接字就不再会使用了,此时我们就可以删除这个套接字了。不过此时的套接字不会马上删除,而是等过一段时间再删除。等待这段时间是为了防止误操作,最常见的误操作就是客户端返回的确认号丢失,至于等待多长时间,和数据包重传的方式有关。


参考来源

SOCKS_维基百科

Unix域套接字(Unix Domain Socket)