Local Ip address
The local ip is the source ip in IP packets send out from a system. When the system is a part of a local area network then the local ip in most cases is something like 192.168.*.* .
The network interface (eth0) is assigned an ip address of that range. However when the machine is connected to internet through some ppp connection (like dialup modem) then the system has an ip address allotted by the isp directly.
Kernel Routing tables
To get the local ip address we first need to find the default network interface that is being used for network communication. Then we can find the ip address of that interface which shall be the local ip address.
The kernel maintains routing tables which it uses to decide the default gateway , its interface and the local ip configured for that interface.
The /proc/net/route file (not really a file but appears like one) has more information about it.
A typical /proc/net/route output would look like:
$ cat /proc/net/route Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT eth0 0000A8C0 00000000 0001 0 0 1 00FFFFFF 0 0 0 eth0 0000FEA9 00000000 0001 0 0 1000 0000FFFF 0 0 0 eth0 00000000 0100A8C0 0003 0 0 0 00000000 0 0 0
The above lists the interface , destination , gateway etc. The interface (Iface) whose destination is 00000000 is the interface of the default gateway.
Now have a look at the route command output
$ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.0.0 0.0.0.0 255.255.255.0 U 1 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 eth0 0.0.0.0 192.168.0.1 0.0.0.0 UG 0 0 0 eth0
Now the gateway for the destination 0.0.0.0 is the default gateway. So from the /proc/net/route output this line is of interest :
eth0 00000000 0100A8C0 0003 0 0 0 00000000 0 0 0
Its destination is 00000000 and gateway is 0100A8C0. The gateway is actually the IP address of the gateway in hex format in reverse order (little endian). Its 01.00.A8.C0 or 1.0.168.192
So by reading that line in a C program we can find out the default gateway and its interface. The IP address of this interface shall be the source ip in IP packets send out from this system.
Code
There are 2 parts to the code. First is to get the default network interface name and then get the ip address of the interface.
The following code gets the default interface name.
FILE *f; char line[100] , *p , *c; f = fopen("/proc/net/route" , "r"); while(fgets(line , 100 , f)) { p = strtok(line , " \t"); c = strtok(NULL , " \t"); if(p!=NULL && c!=NULL) { if(strcmp(c , "00000000") == 0) { printf("Default interface is : %s \n" , p); break; } } }
The above code prints : "Default interface is : eth0"
Get IP address of the interface
Now we need to get the ip address of the default interface eth0. The getnameinfo function can be used for this.
Sample code is found here.
Combining that with our previous code we get :
/* * Find local ip used as source ip in ip packets. * Read the /proc/net/route file */ #include<stdio.h> //printf #include<string.h> //memset #include<errno.h> //errno #include<sys/socket.h> #include<netdb.h> #include<ifaddrs.h> #include<stdlib.h> #include<unistd.h> int main ( int argc , char *argv[] ) { FILE *f; char line[100] , *p , *c; f = fopen("/proc/net/route" , "r"); while(fgets(line , 100 , f)) { p = strtok(line , " \t"); c = strtok(NULL , " \t"); if(p!=NULL && c!=NULL) { if(strcmp(c , "00000000") == 0) { printf("Default interface is : %s \n" , p); break; } } } //which family do we require , AF_INET or AF_INET6 int fm = AF_INET; struct ifaddrs *ifaddr, *ifa; int family , s; char host[NI_MAXHOST]; if (getifaddrs(&ifaddr) == -1) { perror("getifaddrs"); exit(EXIT_FAILURE); } //Walk through linked list, maintaining head pointer so we can free list later for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if (ifa->ifa_addr == NULL) { continue; } family = ifa->ifa_addr->sa_family; if(strcmp( ifa->ifa_name , p) == 0) { if (family == fm) { s = getnameinfo( ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6) , host , NI_MAXHOST , NULL , 0 , NI_NUMERICHOST); if (s != 0) { printf("getnameinfo() failed: %s\n", gai_strerror(s)); exit(EXIT_FAILURE); } printf("address: %s", host); } printf("\n"); } } freeifaddrs(ifaddr); return 0; }
Output
Default interface is : eth0 address: 192.168.0.6
The above method used the kernel routing tables to determine the primary gateway interface and its ip address, which is the local ip address. An alternative method can be used, which does not check the routing tables.
Using getsockname with an external connection
In this method, a program makes a socket connection with some remote server outside the network and then call getsockname on the connected socket. This would return the local ip address.
Code
/* * Find local ip used as source ip in ip packets. * Use getsockname and a udp connection */ #include<stdio.h> //printf #include<string.h> //memset #include<errno.h> //errno #include<sys/socket.h> //socket #include<netinet/in.h> //sockaddr_in #include<arpa/inet.h> //getsockname #include<unistd.h> //close int main ( int argc , char *argv[] ) { const char* google_dns_server = "8.8.8.8"; int dns_port = 53; struct sockaddr_in serv; int sock = socket ( AF_INET, SOCK_DGRAM, 0); //Socket could not be created if(sock < 0) { perror("Socket error"); } memset( &serv, 0, sizeof(serv) ); serv.sin_family = AF_INET; serv.sin_addr.s_addr = inet_addr( google_dns_server ); serv.sin_port = htons( dns_port ); int err = connect( sock , (const struct sockaddr*) &serv , sizeof(serv) ); struct sockaddr_in name; socklen_t namelen = sizeof(name); err = getsockname(sock, (struct sockaddr*) &name, &namelen); char buffer[100]; const char* p = inet_ntop(AF_INET, &name.sin_addr, buffer, 100); if(p != NULL) { printf("Local ip is : %s \n" , buffer); } else { //Some error printf ("Error number : %d . Error message : %s \n" , errno , strerror(errno)); } close(sock); return 0; }
Output
Local ip is : 192.168.0.6
I much cleaner solution in my opinion is via an ioctl call:
https://stackoverflow.com/a/2283541
When using it with IPv6 addresses, the string %eth0 is appended to the address:
address: fe80::4a00:33ff:fef4:400%eth0
Thanks, it works fine on my RaspberryPi2.