Skip to content

Binding applications to a specific IP

Linux

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.

Updates:

19.03.15 madmakz wrote in to clarify that all of the bind LD_PRELOAD shims only work with TCP connections. So not with UDP.
I'm not aware of a shim that manipulates UDP sockets.

14.01.14 Christian Pellegrin wrote a superb article on how to achieve per-application routing with the help of Linux network namespaces.

16.06.13 showip.be seems to be gone, so I replaced it with dnstools.ch in the text above. There are plenty of others as well.

22.06.12 Lennart Poettering has a IPv4 only version of a shim and a rather good readme available at his site.

29.11.10 Catalin M. Boie wrote another LD_PRELOAD shim, force_bind. I have not tested this one. It's capable of handling IPv6 binds.

11.01.09 Daniel Ryde has replied to my email and updated his local copy now as well.

Trackbacks

linuxedu.tetaneutral.net on : PingBack

Unfortunately, the contents of this trackback can not be displayed.

qa.apthow.com on : PingBack

Unfortunately, the contents of this trackback can not be displayed.

qa.apthow.com on : PingBack

Unfortunately, the contents of this trackback can not be displayed.

bootpanic.com on : PingBack

Unfortunately, the contents of this trackback can not be displayed.

fixuser.com on : PingBack

Unfortunately, the contents of this trackback can not be displayed.

fixuser.com on : PingBack

Unfortunately, the contents of this trackback can not be displayed.

Comments

Display comments as Linear | Threaded

dj on :

Didnt work for me,have tried compiling and pre-compiled bin. My routing table

http://pastebin.com/m376d810a

my ip : 10.77.0.200

BIND_ADDR=10.77.0.200 LD_PRELOAD=/home/djkz/Desktop/bind.so firefox

Richard on :

Doesn't work. IP remains always that of the default NIC. Nada no nothing. :-(

Daniel Lange on :

Well, it works for me and many, many others. strace your application and see what it does. Notice that LD_PRELOAD is not evaluated for setuid/setgid applications.

CloneIDEA on :

Thank you. It's work for me. I used Daniel Ryde's script.

I bind 3 firefox with 3 IPs, all of them work well :-)

Folie Triks on :

Why isn't this code in my repository??

It's absolutely frickin essential!!

Thank you.

Ace on :

I have tested it on Debian Jessie and it works. Thank you very much! I have used your solution to force NeoRouter Server to bind to a specific address. For those interested, solution is described here.

FlashT on :

Doesn't seem to work as default gateway stays the same... so binding to other ip with same gateway leads to connectivity issues.

Daniel Lange on :

Nope. The bind() call doesn't know anything about routing or default gateways. That's in the kernel network stack. So there must be something else broken with your setup and use of multiple IPs. Obviously you can only tweak the bind() call to use IPs that are actually bound, up and working on the host system.

Mr upS on :

third_party/bind$ gcc -nostartfiles -fpic -shared bind.c -o bind.so bind.c: In function ‘_init’: bind.c:71:21: error: ‘RTLD_NEXT’ undeclared (first use in this function) real_bind = dlsym (RTLD_NEXT, "bind"); ^ bind.c:71:21: note: each undeclared identifier is reported only once for each function it appears in bind.c:83:21: warning: implicit declaration of function ‘inet_addr’ [-Wimplicit-function-declaration] bind_addr_saddr = inet_addr (bind_addr_env); ^

Daniel Lange on :

You failed the copy & paste new developer course.

The -D_GNU_SOURCE is not optional.

Dave Lawrence on :

Is this code threadsafe? We have

struct sockaddr_in local_sockaddr_in[] = { 0 };

and in bind() and connect() we have

static struct sockaddr_in *rsk_in;

I think all these should be stack variables for thread safety.

I've done a variation of this that allows the bind address to be changed dynamically via the JNI for using this with Java if you're interested

kiko on :

Any idea how to port to using with ipv6 ?

thanks

Daniel Lange on :

If you look at the update from 29.11.10, there is a IPv6 capable shim linked.

kiko on :

Hi,

Thanks I have tried that one but without success yet, just got the error:

LD_PRELOAD="${LD_PRELOAD}:./force_bind.so" export FORCE_BIND_ADDRESS_V6=2001:xxxx

firefox /bin/sh: symbol lookup error: ./force_bind.so: undefined symbol: dlsym

I am not skilled in programming so I am stuck in there, look for another ways. thanks

Daniel Lange on :

The "undefined symbol: dlsym" mean you have built without -ldl. That should not happen™ if you have used ./configure; make properly (aka inspected its output).

Cătălin seems like a nice guy. He shows his email address on his home page. Drop him a qualified error report and he may have time to fix it.

kiko on :

But when I do make and see the output shows with the flag -ldl, see below:

gcc -ggdb3 -Wall -Wextra -Wno-long-long -pipe -ldl -lc -shared -rdynamic -fpic -Wl,-soname,force_bind.so -o force_bind.so force_bind.c

But I already leave a message in github.

Daniel Lange on :

Well, Mirco is not Cătălin. I'm not sure how much posting an issue to a random github repo is worth. Read: that guy is not the author of the code.

kiko on :

Well I am sorry if I am mistaken someone here I don't know much but what are you saying is I am post about my issue in the wrong/random github ?

https://github.com/meebey/force_bind

It's not the correct ?

thank you and sorry for any troubles

Daniel Lange on :

Yes, that's Mirko. He has nothing to do with the author of the shim you are trying to use.

aniy -iis, n. on :

This is awesome!! It worked as I longed for on my ubuntu 18.04 running on aws EC2 with two nics in the same segment...! Thank you so much Daniel & Daniel!!! :-)

root@xxxx:/home/xxxxxx# curl ifconfig.me
aaa.bbb.164.63
root@xxxx:/home/xxxxxx# BIND_ADDR="eee.fff.1.110" LD_PRELOAD=/usr/lib/bind.so curl ifconfig.me
ccc.ddd.201.19
root@xxxx:/home/xxxxxx#

*eee.fff.1.110 is another local ip address bound to the second nic set with global ip of ccc.ddd.201.19.

Michael on :

Awesome this page. Just like I found transocks_ev and ssync thanks to another blogger. Used ssync since 2004 on FreeBSD. Was tired of rsync on Linux and had misplaced the source code. haha, is like rediscovering the music hits from the 60's and 70's. Seems like we are dying breed. Miss the projects where the OS is the framework and bash it's glue. Where are those freelance projects today?

Add Comment

Markdown format allowed
Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

Form options

Submitted comments will be subject to moderation before being displayed.