I'm 赵一开/BlahGeek, a CS&T Student @ Tsinghua U.

Get in touch:   See more:

新年快乐!自定义你的Traceroute路径

Note: 这个创意并不是我原创的,在圣诞节时在推上看到有人做过,但是来源已经找不到了。原作者是用的IPv4地址做的,而且似乎使用了多台机器,成本比较高。而我的实现方案使用IPv6地址并且只需要一台机器,成本几乎为零。

Get it!

Fork me on GitHub

实现原理

要实现自定义Traceroute路径,并不需要真的准备多个机器(或网络接口)配置他们之间的路由,利用Traceroute的原理,只需要一台机器就够了(当然多个IP地址还是需要的)。

Traceroute的原理是一次发送Hop Limit(IPv4中叫做TTL)为1,2,3……的IP包,Hop Limit每过一个路由器会减一,减少至零时路由器回向源主机发送一个不可达信息,源主机由此判断第N跳的路由器地址。那么比如我们要在目的地址前增加10条路由,我们只需要在收到Hop Limit小于等于10的包时,将该包丢弃并以不同IP地址向源主机发送不可达信息即可。

在Linux中,为了实现应用程序对这些包的处理,可以使用netfilter的NFQUEUE目标,将特定的包发送至用户空间由用户空间程序处理并决定丢弃或接收改包。用户空间程序通过libnetfilter_queue实现对包的处理。

实现细节

  • 首先,从Tunnel Broker注册一个6in4 Tunnel并配置,它提供一整个/64的IPv6地址并且支持Reverse DNS(Linode不支持IPv6 Reverse DNS)。以我的地址为例,我得到的/64地址块为2001:470:1f05:42c::/64

值得注意的是,虽然2001:470:1f05:42c::/64的所有地址均会被路由至我的机器,但是由于并没有在接口上绑定所有的地址,Ping其中的任意一个地址并不会得到返回。

  • 我选择2001:470:1f05:42c:2015::作为Traceroute的目的地址,通过iptables将所有发向该地址的包queue至用户空间:
ip6tables -t nat -I PREROUTING -d 2001:470:1f05:42c:2015:: -j NFQUEUE --queue-num 1

注意该地址并没有绑定至本机的接口,因此走的是nat表,否则应该加入filter表的INPUT规则。

  • 写一个用户程序对这些包进行处理。这里我使用的是libnetfilter_queue的Python wrapper,另外还使用了scapy用于对IP包的处理。
def handle(inpkt):
    pkt = IPv6(inpkt.get_payload())
    hlim = pkt.hlim - 1
    if hlim < len(ROUTES):
        inpkt.drop()
        send(IPv6(src=ROUTES[hlim], dst=pkt.src) / ICMPv6TimeExceeded() / pkt)
    else:
        inpkt.accept()

if __name__ == '__main__':
    nfqueue = NetfilterQueue()
    nfqueue.bind(1, handle)
    nfqueue.run()

即对于Hop Limit小于某个值时,以某一特定的地址发送一个Time Exceeded ICMPv6包,并将该包丢弃;否则接收该包由内核继续处理。

此时从另一台机器上运行mtr 2001:470:1f05:42c:2015::便能看到多了很多地址。

最后,在Tunnel Broker上对相应的地址设置想要的Reverse DNS,在DNS服务商中把happy.2015.blahgeek.com指向2001:470:1f05:42c:2015::就大功告成啦!