有关torrent的那些事 时间: 2018-12-28 21:27 分类: 技术笔记 想要自己实现一下bittorent tracker的功能。于是着手研究了一下bt协议。 采用的后端是Python+Flask。因为我对这这个框架运用比较熟悉了。当然这个框架意味着没有现成的项目可供参考。只能靠自己摸索了。 抓取客户端发来的请求,URL类似这样 ``` /announce?info_hash=%ba%e7……&peer_id=-UT2040-%86V……&port=18500&uploaded=0&downloaded=0&left=0&corrupt=0&key=E0000000&event=started&numwant=200&compact=1&no_peer_id=1&ip=xx.xx.xx.xx HTTP/1.1 ``` 根据文档,我们知道,客户端每次与tracker的通讯,是通过HTTP GET的方式请求的,也就是说请求内容都直接写在URL上。%xx就是url编码(encode)后的结果。 为了判断请求合法性,首先就是要检查各个参数是否正常。那么第一步就是获取这些参数的内容了。 我参考了国内用的最多的NexusPHP Tracker的逻辑,首先对参数完整性和关键参数是否正常进行检查。然而第一步就出现了问题。 采用Flask框架自带的GET参数解码request.args.get函数,解码出的info_hash是乱码的,而且连长度都不统一。我开始怀疑是编码的问题,各种转码但是都无法解决甚至报错。无奈翻阅文档,确定了这个hashinfo是只有数字字母的。最后去搜索hash_info的url的构建过程,找到了stackoverflow上面的[这篇文章](https://stackoverflow.com/questions/1855815/calculating-info-hash-from-urlencoded-query-string ),然后终于搞清楚了编解码的逻辑。 url中的infohash长这样 `%92%c345%c0%28%15%e4rr%b1y%17%b7%cbs%0a%ef%9a%fc` 如果我们把它像这样拆开,恰好有20组。 `%92` `%c3` `4` `5` `%c0` `%28` `%15` `%e4` `r` `r` `%b1` `y` `%17` `%b7` `%cb` `s` `%0a` `%ef` `%9a` `%fc` 实际上,这就是把HASHINFO的**40个字符拆成20组两位16进制数**。如果有一组**16进制数对应的ASCII码恰好能组成字符**的话,那么就直接**写出这个字符**,而**不再保留**百分号和两位16进制数。否则就加上去。 我不知道是**哪个天才**想出来的睿智算法,**想打人**。 想要decode其实很简单,就是把raw的请求内容直接作为完整输入字符串,读取到百分号就把百分号去掉,对应的两位16进制数字保留下来。而不是%00格式的部分,直接转换为16进制的ASCII码。这样得到的就是正确的40字符的infohash了。 接下来的参数peer_id也是这样, 本来是标记客户端的,在url中的格式是`-UT2040-%86V%3eC%0c%a2%e3%97JY%94e` 没错,前面是`杠` `客户端版本` `杠` 后面又是那**该死的编码** **解决方案:正则解析出这个部分,然后解码再合到一块。** 接下来丢出整块的代码 ```python def get_info_hash(full_path): """ Get raw info_hash from origin url""" result = re.search("info_hash=(.*?)&", full_path) if result: return result.group(1) else: return "" def get_peer_id(full_path): """ Get raw peer_id from origin url""" result = re.search("peer_id=(.*?)&", full_path) if result: return result.group(1) else: return "" def peer_id_decode(urlencoded_peerid): """ Decode raw peer_id and convert to origin 24 char peerid""" result = re.search("-(.*?)-(.*)", urlencoded_peerid) if result: version = result.group(1) feature = hash_decode(result.group(2)) return "-{}-{}".format(version, feature) else: return urlencoded_peerid def hash_decode(urlencoded_hash): """ Decode raw anything and convert to origin hash format. This fxxking encoding method, putting hashed char as a group of two-digit-hex-num, therefore, if one char group could be decoded as a hex number smaller than 128(in dec) or 80(in hex) which means max ascii code, it will be directly decoded as a normal char with that ascii code. Otherwise, just add a % and write the two char after it. So what we do here is to remove the % of "bigger than 128" char group and convert normal char back to two alphabet hex. """ hexval="" i=0 while(i 标签: 无