记一次带宽扩容
最近遇到一个问题:需要使用远程桌面连回到家里的电脑上打游戏,但是带宽不够。
我家带宽的情况:杭州电信,一年500多RMB,下行100M,上行30M。
30M的上行带宽,在大部分场景下还是比较流畅的,但是会间歇性卡顿。之后尝试了电信的增值服务,7RMB可以1天提升到带宽:上行200M,下行50M。
经过测试,在这种情况下,远程桌面连回家里,打游戏一点也不卡,非常流畅,玩些大型游戏也是没问题的。
所以能大致得出结论,网络会间歇性卡顿是偶尔需要传输40-50M左右的流量(起码50M的上行是一点也不卡)。如果我氪金的话,一年需要7*30*12=2520RMB
,太贵了。
所以考虑其他方案,然后发现电信的宽带可以进行多拨,电信的宽带是有给公网ip的,并且我装宽带的时候已经让设置桥接了。我家用的是软路由+OpenWrt,所以还是比较容易设置多线多拨的。
多线多拨 第一步 在正常设置的情况下,在添加一个接口,这个接口绑定另一张跟猫连接的网卡。然后设置拨号上网,填上你的账号密码。然后改一个跃点数就行了,跃点数越高,优先级越低。
第二步 这步的前提是,你已经有两个网口成功拨好上网,有两个公网IP了。然后就是使用mwan3设置负载均衡,这一步有两个需要注意的地方:
接口名字大小写敏感
如果配置好了,发现还是有问题,可以尝试把mwan3的配置都删了,重新设置一遍。
负载均衡配置好以后,上行带宽已经成功提升到60M了,不过下行的仍然是100M不变。
多WAN口,单服务负载均衡 按上面的步骤做到负载均衡以后,仍然没能达到我的目的,因为这种负载均衡是多线程的负载均衡。第一次请求走WAN1口,第二次走WAN2口。
但是远程桌面是监听单个端口,等待连接进来,连接进来以后,就都是用进来的那个网口进行通信了。
拓扑图懒得画了,自己脑中勾勒吧,左边有一台客户端A连接到中间的路由器B,路由器有两个WAN口,客户端A可以连接任意一个WAN口,然后路由器的右边LAN口,连接着服务器C。
我的需求是:
客户端A发起请求,数据包先发送到路由器B的其中一个网卡,然后做端口转发到服务器C,C接收到数据包后需要返回数据,比如返回的数据有20M,我希望有10M走路由器B的WAN1口,另外10M走WAN2口。
然后使用iptables+自己写代码进行研究测试,发现即使是UDP协议,仍然没法做到。举个例子:
客户端A,往路由器B的WAN1口(192.168.1.1)的8888端口发送UDP流量
客户端只能接收到192.168.1.1:8888端口发送来的UDP流量
因为有运营商的限制,没办法把WAN2口(192.168.1.2)流量的源IP改成192.168.1.1
在客户端上把接收到的源IP为:192.168.1.2的包改为192.168.1.1的包可以实现,但是这个时候却没办法控制源端口了,这个使用源端口不再是8888,感觉这也是运营商那做了限制。
还研究了一些代理,也都没办法把单个链接中的多个数据包从多个网口发出去。最后,我尝试自己用python写了个代理,实现了我的需求。
首先是需要在路由器B上运行一个服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 from threading import Threadfrom queue import Queueimport socketimport randomimport syssSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sSock.setsockopt(socket.SOL_SOCKET, 25 , b"pppoe-wan\0" ) sSock_b = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sSock_b.setsockopt(socket.SOL_SOCKET, 25 , b"pppoe-WAN2\0" ) cSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sSock.bind(("wan1 ip" , xxxxx)) sSock_b.bind(("wan2 ip" , xxxxx)) cSock.bind(("路由器B lan口IP" , 随便一个端口)) if len (sys.argv) == 3 : target = (sys.argv[1 ], int (sys.argv[2 ])) else : target = ("远程桌面IP" , 远程桌面端口) remote_client = {} sendSock = [sSock, sSock_b] receive_data = Queue() send_data = Queue() echo_data = Queue() def server_rv (s ): echo_data.put(b"start server_rv" ) global remote_client while True : data = s.recvfrom(10240 ) echo_data.put(b"server_rv get %s" %data[0 ]) if s not in remote_client: remote_client[s] = data[1 ] receive_data.put(data[0 ]) def server_sd (sendSock ): echo_data.put(b"start server_sd" ) while True : data = send_data.get() if not remote_client: continue echo_data.put(b"server_sd send %s" %data) while True : s = random.choice(sendSock) if s in remote_client: break s.sendto(data, remote_client[s]) def client_rv (s ): echo_data.put(b"start client_rv" ) while True : data = s.recvfrom(10240 ) echo_data.put(b"client_rv get %s" %data[0 ]) send_data.put(data[0 ]) def client_sd (s ): echo_data.put(b"start client_sd" ) while True : data = receive_data.get() echo_data.put(b"client_sd send %s" %data) s.sendto(data, target) def echo (): while True : data = echo_data.get() def main (): task = [ Thread(target=server_rv, args=(sSock,)), Thread(target=server_rv, args=(sSock_b,)), Thread(target=server_sd, args=(sendSock,)), Thread(target=client_rv, args=(cSock,)), Thread(target=client_sd, args=(cSock,)) ] for t in task: t.start() echo() main()
服务端的逻辑是:
在不同的WAN口上,简单同一个端口
接受到的数据转发给远程桌面,这就相当于是路由器B在请求远程桌面。
接收到的请求,随机从一个网口发回去。
不通网口发回的流量,我们的客户端只能接收到一个网卡的,所以需要一个配套的客户端代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 from threading import Threadfrom queue import Queueimport socketimport randomsSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) cSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sSock.bind(("0.0.0.0" , xxxxx)) cSock.bind(("0.0.0.0" , 远程桌面服务端口)) target = [("WAN1 IP" , port), ("183.158.53.95" , port)] c_target = None receive_data = Queue() send_data = Queue() echo_data = Queue() def server_rv (s ): echo_data.put(b"start server_rv" ) while True : data = s.recvfrom(10240 ) echo_data.put(b"server_rv get %s" %data[0 ]) receive_data.put(data[0 ]) def server_sd (sendSock ): echo_data.put(b"start server_sd" ) while True : data = send_data.get() echo_data.put(b"server_sd send %s" %data) t = random.choice(target) sendSock.sendto(data, t) def client_rv (s ): global c_target echo_data.put(b"start client_rv" ) while True : data = s.recvfrom(10240 ) if not c_target: c_target = data[1 ] echo_data.put(b"client_rv get %s" %data[0 ]) send_data.put(data[0 ]) def client_sd (s ): echo_data.put(b"start client_sd" ) while True : data = receive_data.get() if not c_target: continue echo_data.put(b"client_sd send %s" %data) s.sendto(data, c_target) def echo (): while True : data = echo_data.get() def main (): task = [ Thread(target=server_rv, args=(sSock,)), Thread(target=server_sd, args=(sSock,)), Thread(target=client_rv, args=(cSock,)), Thread(target=client_sd, args=(cSock,)) ] for t in task: t.start() echo() main()
客户端脚本一般放在客户端A上运行,逻辑如下:
在本地监听一个跟远程桌面相同的端口,相当于是一个伪远程桌面服务。
客户端A上的远程桌面连接到该服务上,把流量随机转发到路由器B的其中一个WAN口上。
接收到路由器B返回的流量,全都转发给客户端A。
其他 上述脚本经过测试,能成功扩容上行带宽,就算是python写的,性能也不会很差。不过有空的时候,可以尝试有C或者Golang写一个,到时候可以对比一下性能。