Pages

Sunday, 31 January 2016

如何让Linux下的程序使用指定的IP地址

Unix* 系统都支持为一个网卡绑定多个IP地址,但是通常操作系统会根据路由表自动选择IP地址,应用程序使用哪个IP地址用户无法主动控制。本文分别讲解在Linux下为应用程序绑定指定IP地址的方法。
关于Windows如何选择IP地址可以参考这篇文章:《Source IP address selection on a Multi-Homed Windows Computer》
作者Daniel Ryde采用了LD_PRELOAD进行HACK,为应用程序注入一个动态库bind.so,这个动态库中对bind和connect函数加钩子,程序建立socket连接前绑定指定的本地IP地址。
使用方法:
$ BIND_ADDR="192.168.8.9" LD_PRELOAD=./bind.so YOUR_PROGRAME
程序源码可从这里下载:https://copy.com/lsJhY1KSNzRwbv2j/bind.c,
http://www.ryde.net/code/bind.c.txt
编译方法:
$ gcc -nostartfiles -fpic -shared bind.c -o bind.so -ldl -D_GNU_SOURCE
$ strip bind.so
--------------------

BINDING APPLICATIONS TO A SPECIFIC IP

These days many systems are multi-homed in the sense that they have more than one IP address bound at the same time.
I.e. for different network cards, virtual IPs for shared servers or just using WiFi and a wired network connection at the same time on a laptop.
Murphy of course makes sure that your system will choose to worst IP (i.e. that on slow WiFi or the one reserved for admin access) when an application does not specifically supports binding to a selected IP address. And Mozilla Firefox for example doesn't.
The kernel chooses an outgoing IP from those in the routing table with the same metric:
daniel@server:~$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.0.2.1         0.0.0.0         U     0      0        0 eth0
0.0.0.0         192.0.2.2         0.0.0.0         U     0      0        0 eth1
0.0.0.0         192.0.2.3         0.0.0.0         U     0      0        0 eth2
0.0.0.0         192.0.2.4         0.0.0.0         U     0      0        0 eth3
You can obviously play around with the metric and make the kernel router prefer the desired interface above others. This will affect all applications though. Some people use the firewall to nat all packages to port 80 onto the network interface desired for web browsing. Gee, beware the http://somewebsite.tld:8080 links...
Thankfully Daniel Ryde has solved the problem via a LD_PRELOAD shim. With his code you can run
daniel@laptop:~$ BIND_ADDR="192.0.2.100" LD_PRELOAD=/usr/lib/bind.so firefox (*)
and happily surf away.
To compile his code (3.3kB, local copy, see note 1) you need to run
gcc -nostartfiles -fpic -shared bind.c -o bind.so -ldl -D_GNU_SOURCE
strip bind.so
cp -i bind.so /usr/lib/
and you're set to go.
If you don't have gcc available (and trust me) you can download pre-compiled 32bit and 64bit (glibc-2) bind.so libraries here (4.5kB).
I guess because Daniel Ryde hid his code so well on his webpage, Robert J. McKay wrote another LD_PRELOAD shim, called Bindhack(4.5kB, local mirror). This will - as is - only compile on 32bit machines. But YMMV.
Run the above command (*) with your desired (and locally bound) IP address in bash and visit MyIP.dk or DNStools.ch or any of the other services that show your external IP to see whether you've succeeded.
Notes:
  1. Daniel Ryde did not specify the -D_GNU_SOURCE in the comments section of bind.c. Modern glibc/gcc need that as he used RTLD_NEXT which is Unix98 and not POSIX. I amended the local copy of bind.c and sent him an email so he can update his.
  2. Both are IPv4 only, no IPv6 support.
from http://daniel-lange.com/archives/53-Binding-applications-to-a-specific-IP.html