Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

进一步讨论机制问题 #9

Open
ccaapton opened this issue Aug 13, 2017 · 29 comments
Open

进一步讨论机制问题 #9

ccaapton opened this issue Aug 13, 2017 · 29 comments
Labels

Comments

@ccaapton
Copy link

ccaapton commented Aug 13, 2017

你好,我是v站的s82kd92l,咱一起讨论过udp2raw细节问题。这两天看了下代码,也仔细回味了一下。架构上有些建议:

  1. 三次握手根本不需要我们自己完成,我们可以开一个普通的tcp socket和服务器进行初始握手,一切都由内核完成,之后再劫持这个socket进行隧道通信。

  2. 只要有个原生tcp socket,就不再需要"iptables -I INPUT" 这种dirty hack. 我们可以在本地利用setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,...)这种接口,让原生socket在握手之后发送的包ttl变小,这样原生socket无法收到对方发出的内容,也就不需要iptables了。

有新想法我再加

@ccaapton
Copy link
Author

又有了一个更疯狂的想法。linux内核有个叫nfqueue的东西,可以直接把感兴趣的包发到用户态程序,修改之后再发回内核。具体操作参加
https://stackoverflow.com/questions/6749423/packet-modification-with-netfilter-queue

这样就可以利用一个普通的tcp socket通信,利用nfqueue在发出包时找出seq的最新值,然后在收到包时,把所有的ack都改为我们记录到的最新值。这样就变相废除了阻塞和重传机制。

@linhua55
Copy link

@ccaapton
不错,有类似的想法 linhua55/lkl_study#11

@wangyu-
Copy link
Owner

wangyu- commented Aug 13, 2017

你好,我是v站的s82kd92l,咱一起讨论过udp2raw细节问题。这两天看了下代码,也仔细回味了一下。架构上有些建议:

  1. 三次握手根本不需要我们自己完成,我们可以开一个普通的tcp socket和服务器进行初始握手,一切都由内核完成,之后再劫持这个socket进行隧道通信。
  1. 只要有个原生tcp socket,就不再需要"iptables -I INPUT" 这种dirty hack. 我们可以在本地利用setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,...)这种接口,让原生socket在握手之后发送的包ttl变小,这样原生socket无法收到对方发出的内容,也就不需要iptables了。

欢迎s82kd92l。

2有个问题。如果原生的tcp socket不屏蔽掉,虽然可以用ttl让它发不出去数据,他会一直收到东西,占内核的处理资源。还有,在有丢包的情况下,这个原生tcp只要漏掉一个包,后续收到的数据就不能提交给上层。然后buffer会满。buffer满了后又一直收到新数据,丢的数据又重传不了,我担心内核会把他回收掉然后回复RTS

因为2的问题,1的优势也不明显了。

@wangyu-
Copy link
Owner

wangyu- commented Aug 13, 2017

又有了一个更疯狂的想法。linux内核有个叫nfqueue的东西,可以直接把感兴趣的包发到用户态程序,修改之后再发回内核。具体操作参加
https://stackoverflow.com/questions/6749423/packet-modification-with-netfilter-queue

这样就可以利用一个普通的tcp socket通信,利用nfqueue在发出包时找出seq的最新值,然后在收到包时,把所有的ack都改为我们记录到的最新值。这样就变相废除了阻塞和重传机制。

这个不错。我一直想找个类似的东西,用原生的tcp握手和发数据,然后干掉按序到达和拥塞控制。我先仔细看一下。

@ccaapton
Copy link
Author

用ttl让它发不出去数据,他会一直收到东西,占内核的处理资源

如果对方的ttl够小发不出,你又怎么会一直收到东西呢:)

@wangyu-
Copy link
Owner

wangyu- commented Aug 13, 2017

我是说对端的faketcp发来的数据包。ttl只是关掉了双方原生tcp的发送,没有关闭接收。对方rawsocket发来的tcp数据也会被原生tcp收到。

==updated==
也许我理解错你的意思了?我再看一遍

@ccaapton
Copy link
Author

我是说对端的faketcp发来的数据包。ttl只是关掉了双方原生tcp的发送,没有关闭接收。

你目前的实现里面,seq没有变化吧。这样内核默认这个是重传的包,应该不会丢给用户态的。

@ccaapton
Copy link
Author

上面那个stackoverflow我随便搜的,这里好像有个更详细的blog:

https://blog.talentica.com/2016/05/17/introduction-to-packet-interception-using-netfilter/

@wangyu-
Copy link
Owner

wangyu- commented Aug 13, 2017

你目前的实现里面,seq没有变化吧。这样内核默认这个是重传的包,应该不会丢给用户态的

有变化。代码在int after_send_raw0(raw_info_t &raw_info) ,我用tcpdump抓包和真实tcp对比过,确认它是涨了的。

但是用--seq-mode 0可以关掉。

如果实现成不变的话,容易被运营商封。

@wangyu-
Copy link
Owner

wangyu- commented Aug 13, 2017

想起个事,@ccaapton 我昨天修了个bug。修了以后icmp模式的速度彪满20m宽带了(上/下行都可以)。

= =。也许是偶然现象,我多用几天再下结论。

@wangyu-
Copy link
Owner

wangyu- commented Aug 13, 2017

@linhua55 读了你的连接。
https://stackoverflow.com/questions/31762305/prevent-kernel-from-processing-tcp-segments-bound-to-a-raw-socket

So you are right that the kernel sends an RST packet because it has no knowledge of active TCP sockets or connections on the specified port. As I said, you can't stop the kernel from doing its work, but a relatively quick (and perhaps ugly) hack is to drop RST packets with iptables:

iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP
Yes, not very elegant, but I think there's not much we can do here.

As suggested in the comments, you might also create a dummy TCP socket bound to the same port and address where you just receive and discard the messages. That way the kernel won't send RST replies, and you don't need to mess with iptables.

之前见过这个用法。只屏蔽掉发出去的RST。 然后我尝试在网上找这么做以后那个dummy tcp收到数据了的行为,什么也没找到。 我担心这个不确定的行为,所以最后选择了彻底屏蔽掉接受的方案。

@ccsexyz
Copy link

ccsexyz commented Aug 13, 2017

这个思路我实现过,感觉没啥用其实,在 linux 上修改 iptables 太简单了。

@linhua55
Copy link

linhua55 commented Aug 13, 2017

最好是 彻底屏蔽掉接受,这样 本地内核 就不会产生RST包了。 如果是屏蔽掉输出,则是 本地内核产生RST包后,这样就浪费了CPU。

使用dummy TCP是为了 在接受输入的情况下(不使用iptables屏蔽) 不让 本地内核 产生RST包, 本地内核 产生RST包的几种情况:
https://zhangbinalan.gitbooks.io/protocol/content/tcpde_rst.html

@wangyu-
Copy link
Owner

wangyu- commented Aug 13, 2017

@ccsexyz 确实。

@wangyu-
Copy link
Owner

wangyu- commented Aug 13, 2017

@linhua55 自己做TCP握手其实不是很麻烦,我觉得用raw socket的方式自己控制全部应该会更好,依赖一个其他东西的行为然后在上面打补丁可能会出难以预料的问题。

@linhua55
Copy link

linhua55 commented Aug 13, 2017

@wangyu- 对,这个确实没必要,也只有docker容器可能会用到(樱花docker不能使用iptables,但可以使用tcpdump(raw socket)),但现在提供docker服务的很少,暂时是没必要的

@ccsexyz
Copy link

ccsexyz commented Aug 13, 2017

@linhua55 提供docker服务的一般都会在前端怼一个 haproxy 之类的代理,然后 fake-tcp 就根本不能工作了.不要问我是怎么知道的,我也是写完才发现自己好蠢,忽略了这一点.

@wangyu-
Copy link
Owner

wangyu- commented Aug 13, 2017

@ccsexyz 哦,哈哈,我这边udp有问题,看来我跟docker无缘了。

@wangyu- wangyu- added the idea label Aug 14, 2017
@wangyu-
Copy link
Owner

wangyu- commented Sep 18, 2017

有的docker vps服务商在前端没有haproxy,而是用的iptables SNAT和DNAT。这种情况可以用raw socket。

@linhua55

也只有docker容器可能会用到(樱花docker不能使用iptables,但可以使用tcpdump(raw socket))

现在我已经实现了简单的dummy socket,在server端可以不用添加iptables,可以在docker中运行而不产生RST,稳定性还有待考证(在我自己的环境上测试通过, #41 里面别人的环境也测试通过)。client端还是需要iptables。

更多信息参考:linhua55/lkl_study#11

@ccaapton
Copy link
Author

最近发现一个可以利用的机制: 如果客户端发送的tcp包包含的ack > 服务器端期望的ack,那么服务器就会把这个包在内核处理阶段丢掉。反之亦然。

那么还是回到我最初的建议,开普通的tcp socket进行3次握手,握手结束后双方的udp2raw都用更高的seq/ack进行通信,那么就不再需要iptables了。

@wangyu-
Copy link
Owner

wangyu- commented Jan 31, 2018

@ccaapton

这个方案在server端已经实现了(不完美,server端会回复2个syn ack,但是不影响使用),client端还没有。

在这个帖子里有进一步的讨论,和tcpdump抓包数据:
linhua55/lkl_study#11

这个方案的优点:

  1. 不需要iptables

缺点:

  1. 如果客户端发送的tcp包包含的ack > 服务器端期望的ack,那么服务器就会把这个包在内核处理阶段丢掉。反之亦然 依赖内核的undocumented feature。

  2. 普通的tcp socket一直开着不关,收到的数据包需要过一遍内核的协议栈,浪费性能。

之所以没实现,是因为没有足够的动力。

不过,多一个选择总是好的,如果有兴趣,可以PR。 这个方案已经加到了contribution guide里。

@ccaapton
Copy link
Author

@wangyu- 如果client能摆脱iptables的依赖,很可能就能摆脱linux的依赖。
依赖内核的undocumented feature 这个应该是tcp协议要求,不是undefined behavior,所以其他操作系统应该会有一样的行为。

@wangyu-
Copy link
Owner

wangyu- commented Jan 31, 2018

@ccaapton

但是如果一个tcp socket一直收不到有效的包/一直收到序号不合法的包,2个小时后,这个socket会不会被系统回收掉呢,就是undocumented feature了。

如果client能摆脱iptables的依赖,很可能就能摆脱linux的依赖

这也是优点之一吧。

不过windows/mac上也有iptables的替代品。目前只支持linux,主要的原因不是因为对iptables的依赖,是没有人愿意移植。。。我自己目前也没有足够的动力移植。

@ccaapton
Copy link
Author

ccaapton commented Jan 31, 2018

@wangyu- 确实移植还不如用golang重写,我在看这个golang packet,好像对bpf/pcap各种支持都有,还有很多其他golang raw socket的库。有空我试试

你有没有什么不依赖openvpn之类轻量的测试代码可用于兼容性测试?

@wangyu-
Copy link
Owner

wangyu- commented Jan 31, 2018

确实移植还不如用golang重写,我在看这个golang packet,好像对bpf/pcap各种支持都有,还有很多其他golang raw socket的库。有空我试试

golang的rawsocket和pcap可以参考一下kcpraw。

你有没有什么不依赖openvpn之类轻量的测试代码可用于兼容性测试?

我自己也是用VPN来测试的,原来我一直用openvpn。现在我主要用自己写的那个tinyFecVPN测试,不需要证书,轻量很多。

@ccaapton
Copy link
Author

ccaapton commented Feb 1, 2018

补充一下,使用原生socket还有个好处,就是在建立fake tcp连接之前,利用原生的socket交换一些非常有用的信息,比如
1.tls + http digest authentication 用来做服务器与客户端的相互身份验证,避免被DOS攻击。
2.客户端能在socket里动态写入想要转发的udp目的地,这样就不用在服务器启动时就把目的地用参数写死。
3.由2衍生出来:服务器可以转发多个udp服务,甚至对多个不同的用户提供服务

@wangyu-
Copy link
Owner

wangyu- commented Feb 1, 2018

2.客户端能在socket里动态写入想要转发的udp目的地,这样就不用在服务器启动时就把目的地用参数写死。
3.由2衍生出来:服务器可以转发多个udp服务,甚至对多个不同的用户提供服务

这两个用目前的机制也可以实现。用udp2raw+VPN 和udp2raw+socks5也可以达到类似的效果,所以没有实现在udp2raw中。

@wangyu-
Copy link
Owner

wangyu- commented Jun 21, 2018

@linhua55 @ccaapton pcap版的udp2raw实现了在client端用系统socket握手的功能:
wangyu-/udp2raw-multiplatform@33d9633
实现得比较简陋,不完美,只用了几十行代码。有时候收到一个包会回2个ack包。不过已经能免iptables屏蔽rst了。

@xtaci
Copy link

xtaci commented Jul 16, 2019

按类似思路实现了,但有几点问题:

  1. unicast TTL最小为1,Linux内核限制,也就是至少需要一个HOP,如果不想对网关 flood, iptables是必须的
  2. 即使是linux, mips内核不支持AF_PACKET抓包,只能用AF_INET抓。但Darwin内核又只能用AF_PACKET抓,所以做不到统一。

源码: https://github.com/xtaci/tcpraw

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants