Best way to get default outgoing IP address on Linux

There are multiple cases where you may need to know which IPv4 or IPv6 address from all IP addresses available on your machine will be used for outgoing connections to remote hosts.

Generic Linux machine may have multiple network interfaces and each of them may have multiple IPv4 or IPv6 addresses assigned to each of them but only single IPv4 and single IPv6 will be used by Linux when you try to connect to external host.

In case of legacy IPv4 protocol default outgoing IP address may be from private range or it may be globally routable IPv4 address assigned directly to machine.

Let's start from looking on example list of IP addresses assigned to physical 1G interface on my work AMD Ryzen based PC with Ubuntu 22.04:

We can easily guess default outgoing IPv4 address as we have only single of them but for IPv6 case it's not very obvious as we have 3 differently looking IPv6 addresses.

How Linux selects outgoing IP address to use? It uses routing lookup in Linux Kernel which selects IP address using variety of metrics and we can easily replicate it using ip route get command this way:

In both cases for IPv4 and IPv6 we can see our default outgoing IP addresses after "src" text.  That's how we can implement exceptionally unreliable approach to get default outgoing IP address which relies on text based parser or regular expression and we did it that way for IPv4. It worked fine for few months until new version of ip tool appeared in  Ubuntu 18.04 release where authors added field "uid" right after default outgoing IP address which broke our parser.

What is the best Linux API based alternative to using ip tool? To replicate route lookup logic from userspace without using ip tool we have to use protocol called Netlink to communicate with Linux Kernel.

Netlink is the most sophisticated protocol you can ever imagine. Lack of clear and up to date documentation makes this protocol almost impossible to deal with. There are variety of wrapper libraries like libnl to deal with this protocol but we're clearly not adding new dependency library just for such basic thing as outgoing IP address detection.

Do we have any alternative options? What we need is to replicate what Linux network stack does to implement outgoing network connection without actually doing network connection. We just need side effect of this process which is the default outgoing IP address which have to be determined in this process before any actual data can be sent.

There is a quite interesting hint about possibility to do so on Linux man page about UDP protocol:

UDP sockets in Linux

In this context "local address" is exactly what we need. This field will keep default outgoing IP address of machine.  UDP sockets after connect() call on them are special and we call them "connected UDP sockets".

Such name clearly need clarification as UDP is connection-less protocol and UDP has no handshake phase.

What exactly happens in Linux kernel when we do connect() on UDP socket? You can find it below:

Linux kernel is tricky

What is the most important for us is following part of logic which first does lookup to select best source IP address:

And after that it populates source address field for our socket:

Source address update

Yay! That's exactly what we need. I think man page can be improved slightly by adding note that connect() on UDP socket populates not only destination address but source address for socket too.

We can even use netstat or ss tools to get source and remote IP addresses after UDP socket was connected.

We just need to find way to retrieve this address from socket and getsockname() can help us with this final step.

This approach provides very reliable way to get default outgoing IP address and does not involve any network activity. You can even run tcpdump to be 100% sure.  

You can find complete C (same logic can be used for C++) source code for IPv4 and IPv6 cases in this GitHub: link.

Subscribe to Pavel's blog about underlying Internet technologies

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.