HTTP协议和SOCKS5协议
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
我们平时上网的时候基本上是离不开浏览器的,尤其是搜索资料的时候,那么这个浏览器是如何工作的呢?用的又是什么协议呢?协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则。这就引入了我们今天要的两个主角,即HTTP和SOCK5协议,他们都可以做代理服务器。经过查阅相关资料,终于这这2个协议有点眉目,再次和大家共享一下学习心得。
一.什么是HTTP协议。
1>.HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传输协议。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等;
2>.HTTP在传输层用的是TCP协议。我们知道HTTP是属于应用层的协议;
3>.HTTP是客户端浏览器或其他程序与Web服务器之间的应用层通信协议;
4>.我们在浏览器的地址栏里输入的网站地址叫做URL (Uniform Resource Locator,统一资源定位符)。浏览器通过超文本传输协议(HTTP),将Web服务器上站点的网页代码提取出来,并翻译成漂亮的网页;
5>.http协议是无状态的,同一个客户端的这次请求和上次请求是没有对应关系,对http服务器来说,它并不知道这两个请求来自同一个客户端。 为了解决这个问题, Web程序引入了Cookie机制来维护状态。
二.打开一个网页需要浏览器发送很多次Request。
1>.. 当你在浏览器输入URL"www.cnblogs.com/yinzhengjie"的时候,浏览器发送一个格式化后的Request去获取 “ http://www.cnblogs.com/yinzhengjie ” 的html. 服务器把Response发送回给浏览器.
2>. 浏览器分析Response中的 HTML,发现其中引用了很多其他文件,比如图片,CSS文件,JS文件。浏览器会按照自己的规则去获取相应的内容,比如会先显示文字后显示图片等等。
3>. 浏览器会自动再次发送Request去获取文字,图片,CSS文件,或者JS文件。
4>. 等所有的文件都下载成功后。 漂亮的网页就被显示出来了。
三. HTTP的URL介绍。
HTTP使用统一资源标识符(Uniform Resource Identifiers, URI,用来唯一的标识一个资源)来传输数据和建立连接。URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息URL,全称是UniformResourceLocator, 中文叫统一资源定位符(也叫统一资源定位器),它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。具体的格式可归纳为:http://host[":"port][abs_path]。
下面我们举个例子,相信大家就一目了然了,比如:“https://www.yinzhengjie.com:8888/web/yinzhengjie/index.html?name=尹正杰&x=true#第三章”
我们可以用Golang代码来解析一下:
/*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"net/url"
"log"
"fmt"
) func main() {
s := "https://www.yinzhengjie.com:8888/web/yinzhengjie/index.html?name=尹正杰&x=true#第三章"
u,err := url.Parse(s)
if err != nil {
log.Fatal(err)
}
fmt.Println("使用的协议是:",u.Scheme)
fmt.Println("主机名称是:",u.Host)
fmt.Println("端口号是:",u.Port())
fmt.Println("当前路径是:",u.Path)
fmt.Println("请求信息是:",u.RawQuery)
fmt.Println("参数部分是:",u.User)
fmt.Println("当前的锚点是:",u.Fragment)
} /*
1.协议部分:该URL的协议部分为“https:”,这代表网页使用的是HTTP协议。在Internet中可以使用多种协议,如HTTP,FTP,SVN等等本例中使用的是HTTPS协议。在"HTTPS"后面的“//”为分隔符;
2.域名部分:该URL的域名部分为“www.yinzhengjie.com”。一个URL中,也可以使用IP地址作为域名使用;
3.端口部分:跟在域名后面的是端口,我们的端口就是8888,域名和端口之间使用“:”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口,HTTP默认就是80端口;
4.虚拟目录部分:从域名后的第一个“/”开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分。本例中的虚拟目录是“/web/yinzhengjie”;
5.文件名部分:从域名后的最后一个“/”开始到“?”为止,是文件名部分,如果没有“?”,则是从域名后的最后一个“/”开始到“#”为止,是文件部分,如果没有“?”和“#”,
那么从域名后的最后一个“/”开始到结束,都是文件名部分。本例中的文件名是“index.html”。文件名部分也不是一个URL必须的部分,如果省略该部分,则使用默认的文件名,比如apache的默认文件
就是"index.html",当然如果你修改过http的首页配置文件就林当别论啦;
6.锚部分:从“#”开始到最后,都是锚部分。本例中的锚部分是“第三章”。锚部分也不是一个URL必须的部分;
7.参数部分:从“?”开始到“#”为止之间的部分为参数部分,又称搜索部分、查询部分。本例中的参数部分为“name=尹正杰&x=true”。参数可以允许有多个参数,参数与参数之间用“&”作为分隔符。
*/ #以上代码执行结果如下:
使用的协议是: https
主机名称是: www.yinzhengjie.com:8888
端口号是: 8888
当前路径是: /web/yinzhengjie/index.html
请求信息是: name=尹正杰&x=true
参数部分是: <nil>
当前的锚点是: 第三章
四.HTTP协议结构。
大家都知道,HTTP(HyperText Transport Protocol)是超文本传输协议的缩写,它用于传送WWW方式的数据,关于HTTP协议的详细内容请参考RFC2616。通常HTTP消息包括客户机向服务器的请求消息和服务器向客户机的响应消息。这两种类型的消息由一个起始行,一个或者多个头域,一个指示头域结束的空行和可选的消息体组成。HTTP的头域包括通用头,请求头,响应头和实体头四个部分。
五.通用头域。
通用头域包含请求和响应消息都支持的头域,通用头域包含Cache-Control、Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via。对通用头域的扩展要求通讯双方都支持此扩展,如果存在不支持的通用头域,一般将会作为实体头域处理。
1.请求消息头域:(HTTP Request header)。
使用Fiddler 能很方便的查看Reques header, 点击Inspectors tab ->Request tab-> headers 如下图所示:
header 有很多,比较难以记忆,我们也按照Fiddler那样把header 进行分类,这样比较清晰也容易记忆。
Cache 头域
If-Modified-Since作用: 把浏览器端缓存页面的最后修改时间发送到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行对比。如果时间一致,那么返回304,客户端就直接使用本地缓存文件。如果时间不一致,就会返回200和新的文件内容。客户端接到之后,会丢弃旧文件,把新文件缓存起来,并显示在浏览器中.例如:If-Modified-Since: Thu, 09 Feb 2012 09:07:57 GMT.实例如下图:
If-None-Match作用: If-None-Match和ETag一起工作,工作原理是在HTTP Response中添加ETag信息。 当用户再次请求该资源时,将在HTTP Request 中加入If-None-Match信息(ETag的值)。如果服务器验证资源的ETag没有改变(该资源没有更新),将返回一个304状态告诉客户端使用本地缓存文件。否则将返回200状态和新的资源和Etag. 使用这样的机制将提高网站的性能.例如: If-None-Match: "03f2b33c0bfcc1:0".实例如下图:
Pragma作用: 防止页面被缓存, 在HTTP/1.1版本中,它和Cache-Control:no-cache作用一模一样Pargma只有一个用法, 例如: Pragma: no-cache.注意: 在HTTP/1.0版本中,只实现了Pragema:no-cache, 没有实现Cache-Control.
Cache-Control作用: 这个是非常重要的规则。 这个用来指定Response-Request遵循的缓存机制。各个指令含义如下:
'''
Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存
处理过程。请求时的缓存指令包括no-cache、no-store、max-age、max-stale、min-fresh、only-if-cached,响应消息中的指令包括public、
private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age。
各个消息中的指令含义如下:
1>.Public指示响应可被任何缓存区缓存。
2>.Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应
消息对于其他用户的请求无效。
3>.no-cache指示请求或响应消息不能缓存
4>.no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
5>.max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
6>.min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
7>.max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
'''
Client 头域
Accept作用: 浏览器端可以接受的媒体类型,例如: Accept: text/html 代表浏览器可以接受服务器回发的类型为 text/html 也就是我们常说的html文档,如果服务器无法返回text/html类型的数据,服务器应该返回一个406错误(non acceptable)。通配符 * 代表任意类型.例如 Accept: */* 代表浏览器可以处理所有类型,(一般浏览器发给服务器都是发这个)
Accept-Encoding作用: 浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),(注意:这不是只字符编码);例如: Accept-Encoding: gzip, deflate
Accept-Language作用: 浏览器申明自己接收的语言。 语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等;例如: Accept-Language: en-us
User-Agent作用:告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本.我们上网登陆论坛的时候,往往会看到一些欢迎信息,其中列出了你的操作系统的名称和版本,你所使用的浏览器的名称和版本,这往往让很多人感到很神奇,实际上,服务器应用程序就是从User-Agent这个请求报头域中获取到这些信息User-Agent请求报头域允许客户端将它的操作系统、浏览器和其它属性告诉服务器。例如: User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; CIBA; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; InfoPath.2; .NET4.0E)
Accept-Charset作用:浏览器申明自己接收的字符集,这就是本文前面介绍的各种字符集和字符编码,如gb2312,utf-8(通常我们说Charset包括了相应的字符编码方案);例如: Accept-Language: en-us
Cookie/Login 头域
Cookie:作用: 最重要的header, 将cookie的值发送给HTTP 服务器
Entity头域
Content-Length作用:发送给HTTP服务器数据的长度。例如: Content-Length: 38
Content-Type作用:例如:Content-Type: application/x-www-form-urlencoded
Miscellaneous 头域
Referer作用: 提供了Request的上下文信息的服务器,告诉服务器我是从哪个链接过来的,比如从我主页上链接到一个朋友那里,他的服务器就能够从HTTP Referer中统计出每天有多少用户点击我主页上的链接访问他的网站。例如: Referer:http://www.cnblogs.com/yinzhengjie
Transport 头域
Connection例如: Connection: keep-alive 当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接例如: Connection: close 代表一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接。
Host(发送请求时,该报头域是必需的)作用: 请求报头域主要用于指定被请求资源的Internet主机和端口号,它通常从HTTP URL中提取出来的.例如: 我们在浏览器中输入:http://www.cnblogs.com/yinzhengjie浏览器发送的请求消息中,就会包含Host请求报头域,如下:http://www.cnblogs.com/yinzhengjie.此处使用缺省端口号80,若指定了端口号,则变成:Host:指定端口号
2.响应消息头域:(HTTP Response header)。
同样使用Fiddler 查看Response header, 点击Inspectors tab ->Response tab-> headers 如下图所示
我们也按照Fiddler那样把header 进行分类,这样比较清晰也容易记忆。
Cache头域
Date作用: 生成消息的具体时间和日期.例如: Date: Sat, 11 Feb 2012 11:35:14 GMT
Expires作用: 浏览器会在指定过期时间内使用本地缓存例如: Expires: Tue, 08 Feb 2022 11:35:14 GMT
Vary作用:例如: Vary: Accept-Encoding
Cookie/Login 头域
P3P作用: 用于跨域设置Cookie, 这样可以解决iframe跨域访问cookie的问题.例如: P3P: CP=CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR
Set-Cookie作用: 非常重要的header, 用于把cookie 发送到客户端浏览器, 每一个写入cookie都会生成一个Set-Cookie.例如: Set-Cookie: sc=4c31523a; path=/; domain=.acookie.taobao.com
Entity头域
ETag作用: 和If-None-Match 配合使用。 (实例请看上节中If-None-Match的实例).例如: ETag: "03f2b33c0bfcc1:0"
Last-Modified:作用: 用于指示资源的最后修改日期和时间。(实例请看上节的If-Modified-Since的实例).例如: Last-Modified: Wed, 21 Dec 2011 09:09:10 GMT
Content-Type作用:WEB服务器告诉浏览器自己响应的对象的类型和字符集,例如:Content-Type: text/html; charset=utf-8; Content-Type:text/html;charset=GB2312; Content-Type: image/jpeg
Content-Length作用:指明实体正文的长度,以字节方式存储的十进制数字来表示。在数据下行的过程中,Content-Length的方式要预先在服务器中缓存所有数据,然后所有数据再一股脑儿地发给客户端。例如: Content-Length: 19847
Content-Encoding作用:WEB服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip
Content-Language作用: WEB服务器告诉浏览器自己响应的对象的语言者。例如: Content-Language:da
Miscellaneous 头域
Server作用:指明HTTP服务器的软件信息。例如:Server: Microsoft-IIS/7.5
X-AspNet-Version作用:如果网站是用ASP.NET开发的,这个header用来表示ASP.NET的版本。例如: X-AspNet-Version: 4.0.30319
X-Powered-By作用:表示网站是用什么技术开发的例如: X-Powered-By: ASP.NET
Transport头域
Connection作用:例如: Connection: keep-alive 当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。例如: Connection: close 代表一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接。
Location头域
Location作用: 用于重定向一个新的位置, 包含新的URL地址。 实例请看304状态实例。
六.请求消息。
先看Request 消息的结构, Request 消息分为3部分,第一部分叫Request line, 第二部分叫Request header, 第三部分是body. header和body之间有个空行, 结构如下图:
第一行中的Method表示请求方法,比如"POST","GET", Path-to-resoure表示请求的资源, Http/version-number 表示HTTP协议的版本号.当使用的是"GET" 方法的时候, body是为空的,比如我们打开博客园首页的request 如下:
GET http://www.cnblogs.com/ HTTP/1.1
Host: www.cnblogs.com
抽象的东西,难以理解,老感觉是虚的, 所谓眼见为实, 实际见到的东西,我们才能理解和记忆。 我们今天用Fiddler,实际的看看Request和Response.下面我们打开Fiddler 捕捉一个博客园登录的Request 然后分析下它的结构, 在Inspectors tab下以Raw的方式可以看到完整的Request的消息, 如下图:
七.响应消息。
我们再看Response消息的结构, 和Request消息的结构基本一样。 同样也分为三部分,第一部分叫Response line, 第二部分叫Response header,第三部分是body. header和body之间也有个空行, 结构如下图:
HTTP/version-number表示HTTP协议的版本号, status-code 和message 请看下节[状态代码]的详细解释.我们用Fiddler 捕捉一个博客园首页的Response然后分析下它的结构, 在Inspectors tab下以Raw的方式可以看到完整的Response的消息, 如下图:
八.状态码。
Response 消息中的第一行叫做状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。状态码用来告诉HTTP客户端,HTTP服务器是否产生了预期的Response.HTTP/1.1中定义了5类状态码, 状态码由三位数字组成,第一个数字定义了响应的类别:
1XX 提示信息 - 表示请求已被成功接收,继续处理;
2XX 成功 - 表示请求已被成功接收,理解,接受;
3XX 重定向 - 要完成请求必须进行更进一步的处理;
4XX 客户端错误 - 请求有语法错误或请求无法实现;
5XX 服务器端错误 - 服务器未能实现合法的请求;
看看一些常见的状态码:
200 OK 最常见的就是成功响应状态码200了, 这表明该请求被成功地完成,所请求的资源发送回客户端如下图, 打开博客园首页:
302 Found 重定向,新的URL会在response 中的Location中返回,浏览器将会自动使用新的URL发出新的Request,例如在IE中输入, http://www.google.com. HTTP服务器会返回302, IE取到Response中Location header的新URL, 又重新发送了一个Request.
304 Not Modified 代表上次的文档已经被缓存了, 还可以继续使用,例如打开博客园首页, 发现很多Response 的status code 都是304
提示: 如果你不想使用本地缓存可以用Ctrl+F5 强制刷新页面
400 Bad Request 客户端请求与语法错误,不能被服务器所理解;
403 Forbidden 服务器收到请求,但是拒绝提供服务;
404 Not Found;
请求资源不存在(输错了URL)比如在IE中输入一个错误的URL, http://www.cnblogs.com/tesdf.aspx;
500 Internal Server Error 服务器发生了不可预期的错误
503 Server Unavailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常
想要了解更多报错信息,请参考:https://baike.baidu.com/item/http/243074?fr=aladdin&fromid=1276942&fromtitle=HTTP%E5%8D%8F%E8%AE%AE。
九.HTTP协议是无状态的和Connection: keep-alive的区别。
无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。
从另一方面讲,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系.HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使用的是UDP协议(无连接).从HTTP/1.1起,默认都开启了Keep-Alive,保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接.Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间.
以上内容大部分来自网络,但是以上的内容并不是我本篇博客的重点,想要了解更多请参考可以自行百度,我这里给大家推荐几个网友写的不错的链接:
链接一:https://baike.baidu.com/item/http/243074?fr=aladdin&fromid=1276942&fromtitle=HTTP%E5%8D%8F%E8%AE%AE
链接二:http://www.cnblogs.com/ranyonsue/p/5984001.html
链接三:http://www.cnblogs.com/TankXiao/archive/2012/02/13/2342672.html#statecode
以上是关于HTTP协议的一个扫盲,接下来我们的部分我们就放弃说关于HTTP协议的介绍,因为它并不是我们的主角,我们的主角是用一种协议来做代理服务器,用于国内的用户*,就是突然之间对HTTP很感兴趣,就在网络上查阅一下资料对自己的知识库进行一个扫盲。好了,下面就进入我们的主菜,如果对HTTP和SOCKS5协议都是相当了解和熟悉的小伙伴可以直接越过关于SOCKS5的协议的介绍了,直接去最后看用Golang实现的关于SOCKS5协议*的代码吧~由于Golang是基于socks5协议实现的代理,那么我建议还是多少了解一下socks5协议还是相当有好处的,接下来就跟我一起进行socks5协议的探索之旅吧!
十.什么是SOCKS5协议。
SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。SOCKS是"SOCKetS"的缩写[1]。
当防火墙后的客户端要访问外部的服务器时,就跟SOCKS代理服务器连接。这个代理服务器控制客户端访问外网的资格,允许的话,就将客户端的请求发往外部的服务器。这个协议最初由David Koblas开发,而后由NEC的Ying-Da Lee将其扩展到版本4。最新协议是版本5,与前一版本相比,增加支持UDP、验证,以及IPv6。根据OSI模型,SOCKS是会话层的协议,位于表示层与传输层之间。
十一.与HTTP代理的对比。
SOCKS工作在比HTTP代理更低的层次:SOCKS使用握手协议来通知代理软件其客户端试图进行的连接SOCKS,然后尽可能透明地进行操作,而常规代理可能会解释和重写报头(例如,使用另一种底层协议,例如FTP;然而,HTTP代理只是将HTTP请求转发到所需的HTTP服务器)。虽然HTTP代理有不同的使用模式,CONNECT方法允许转发TCP连接;然而,SOCKS代理还可以转发UDP流量和反向代理,而HTTP代理不能。HTTP代理通常更了解HTTP协议,执行更高层次的过滤(虽然通常只用于GET和POST方法,而不用于CONNECT方法)。
SOCKS
Bill希望通过互联网与Jane沟通,但他们的网络之间存在一个防火墙,Bill不能直接与Jane沟通。所以,Bill连接到他的网络上的SOCKS代理,告知它他想要与Jane创建连接;SOCKS代理打开一个能穿过防火墙的连接,并促进Bill和Jane之间的通信。
有关SOCKS协议的技术细节的更多信息,请参阅下面的部分。
HTTP
Bill希望从Jane的Web服务器下载一个网页。Bill不能直接连接到Jane的服务器,因为在他的网络上设置了防火墙。为了与该服务器通信,Bill连接到其网络的HTTP代理。他的网页浏览器与代理通信的方式与他直接连接Jane的服务器的方式相同;也就是说,网页浏览器会发送一个标准的HTTP请求头。HTTP代理连接到Jane的服务器,然后将Jane的服务器返回的任何数据传回Bill。
十二.SOCKS的版本分支。
1.SOCKS 4
下面是客户端向SOCKS 4代理服务器,发送的连接请求包的格式(以字节为单位):
VN | CD | DSTPORT | DSTIP | USERID | NULL |
1 | 1 | 2 | 4 | variable | 1 |
- VN是SOCK版本,应该是4;
- CD是SOCK的命令码,1表示CONNECT请求,2表示BIND请求;
- DSTPORT表示目的主机的端口;
- DSTIP指目的主机的IP地址;
- NULL是0;
代理服务器而后发送回应包(以字节为单位):
VN | CD | DSTPORT | DSTIP |
1 | 1 | 2 | 4 |
- VN是回应码的版本,应该是0;
- CD是代理服务器答复,有几种可能:
-
- 90,请求得到允许;
- 91,请求被拒绝或失败;
- 92,由于SOCKS服务器无法连接到客户端的identd(一个验证身份的进程),请求被拒绝;
- 93,由于客户端程序与identd报告的用户身份不同,连接被拒绝。
- DSTPORT与DSTIP与请求包中的内容相同,但被忽略。
如果请求被拒绝,SOCKS服务器马上与客户端断开连接;如果请求被允许,代理服务器就充当客户端与目的主机之间进行双向传递,对客户端而言,就如同直接在与目的主机相连。
2.SOCKS4a
SOCKS 4A是SOCKS 4协议的简单扩展,允许客户端对无法解析的目的主机,进行自行规定。
客户端对DSTIP的头三个字节设定为NULL,最后一个字节为非零;对应的IP地址就是0.0.0.x,其中x是非零,这当然不可能是目的主机的地址,这样即使客户端可以解析域名,对此也不会发生冲突。USERID以紧跟的NULL字节作结尾,客户端必须发送目的主机的域名,并以另一个NULL字节作结尾。CONNECT和BIND请求的时候,都要按照这种格式(以字节为单位):
VN | CD | DSTPORT | DSTIP 0.0.0.x | USERID | NULL | HOSTNAME | NULL |
1 | 1 | 2 | 4 | variable | 1 | variable | 1 |
使用4a协议的服务器必须检查请求包里的DSTIP字段,如果表示地址0.0.0.x,x是非零结尾,那么服务器就得读取客户端所发包中的域名字段,然后服务器就得解析这个域名,可以的话,对目的主机进行连接。
3.SOCKS5
SOCKS5比SOCKS4a多了鉴定、IPv6、UDP支持。创建与SOCKS5服务器的TCP连接后客户端需要先发送请求来协商版本及认证方式,格式为(以字节为单位):
VER | NMETHODS | METHODS |
1 | 1 | 1-255 |
- VER是SOCKS版本,这里应该是0x05;
- NMETHODS是METHODS部分的长度;
- METHODS是客户端支持的认证方式列表,每个方法占1字节。当前的定义是:
-
- 0x00 不需要认证
- 0x01 GSSAPI
- 0x02 用户名、密码认证
- 0x03 - 0x7F由IANA分配(保留)
- 0x80 - 0xFE为私人方法保留
- 0xFF 无可接受的方法
服务器从客户端提供的方法中选择一个并通过以下消息通知客户端(以字节为单位):
VER | METHOD |
1 | 1 |
- VER是SOCKS版本,这里应该是0x05;
- METHOD是服务端选中的方法。如果返回0xFF表示没有一个认证方法被选中,客户端需要关闭连接。
之后客户端和服务端根据选定的认证方式执行对应的认证。认证结束后客户端就可以发送请求信息。如果认证方法有特殊封装要求,请求必须按照方法所定义的方式进行封装。
SOCKS5请求格式(以字节为单位):
VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
1 | 1 | 0x00 | 1 | 动态 | 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 网络字节序表示的目的端口
服务器按以下格式回应客户端的请求(以字节为单位):
VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
1 | 1 | 0x00 | 1 | 动态 | 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 用户名密码认证方式
在客户端、服务端协商使用用户名密码认证后,客户端发出用户名密码,格式为(以字节为单位):
鉴定协议版本 | 用户名长度 | 用户名 | 密码长度 | 密码 |
1 | 1 | 动态 | 1 | 动态 |
鉴定协议版本目前为 0x01 。
服务器鉴定后发出如下回应:
鉴定协议版本 | 鉴定状态 |
1 | 1 |
其中鉴定状态 0x00 表示成功,0x01 表示失败。
十三.用SOCKS5实现proxy功能。
以上关于SOCKS协议的介绍均来自*连接为:https://zh.wikipedia.org/wiki/SOCKS。知道这个协议的工作原理,那么我们看关于用Golang实现的代理就会很简单了,具体代码如下:
/*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"net"
"flag"
"log"
"bufio"
"errors"
"io"
"encoding/binary"
"fmt"
"sync"
) /*
1.了解什么是socks5协议;
2.握手
3.获取客户端代理的请求
4.开始代理
*/
func Hand_shake(r *bufio.Reader, conn net.Conn) error {
versiom,_ := r.ReadByte() //用“*bufio.Reader”的“ReadByte”方法读取一个字节,即socks的版本号
log.Printf("版本号是:%d",versiom) //解析版本
if versiom != 5 {
return errors.New("该协议不是socks5协议")
}
nmethods,_ := r.ReadByte() //nmethods是记录methods的长度的。nmethods的长度是1个字节。methods表示客户端支持的验证方式,可以有多种,他的尝试是1-255个字节。
log.Printf("METHODS长度是:%d",nmethods) buf := make([]byte,nmethods)
io.ReadFull(r,buf) //这个方法和“io.Copy”效果是看起来很相反,“io.ReadFull”循环读取“r”的数据并依次写入到“buf”中,直到吧“buf”写满为止。
log.Printf("验证方式为:%v",buf) /*常见的几种方式如下::
1>.数字“0”:表示不需要用户名或者密码验证;,
2>.数字“1”:GSSAPI是SSH支持的一种验证方式;
3>.数字“2”:表示需要用户名和密码进行验证;
4>.数字“3”至“7F”:表示用于IANA 分配(IANA ASSIGNED)
5>.数字“80”至“FE”表示私人方法保留(RESERVED FOR PRIVATE METHODS)
4>.数字“FF”:不支持所有的验证方式,这样的话就无法进行连接啦! */ resp :=[]byte{5,0} //以上操作实现了接受客户端消息,所以服务器需要回应客户端消息。第一个参数表示版本号为5,即socks5协议,第二个参数表示认证方式为0,即无需密码访问。
conn.Write(resp)
return nil
} func Read_Addr(r *bufio.Reader) (string ,error) {
version,_ := r.ReadByte() //读取一个字节,获取Socks协议的版本,Socks5默认为0x05,其值长度为1个字节。
log.Printf("客户端协议版本:%d",version)
if version != 5 {
return "",errors.New("该协议不是socks5协议")
}
cmd ,_ := r.ReadByte() /*从上一次读取的位置再往下读取一个字节。cmd代表客户端请求的类型,值长度也是1个字节,
有三种类型:
1>.数字“1”:表示客户端需要你帮忙代理连接,即CONNECT ;
2>.数字“2”:表示让你代理服务器,帮他建立端口,即BIND ;
3>.数字“3”:表示UDP连接请求用来建立一个在UDP延迟过程中操作UDP数据报的连接,即UDP ASSOCIATE;
*/
log.Printf("客户端请求的类型是:%d",cmd)
if cmd != 1 { //此处表示我们只处理客户端请求类型为“1”的连接。
return "",errors.New("客户端请求类型不为“1”,即请求类型必须是代理连接!.")
} r.ReadByte() //跳过RSV字段,即RSV保留字端,值长度为1个字节。 addrtype,_ := r.ReadByte()
log.Printf("客户端请求的远程服务器地址类型是:%d",addrtype) /*“addrtype”代表请求的远程服务器地址类型,它是一个可变参数,但是它值的长度1个字节,
有三种类型:
1>.数字“1”:表示是一个IPV4地址(IP V4 address);
2>.数字“3”:表示是一个域名(DOMAINNAME);
3>.数字“4”:表示是一个IPV6地址(IP V6 address);
*/
if addrtype != 3 { //表示只处理请求的远程服务器地址类型是域名的。
return "",errors.New("请求的远程服务器地址类型部位“3”,即请求的远程服务器必须地址必须是域名!")
} addrlen,_ := r.ReadByte() //读取一个字节以得到域名的长度。因为服务器地址类型的长度就是“1”,所以它是IP还是域名我们都能获取到完整的内容。如果能走到这一行代码说明一定是域名,如果没有上面的一行过滤代码我们就还需要考虑IPV4和IPV6的两种情况啦!
addr := make([]byte,addrlen) //定义一个和域名长度一样大小的容器。
io.ReadFull(r,addr) //将域名的内容读取出来。
log.Printf("域名为:%s",addr) var port int16 //因为端口是有2个字节来表示的,所以我们用int16来定义它的取值范围就OK。
binary.Read(r,binary.BigEndian,&port) //读取2个字节,并将读取到的内容赋值给port变量。 return fmt.Sprintf("%s:%d",addr,port),nil } func handle_conn(conn net.Conn) {
defer conn.Close()
r := bufio.NewReader(conn) //把“conn”进行包装,这样方便我们处理“conn”的数据。
Hand_shake(r,conn) //进行握手,该函数是建立服务端和客户端的连接,但是仅仅建立握手并没有什么卵用,只是服务器收到了客户端的请求,我们还需要继续往下走。
addr,err := Read_Addr(r) //获取客户端代理的请求,即让客户端发起请求,告诉Socks服务端客户端需要访问哪个远程服务器,其中包含,远程服务器的地址和端口,地址可以是IP4,IP6,也可以是域名。
if err != nil {
log.Print(err)
}
log.Print("得到的完整的地址是:",addr) //注意:HTTP对应的是80端口,HTTPS对应的是443端口。
resp := []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} //详情请参考:http://www.cnblogs.com/yinzhengjie/p/7357860.html
conn.Write(resp) //现在客户端把要请求的远程服务器的信息都告诉Socks5代理服务器了,那么Socks5代理服务器就可以和远程服务器建立连接了,不管连接是否成功等,都要给客户端回应。 //实现代理部分需要字节填充。首先你得会用switchyomega软件来调试上面的代码。
var (
remote net.Conn //定义远端的服务器连接。
) remote,err = net.Dial("tcp",addr) //建立到目标服务器的连接。
if err != nil {
log.Print(err)
conn.Close()
return
} wg := new(sync.WaitGroup)
wg.Add(2) go func() {
defer wg.Done()
io.Copy(remote,r) //读取原地址请求(conn),然后将读取到的数据发送给目标主机。这里建议用"r",不建议用conn哟!因为它有重传机制!
remote.Close()
}() go func() {
defer conn.Close()
io.Copy(conn,remote) //与上面相反,就是讲目标主机的数据返回给客户端。
conn.Close()
}()
wg.Wait() } func main() {
flag.Parse()
listener,err := net.Listen("tcp",":8888")
if err != nil {
log.Fatal(err)
}
for {
conn,err := listener.Accept()
if err != nil {
log.Fatal(err)
}
go handle_conn(conn)
}
}
以上代码可以直接放在内网上做proxy使用,但是一些学习网络的小伙伴又会提出这个软件安全吗?存在哪些问题呢?在这里,我不得不说明一下,的确是存在安全隐患的,数据是没有被加密的,很容易出现中间人攻击的情况,以上代码存在3点问题:
1>.客户端的请求数据未经过加密处理,安全性能很低;
2>.错误处理没有考虑完整,比如:上面的代码只处理域名的请求;
3>.这个代码放在服务器端运行时会有大量的输出字符,这样会对服务性能会有一些影响的,应该尽量减少这样的输入,我之所以那样写是为了说明代码的作用,熟练Golang的小伙伴可以自行更改。
关于如何解决安全性的问题,请点击:http://www.cnblogs.com/yinzhengjie/p/7368030.html