记一次带宽扩容

记一次带宽扩容

最近遇到一个问题:需要使用远程桌面连回到家里的电脑上打游戏,但是带宽不够。

我家带宽的情况:杭州电信,一年500多RMB,下行100M,上行30M。

30M的上行带宽,在大部分场景下还是比较流畅的,但是会间歇性卡顿。之后尝试了电信的增值服务,7RMB可以1天提升到带宽:上行200M,下行50M。

经过测试,在这种情况下,远程桌面连回家里,打游戏一点也不卡,非常流畅,玩些大型游戏也是没问题的。

所以能大致得出结论,网络会间歇性卡顿是偶尔需要传输40-50M左右的流量(起码50M的上行是一点也不卡)。如果我氪金的话,一年需要7*30*12=2520RMB,太贵了。

所以考虑其他方案,然后发现电信的宽带可以进行多拨,电信的宽带是有给公网ip的,并且我装宽带的时候已经让设置桥接了。我家用的是软路由+OpenWrt,所以还是比较容易设置多线多拨的。

多线多拨

第一步

在正常设置的情况下,在添加一个接口,这个接口绑定另一张跟猫连接的网卡。然后设置拨号上网,填上你的账号密码。然后改一个跃点数就行了,跃点数越高,优先级越低。

第二步

这步的前提是,你已经有两个网口成功拨好上网,有两个公网IP了。然后就是使用mwan3设置负载均衡,这一步有两个需要注意的地方:

  1. 接口名字大小写敏感
  2. 如果配置好了,发现还是有问题,可以尝试把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协议,仍然没法做到。举个例子:

  1. 客户端A,往路由器B的WAN1口(192.168.1.1)的8888端口发送UDP流量
  2. 客户端只能接收到192.168.1.1:8888端口发送来的UDP流量
  3. 因为有运营商的限制,没办法把WAN2口(192.168.1.2)流量的源IP改成192.168.1.1
  4. 在客户端上把接收到的源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
# server.py
#!/usr/bin/env python3
# -*- coding=utf-8 -*-

from threading import Thread
from queue import Queue
import socket
import random
import sys

sSock = 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()
# print(data)

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()

服务端的逻辑是:

  1. 在不同的WAN口上,简单同一个端口
  2. 接受到的数据转发给远程桌面,这就相当于是路由器B在请求远程桌面。
  3. 接收到的请求,随机从一个网口发回去。

不通网口发回的流量,我们的客户端只能接收到一个网卡的,所以需要一个配套的客户端代理:

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
# client.py
#!/usr/bin/env python3
# -*- coding=utf-8 -*-

from threading import Thread
from queue import Queue
import socket
import random

sSock = 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()
# print(data)

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上运行,逻辑如下:

  1. 在本地监听一个跟远程桌面相同的端口,相当于是一个伪远程桌面服务。
  2. 客户端A上的远程桌面连接到该服务上,把流量随机转发到路由器B的其中一个WAN口上。
  3. 接收到路由器B返回的流量,全都转发给客户端A。

其他

上述脚本经过测试,能成功扩容上行带宽,就算是python写的,性能也不会很差。不过有空的时候,可以尝试有C或者Golang写一个,到时候可以对比一下性能。

Author

Hcamael

Posted on

2021-12-07

Updated on

2021-12-08

Licensed under