# rProxy3 **Repository Path**: EasyWord/r-proxy3 ## Basic Information - **Project Name**: rProxy3 - **Description**: No description available - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2020-11-02 - **Last Updated**: 2023-03-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 介绍 算是一个比较特殊的反向代理程序吧,frp项目带给我的启发,好处是不用配置就能直接实现http/https反向代理 服务端在客户端链接之后,会监听一个端口,例如7202端口,此时任何对7202发出的https代理请求都会被转发给客户端,由客户端作为出口发出请求 大概工作流程如下 (main.py开始) ![image-20201107190554192](Readme.assets/image-20201107190554192.png) 简单来说,就是使用 `rProxyServer.py`和`rProxy.exe`搭建了一个https代理隧道 意义是什么?它可以将一个http/https请求从某个局域网发送出来。实现以下两个功能 + 通过公网访问内网 (还能做更多的事情) + 使多个客户端成为爬虫的代理服务器,这样就不用担心代理IP不够用了 + ··· # 使用项目 ## https服务器 执行`Debug工具/https/https_server.py` 文件,如果成功 就在本地8080端口搭建了一个https服务器 浏览器访问`https://127.0.0.1:8080` 可以看到一个全是1的界面 表示搭建正常 ## 服务端 接下来执行 `rProxyServer/rProxyServer.py` 启动服务端,提示在监听状态之后,程序会监听7201端口 ## 客户端 使用VS2019 打开`rProxy3.sln` 工程文件,编译运行`rProxy3` 项目 运行之后程序会链接服务端 ## 准备就绪,启动测试 当链接正常之后,服务端会提示已经在监听7202端口了 此时,运行`rProxyServer/main.py`程序,程序会通过 7202端口的代理对 `https://127.0.0.1:8080` 发出https请求 **注意: main.py中一次开了10个线程请求,如有必要注释掉启用线程的行即可** # TODO + ~~目前还在修复bug,首个版本甚至不能正常运行... (;´д`)ゞ~~ + ~~记得清理断开之后Client的Mess~~ + ~~之后验证一下vector push的到底是什么。。。erase的是指针还是内存~~ + ~~客户端断开后,服务端应该停止对应的端口监听~~ + 补足http协议的支持,, 才看到没有实现http的代理 + 接下来规范一下代码,补充一些注释 + 完成客户端选择功能,使得可以指定客户端实现指定出口IP + # BUG + ~~当请求几十次之后,socket() 函数异常~~ + ~~服务端会将6字节的数据包拆分成 4+2,以至于http服务器主动断开连接~~ + ~~请求200多次之后请求端大概率出错 (目前猜测可能是某队列没有清理导致的)~~ + ~~有概率ssl错误~~ # Log + 通过本地搭建https服务器测试发现,请求公网https成功的原因仅仅是因为响应比较慢。。。 + 并不是因为慢。。。 经过观察发现,是因为requests库请求了不被信任的证书会失败。需要为 get方法添加参数 `verify=False` 也即是 ```Python r = rq.get(url,verify=False) ``` 如此就不验证证书是否合法了 一开始以为是Python 搭建的https服务器不正确,后来发现使用别的方式搭建https也不行,仔细看了错误提示是证书错误 ψ(._. )>。。。 + 下一个问题,经过多次请求之后,代码会错在 ```C++ SOCKET http_socket = socket(AF_INET, SOCK_STREAM, 0); ``` 这行,错误提示是 > 0x00007FFE824866DB (ws2_32.dll)处(位于 rProxy3.exe 中)引发的异常: 0xC0000005: 读取位置 0xFFFFFFFFFFFFFFFF 时发生访问冲突。 实在看不懂,是读取哪里有问题,为什么会读取到错误的内存,明明参数都是常量。。。 所以,可能是socket函数的问题?通过反汇编如下 ``` mov qword ptr [http_socket],rax ``` 可以知道是取返回值的时候出了问题,但是这又能如何呢? 这种问题大概率是多线程的问题吧。。。但是多线程怎么会造成这种问题呢 + 发现了一个规律,每次都是在处理第25个请求的时候,出错。。。那可能是哪里逻辑错误,,,试试修改index.html大小,看看和sid有关还是和Mess队列有关 + 修改了index.html之后,出现错误的请求位置不一样了,有时候是sid=18 有时 sid=24 似乎是不规则的,也许可以换换思路,例如从编译器的警告开始,感觉似乎和变量访问越界有些关系 + 警告并没有给我重要的线索,也许是socket函数并不是线程安全的?那我写个代码试试多次调用socket()函数会不会复现这样的问题呢? + 确认了不会出现这样的问题,害 库函数错问题的概率肯定很小啊,尤其是这样的函数。一定会考虑多次调用什么的。 + 看起来只能再检查一下代码了,但是重点是什么呢?还是对代码流程的检查吧,注意代码执行流程。真的是毫无头绪啊 (悲) + 出现了新的问题,main.py发出的6次请求 有时候会卡住,出现错误 `SSL: DECRYPTION_FAILED_OR_BAD_RECORD_MAC` 也许是一个解决问题的突破口,复现概率比较大,但不清楚和socket()异常是不是同一个问题,需要分析日志看看。 + 初步日志上没有发现什么线索,似乎所有包都转发了,明天看看是否几个请求的数据包都齐全 + 六个请求 两个不正常,第一个第一个包收包错误,第六个最后一个包出错了,,,竟然如此垃圾的代码 + 宣告失败了,准备先实现一个简单一点的,也就是实现单个的反向代理 + 将来会再次尝试的 + 就是现在,继续~ + 会出现一种情况,当http主动断开连接的时候,服务端和请求端不会断开,导致请求端一直阻塞,虽然这个问题应该由请求端处理,比如说加超时什么的。。。但是我觉得有必要解决。不过为什么http会主动断开连接呢?? 10054 + 发现 有一个请求对http发送了一个2字节的数据,也许是主动断开的原因。而这两个字节是请求端交给服务端的,为什么我就不知道了。没有理由会分两次给服务端发6字节的数据。。。 + 那么我先解决主动断开的问题吧。 + 现在主动断开会同事对面,让对面也断开 + 现在解决,Server的Mess不安全问题 + 已经对Server的Mess加锁了,目前没有出现列表中找不到元素的事情了 + 现在还有一个问题,就是Client这边的socket()函数异常问题 + 总算解决了,最终还是借助了伟大网友的力量 @w2014 + 原因是因为,解包过程中对数据接收缓冲区`recv_buf`的指针做了加法操作,以至于后面调用recv的时候,对堆内存结构造成了破坏。导致种种问题。 + 说起来,这个bug的表象和根源关联性好低... 我确实考虑过是堆栈被破坏了。。。但是复查代码也没有找到问题。总之,还是经验不够,这种表象的bug现在记住了,大概率是堆栈问题,记得检查指针。 + 至于为什么前面的几个请求不出错,,,因为是堆的关系,会有很多空余的内存,而这个空余内存大小不确定,并不能确定什么时候会覆盖到重要的数据导致程序崩溃。。。 + 新的bug,有一定概率ssl错误,目前没有发现规律 + 对与断开验证,似乎没有正常工作。。。 + 经过验证,断开通知功能是正常工作的。。。 + 现在并行请求的时候,会卡住,但是不能确定是卡在哪里了,也不确定是谁的问题 + 目前通过日志来看,可能有两个原因,一个是http服务器出现问题,没有响应,另一个是HttpToServer线程挂了,导致不能转发。那么这里就出现一个问题,也即是超时问题。。。http长时间不断开怎么办。 + 随缘复现什么的最麻烦了 + 目前来看,http始终不回复。。。可以建立新的链接,但是不能得到响应。在这个状态的http服务器得到新的链接之后会主动断开 导致客户端recv()返回-1 但是根据个人经验来看,一般公共代码出现问题的可能性不大,,,一般来说还是自己的问题。但是不重启http服务器的情况下,重启客户端和服务端就能回复正常。这么看的话,可能还是我客户端的问题。稍后再看看那个HttpToServer线程吧,也许是哪里的什么地方阻塞了,导致recv失败。会不会是recv缓冲区满了呢?额,那只会导致发不动才对。总之等会看看那个线程再说吧 + 给浏览器代理能够正常使用了,但是又会出现别的bug。GetPacketLen()函数获取的len不正确 + 新的bug,如果不是gflags我根本注意不到,也即是在解包的时候,因为粘包的关系,记录了实际包长。但是在解包过程中发现实际包长超过了缓冲区大小。。。 例如某次的buf - init_buf_p 结果是3970,而正在解析的包大小是211,加起来是4181,超过了缓冲区4096的大小,此时运行memcpy就会出现访问越界的情况,此时得到的内容几乎一定是错误的,所以会出现ssl异常也不足为奇了。 + 只是现在,为啥会出现这样的问题呀。。。 + 大概想明白了。在recv的缓冲区中,数据包应该是正确的,但是我的recv每次都只取4096字节,此时就会发生一个问题,也即是recv会把其中一个包拆分了,也就是发生异常的时候,接受到的是某个包的前半段,但是根据目前简单的公式计算会计算出超过4096的数值,以至于出现问题。所以,接下来要做的就是应对分包的问题。 + 一个思路,当发现是不完整包的时候,就再临时调用一次recv,把剩下的半个包接受了。 + 因为包有包头的关系,所以不能直接送入解析循环,这样会导致坏包的出现(因为后半部分的包没有包头) + 虽然知道了错误原因 但是并没有解决掉,还是得看明天。。。又出现了点别的问题,莫名奇妙的和服务端断开了。。 + 现在的问题是,锁的竞争,主要是MEss锁,每个线程都需要遍历,但是Mess添加进程也需要锁。。。 这就意味着,主线程要和很多的子线程竞争,所以会导致接受很慢。所以,按照目前的架构来看,就剩下一个解决方法了,就是减少子线程的资源占用时间,现在看来,就是http处理方法占用了太多的时间。就是不知道具体是哪个占用的。。。不过没有关系,,, 反正Session线程是每个http请求一个线程,只要不太长时间占用锁就好了。 + 所以,现在就要把http处理函数移除遍历循环。要卡也别卡主线程。。。 + 现在解决了锁的竞争问题,现在的子线程获取锁之后应该不会阻塞了,顺便就明白了为什么说重入锁比较好了。 + 好的 现在开始解决为什么有时候解析出来的包大小是4096+4。这个是最让我一头雾水的,因为似乎他根本没有根据顺序执行。 + 找到原因了 就是RecvFromServer方法中执行了Unpack()方法,导致哪里被执行了。。。。 干。。。 + 确实是那个问题,现在已经不会崩溃了。但是还是有问题。 + 会出现很多的ssl错误,导致请求失败。打开一个网页都得看脸。。 接下来主要工作就是解决这个问题了。 + 感觉这个问题很有可能是服务端的问题吧,需要重新复习以下服务端了 + 服务端接收时很有可能会按照如下方式接收 ``` [*] <-- 请求端 33 [1] [*] len: b'CONNECT localhost:8081 HTTP/1.0\r\n' [33] [*] --> 客户端 37 [1] [*] <-- 请求端 2 [1] [*] len: b'\r\n' [2] ``` 也即是,把原本的字符串拆分成了两个 + 通过修改`host文件`,将`localhost`变为`localhost123`,发现依旧是后面`\r\n`被分开了。所以,为啥啊。。。 + 测试之后发现 直接接收也会出现这种问题 ``` [+] 监听: 127.0.0.1:7202... [36] b'CONNECT localhost123:8081 HTTP/1.0\r\n' [2] b'\r\n' ``` 由此可见,应该是Python的问题? + 如果是直接通过火狐浏览器请求,一下子请求了300多次,都没有出现那样的情况,是否是发送方的问题?好吧,验证了,确实是发送方的问题,可能是实现的有毛病。 + 那就是其他问题了。 + 会得到10038的错误,这个连接不上没问题,但是为啥`if(-2 < 0)`不成立啊。。。 + 然后就是明明传输数据好好的,但是怎么突然就断开了呢。 + emmm 感觉数据包顺序还是可能乱序,导致过不了ssl认证 + 明天验证代理直接转发 。。 成了直接重写。 这代码设计的也有问题。。。即使成功了,效率也不咋地。。。 + 这也许是最后一次push这个项目 + 2021年1月撤回上面的话,再次开始了开发过程。 + 这次直接使用协程重写开发服务端,使用协程的话,就不需要考虑很多的线程安全的问题了,,,虽然,还是需要考虑。。。但是,不用那么夸张。并且,据说协程比线程效率高(实际测试翻车了,暂时不清楚原因。大概是 threading线程库是Python原生库,而gevent是第三方库导致效率降低吧。实际比较是否使用第三方线程库更公平呢?) + 目前遇到的问题,就是我居然忘记考虑Python那边解决粘包的问题了。。。。一直以为Python是。。。 啊啊啊啊啊啊啊混蛋,说起来,发现这个bug真是巧合呢 + RecvClient收到的数据中data部分是211长度,但是,data_len值为39。此时,发生了什么 [泪奔],,, data_len是39比较靠谱,但是实际数据长度是211,这就意味着发生了粘包,原本的解包函数会把后面172字节的数据给抛弃了。emmm 直接丢失了东西怎么可能不出错呢。。。 + 然后就是,会出现不合法的session_id,这基本上是不可能的。可能是因为解包函数拿到的数据是不合法的,也就是包头错位了,但是什么时候会错位呢?假设和上面的问题有关。客户端发来数据,解包函数对粘包的数据包正常解包,得到正确的data块内容,但是后面的部分应该是被扔掉了。导致后面的丢包,但是下个数据包的头应该还是正常的才对呀。接下来就要测试,粘包的数据包出现之后是否会伴随着异常session_id + 测试不是很顺利,出现粘包问题之后直接就卡住了。明天继续 + 解决了第一个问题,然后第二个问题顺带消失了,现在进行测试并不会报错了。是否解决了问题呢?应该做一个实际的测试。 + 然后,对于上面 `b'\r\n'` ,正常来说,传输层的数据会被应用层重新组织,所以突然散开发送两个字节没有问题,至于为什么会出问题,原因可能是因为多出来的这两个字节打乱了程序解包的节奏。 + **现在应该能够按照要求正常工作了,虽然应该还有不少的坑,哈哈,但是毕竟已经实现了,接下来慢慢改进就可以啦,芜湖~~~** + 发现内存泄漏问题 + 调试内存泄露的时候,发现了惊人的事情,也就是 ProcessRequestThread线程会被执行两次,而且传进去的pd是一样的,这。。。很让人懵逼啊 + 尝试一下别的设计,重写请求处理线程,务必做到线程安全 + 客户端使用了新的设计,目前解决了内存泄漏的问题,但是现在的问题是有的线程并没有退出。目前还不知道是什么问题。不过现在似乎没有非常大的问题了。主要解决线程没有退出的问题,还有请求第1080个请求之后就卡死了,或者说早就卡死了。总共6个请求线程,在第1080次请求之后卡死,但是不清楚具体卡在哪里了。这个问题明天解决 + 线程现在有正常退出,至于卡死的问题,还不清楚是怎么回事,需要测试。经过测试,十个线程请求2300次之后,并没有出现大问题。首先是内存泄漏问题,2300次请求之后,内存比开始多了大约1M,其次是阻塞的请求线程,十个请求线程,最后只剩下了8个,在,掉线的两个请求线程并没有退出,似乎就是处于等待状态。可能和网络有些关系,具体使用不知道会不会出现问题。总之,可以进行一次实际测试了。 + 知道了那个 “/r/n”的问题了,实际上,在请求端发送的第一个数据包,是不会发送到服务器的。但是在分开了之后,会把 "/r/n"发给服务器,这样当然会显示版本号错误了。所以,一个治标不治本的办法。就是忽略长度特别小的值,但是很可能会误杀正常的数据