How to get my non-loopback network ip address in C? - c

For a communication between two hosts, I need to send the IP address of my host to the other site. The problem is that if I request my IP address, it might be that I get back my local loopback IP addres (127.x.x.x) , not the network (ethernet) IP address.
I use the following code:
char myhostname[32];
gethostname(myhostname, 32);
hp = gethostbyname(myhostname);
unsigned my_ip = *(unsigned*)(hp->h_addr);
if( (my_ip % 256) == 127) {
/* Wrong IP adress as it's 127.x.x.x */
printf("Error, local IP address!");
return;
}
The only way to solve it is to make sure my hostname in /etc/hosts is behind the real network address, not the local loopback (the default for e.g. Ubuntu).
Is there a way to solve this without relying on the content of /etc/hosts?
Edit: I changed the above code so it makes use of getaddrinfo, but I still get back the loopback device's number (127.0,0,1) instead of the real IP address:
struct addrinfo hint = {0};
struct addrinfo *aip = NULL;
unsigned ip = 0;
struct sockaddr_in *sinp = NULL;
hint.ai_family = AF_INET; /* IPv4 */
hint.ai_socktype = SOCK_STREAM;
if(getaddrinfo(hostname, NULL, &hint, &aip) != 0) {
return 0;
}
sinp = (struct sockaddr_in *) aip->ai_addr;
ip = *(unsigned *) &sinp->sin_addr;
(I used to get back a list of 3 addrinfo's with the three SOCK_STREAM,SOCK_DGRAM and SOCK_RAW, but the hint prevents that)
So my question still stands...

There is POSIX function getaddrinfo() that returns linked list of addresses for given hostname, so you just need to go through that list and find non-loopback address.
See man getaddrinfo.

Not an answer, but a relevant comment: be aware that as soon as you start sending addressing information in the content of packets, you run the risk of making your application unable to work across NAT:ing routers and/or through firewalls.
These technologies rely on the information in IP packet headers to keep track of the traffic, and if applications exchange addressing information inside packets, where they remain invisible to this inspection, they might break.
Of course, this might be totally irrelevant to your application, but I thought it worth pointing out in this context.

The originating address will be included in the packet sent... there's no need to duplicate this information. It's obtained when accepting the communication from the remote host (see beej's guide to networking, specifically the part on accept())

I just ran into a situation where when only /etc/hosts has information in it and when I used getaddrinfo to get the IP address list, it returned 127.0.0.1 each time. As it turned out, the hostname was aliased to localhost...something often easy to overlook. Here's what happened:
The /etc/hosts file:
127.0.0.1 localhost.localdomain localhost foo
::1 localhost6.localdomain6 localhost6
172.16.1.248 foo
172.16.1.249 bie
172.16.1.250 bletch
So, now, when you call getaddrinfo with host="foo", it returns 127.0.0.1 3 times. The error here, is that foo appears both on the line with "127.0.0.1" and "172.16.1.248". Once I removed foo from the line with "127.0.0.1" things worked fine.
Hope this helps someone.

Look at this:
Discovering public IP programmatically
Note that in some cases a computer can have more than one non-loopback IP address, and in that case the answers to that question tell you how to get the one that is exposed to the internet.

Even if the computer has only one physical network interface (an assumption that may or may not hold, even netbooks have two - ethernet and WLAN), VPNs can add even more IP adresses. Anyway, the host on the other side should be able to determine the IP your host used to contact it.

Use getaddrinfo()

You're almost there. I'm not sure how you're getting my_ip from hp.
gethostbyname() returns a pointer to a hostent structure which has an h_addr_list field.
The h_addr_list field is a null-terminated list of all the ip addresses bound to that host.
I think you're getting the loopback address because it's the first entry in h_addr_list.
EDIT: It should work something like this:
gethostname(myhostname, 32);
hp = gethostbyname(myhostname);
unsigned my_ip = *(unsigned*)(hp->h_addr);
for (int i = 0; hp->h_addr_list[i] != 0; ++i) {
if (hp->h_addr_list[i] != INADDR_LOOPBACK) {
// hp->addr_list[i] is a non-loopback address
}
}
// no address found

If /etc/hosts is still there and still the same, looking for all entries of h_addr_list won't help.

Your new code hardwires the use of IPv4 (in the hint.ai_family field) which is a terrible idea.
Apart from that, you're close, you just should loop through the results of getaddrinfo. Your code just gets the first IP address but there is an aip->ai_next field to follow...
struct addrinfo {
...
struct addrinfo *ai_next; /* next structure in linked list */
};

Related

Send IP packet from Linux Kernel without destination mac address

I want to transmit an skb that contains a valid tcp and ip header in the linux kernel. It should go out a specific interface without being routed.
My problem is, that I cannot use dev_queue_xmit because I dont know the destination mac-address.
My attempts to find out the mac-address with arp_find failed:
...
mh = (struct ethhdr *) skb_push(skb, sizeof(struct ethhdr));
...
arp_find(mh->h_dest, skb); //this or the next line
val = dev_queue_xmit(skb); //crashes kernel
Experiments with ip_local_out also failed. I set the ip header information and call ip_local_out which also results in a kernel crash.
I could not use ip_queue_xmit because I was not able to find out, what data to provide in the struct flowi *fl field.
So my questions:
How can I send out an skb on a device with ip information and no knowledge about lower levels?
If there is no answer to the first question: How can I find out the destination mac/trigger an arp request for the destination ip?
Remark:
I would like to avoid using (raw) sockets.
I have successfully
transmitted self-made skbs via dev_queue_xmit if I had the
destination mac address. So the code building the skb is not an
issue.
If you still want to use ip_local_out or ip_queue_xmit, then you can create your flowinfo like this.
struct flowi4 fl4 = {
.flowi4_oif = skb->dev->ifindex,
.daddr = iph->daddr,
.saddr = iph->saddr,
};
then create dst(rtable) with
ip_route_output_key
set to skb_buff with
skb_dst_set
I think it's a cleaner solution to use socket programming in kernel to send it as RAW IP packet.
You can create socket like this.
sock_create_kern(&sk, AF_INET,
SOCK_RAW, IPPROTO_RAW,
net);
then call
kernel_sendmsg
to let your packet going through the protocol stack.

Getting server ip 0.0.0.0:0 from getaddrinfo()

I'm following Beej's guide on NP.
I did a few modifications and am trying to obtain the IP of my server program through getaddrinfo().
(original can be found here http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html#simpleserver)
Below is the parts I've changed/added.
if ((rv = getaddrinfo(NULL, "0", &hints, &servinfo)) != 0) { //0 for random port?
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
//... some code emitted ...
//freeaddrinfo(servinfo); //I still need it!
printf("ip: %s\nport: %d\n",
inet_ntop(AF_INET, &((struct sockaddr_in *)p->ai_addr)->sin_addr, ip4, INET_ADDRSTRLEN),
ntohs(((struct sockaddr_in *)p->ai_addr)->sin_port)
);
The problem is that I get results
ip: 0.0.0.0
port: 0
Q1:I've read from a couple of websites saying that setting "0" for the port tells the OS that you want the next available port, not actually 0. Is this true?
Q2:I've also read that gethostbyname(gethostname(...)) can give you the machine's ip, but Beej said that these are superseded by getaddrinfo(). So, am I supposed to use getaddrinfo? Or gethostbyname?
Q3:Is there anything else I'm doing wrong?
It's returning exactly what you would expect it to.
From man getaddrinfo:
If the AI_PASSIVE flag is specified in hints.ai_flags, and node is NULL, then the returned socket addresses will be suitable for bind(2)ing a socket that will accept(2) connections. The returned socket address will contain the "wildcard address" (INADDR_ANY for IPv4 addresses, IN6ADDR_ANY_INIT for IPv6 address). The wild‐card address is used by applications (typically servers) that intend to accept connections on any of the hosts's network addresses. If node is not NULL, then the AI_PASSIVE flag is ignored.
The code you link to sets hints.ai_flags to AI_PASSIVE and you're passing NULL for node. The wildcard address is 0.0.0.0. Working as specified. Binding to that address means you're binding to every IP address on your machine.
As for the port ... you're specifying "0" which ... is exactly what you're getting back. You need to set it an actual port you wish to listen on as the example code you link to does.
Q1:I've read from a couple of websites saying that setting "0" for the port tells the OS that you want the next available port, not actually 0. Is this true?
Yes, but only after you have used bind() to attach the address to a real socket. At that point, use getsockname() to get the bound address from the socket; the port will be part of that.
Q2:I've also read that gethostbyname(gethostname(...)) can give you the machine's ip, but Beej said that these are superseded by getaddrinfo(). So, am I supposed to use getaddrinfo? Or gethostbyname?
Use getaddrinfo(); it does all that gethostbyname() did and more, and the interface sucks a lot less. (For example, it's typically thread-safe.)
Q3:Is there anything else I'm doing wrong?
There's no good defined concept of the server's IP address. Servers can have many due to things like multiple network cards (much more common for servers than desktop systems), and the one that the outside world knows it by might not be one of them (thanks to NAT firewalls). Occasionally, you can know exactly where the messages are coming from before the client connects — the most common option is to know that the client will be on localhost — which is part of the information you set during bind() but that's rare. You can find the address of the client once the connection has happened by using getpeername() but NAT might still make that functionally useless. Such addresses are typically set in the app's config file during deployment.
If you only need the info for logging purposes, go right ahead. But beware of using it for anything else, as it doesn't actually tell you that much.

How to listen on all IPV6 addresses using C sockets API

I maintain GPSD, a widely-deployed open-source service daemon that monitors GPSes and other geodetic sensors. It listens for client-application connections on port 2947 on both IPv4 and IPv6. For security and privacy it normally listens only on the loopback address, but there is a -G option to the daemon that is intended to cause it to listen on any address.
The problem: the -G option works in IPv4, but I can't figure out how to make it work with IPv6. The method that should work based on various tutorial examples does not, producing instead an error suggesting the address is already in use. I'm seeking help to fix this from people experienced with IPv6 network programming.
Relevant code is at http://git.berlios.de/cgi-bin/gitweb.cgi?p=gpsd;a=blob;f=gpsd.c;h=ee2156caf03ca23405f57f3e04e9ef306a75686f;hb=HEAD
This code operates correctly in both the -G and non -G cases under IPv4, as is easily verified with netstat -l.
Now look around line 398 after "case AF_INET6:". The listen_global option is set by -G; when false, the code succeeds. There is presently a following comment, inherited from an unknown contributor, that reads like this:
/* else */
/* BAD: sat.sa_in6.sin6_addr = in6addr_any;
* the simple assignment will not work (except as an initializer)
* because sin6_addr is an array not a simple type
* we could do something like this:
* memcpy(sat.sa_in6.sin6_addr, in6addr_any, sizeof(sin6_addr));
* BUT, all zeros is IPv6 wildcard, and we just zeroed the array
* so really nothing to do here
*/
According to various tutorial examples I have looked up, the assignment "sat.sa_in6.sin6_addr = in6addr_any;" is (despite the comment) correct, and it does compile. However, startup with -G fails claiming the listen address is already in use.
Is the assignment "sat.sa_in6.sin6_addr = in6addr_any;" nominally correct here? What else, if anything, am I missing?
The reason the address is already in use is because on many IPv6 networking stacks, by default an IPv6 socket will listen to both IPv4 and IPv6 at the same time. IPv4 connections will be handled transparently and mapped to a subset of the IPv6 space. However, this means you cannot bind to an IPv6 socket on the same port as an IPv4 socket without changing the settings on the IPv6 socket. Make sense?
Just do this before your call to bind (this is taken from one of my projects):
int on = 1;
if (addr->sa_family == AF_INET6) {
r = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
if (r)
/* error */
}
Unfortunately, there is no default value across platforms for IPV6_V6ONLY -- which basically means you always need to turn it either on or off explicitly if you care, unless you don't care about other platforms. Linux leaves it off by default, Windows leaves it on by default...
From a look in a random Linux system's include files, in6addr_any is declared like so:
extern const struct in6_addr in6addr_any; /* :: */
extern const struct in6_addr in6addr_loopback; /* ::1 */
#define IN6ADDR_ANY_INIT { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } }
So, perhaps the nearness to the INIT array confused whoever left that comment in GPSD's sources. The actual type is clearly struct in6_addr, which is assignable.
I did look around, and found some hints that suggested that if IPv4 is already listening to the "any" address, IPv6 can't also. Perhaps that's what's biting you.

How to ensure that a server listens on IPv6 if possible and IPv4 otherwise?

I am trying to write a server application that listens to both IPv6 and IPv4 connections. The proper way to accomplish this seems to be listening on IPv6 address, which will also accept IPv4 connections.
The relevant piece of code is:
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
getaddrinfo(NULL, MYPORT, &hints, &res);
(pretty much copypasted from Beej's Guide)
The problem is that at least on my system, getaddrinfo returns an entry with AF_INET first and AF_INET6 second -- whereas client's getaddrinfo returns AF_INET6 first, as per spec. With my naive approach, server picks IPv4, client picks IPv6 and the connection fails.
I tried to fix this by setting hints.ai_family = AF_INET6, but that fails on systems where IPv6 is not available.
I see two obvious solutions:
a) try asking for IPv6 first and fall back to IPv4 if that fails, or
b) walk through results of getaddrinfo, look for IPv6, and if is not present, pick the first entry
but i don't like either one too much ;) I feel that there should be a way to convince getaddrinfo to do the right thing, or perhaps a different way to accomplish my goal.
The order of the address that getaddrinfo() returns is unspecified, so you have to be prepared to handle either case. That probably means traversing the list, keeping track of the "best address seen so far".
Alternatively, you could try to bind() and listen() on all the addresses returned by getaddrinfo(). This is probably the best option, since some OSes do not accept IPv4 connections to IPv6 sockets listening on 0::0.
Your code should work the way you described. Unfortunately, there's a bug in glibc as described in launchpad bug #673708, which causes it to choose IPv4 first.
Computer configuration solution
There is a work-around which can be done on each Linux computer on which you're running your server program: edit /etc/gai.conf, enable all the default rules (uncomment them):
label ::1/128 0
label ::/0 1
label 2002::/16 2
label ::/96 3
label ::ffff:0:0/96 4
label fec0::/10 5
label fc00::/7 6
label 2001:0::/32 7
then add:
label ::ffff:7f00:1/128 8
Then your code should open IPv6 if supported, and will also accept IPv4 connections.
Code solution
If the above is not practical (it's only practical if you're willing to change the configuration on every computer you run on), then modify your code to prefer IPv6. E.g. I've done this:
Do three passes through the getaddrinfo() results.
The first pass, prefer IPv6. Attempt to clear the IPV6_V6ONLY socket option to support both IPv6 and IPv4.
The second pass, prefer IPv4.
The third pass, take whatever is available.

Discovering the default gateway without DHCP

A crazy question, but is there anyway to discover the default gateway without DHCP?
This would be for a device on a network which does not use DHCP which does not have an IP address as yet. I was thinking if I could discover the default gateway, then i could try to guess an unused ip address then broadcast on the network to see if it's being used.
I may be asking for trouble i understand. E.g. if there is already a computer which has a static IP and is shutdown.
Packet sniff for a while and then apply heuristics. I'm assuming IPv4 and Ethernet for the rest of this. This won't work so well if Ethernet Switches are used rather than HUBs. More on that at the end.
Create your own RARP (reverse address resolution) table based on the Ethernet (or whatever) and IP headers you see as well as actual ARP packets (ignoring broadcast and multicast IP and MAC addresses). Make sure that your table is able to map multiple IP addresses to a single hardware interface. Any IP addresses in the table that are entered or verified by actual ARP packets should be flagged as such.
The gateway(s) is likely to recieve and send more traffic. This traffic is likely to have many IP addresses from many different networks but have the same hardware address. The gateway will show up in your table as one MAC address with lots of IP addresses. It is likely that you will have observed an ARP transaction involving this MAC address address, so you can look for an IP address that aliases that MAC that also has the seen in ARP packet flag set. That is almost certainly a gateway's IP address.
You will probably be able to guess the network address and subnet mask based on the IP address of the gateway, but this might not be something you can trust. If you want to try to make this work on a network with a non-standard netmask you can try to do the following.
Make two IP address sized accumulator variables:
uint32_t acc_and = 0xFFffFFff;
uint32_t acc_or = 0x00000000;
And then for each ARP verified address in your table you do
acc_and &= ip_addr;
acc_or |= ip_addr;
You could also use destination IP addresses (that aren't multicast) in packets that came from the gateway for this, but you would have to know the gateway before you could flag them as such.
finding the netmask and network address
The network portion of the address should stay the same in both (after the first operation), but in acc_and the bottom bits should start clearing out while in the acc_or the bottom bits should start filling up. With enough samples you would be able to determine the network address by:
uint32_t net_addr = acc_and & acc_or;
and the netmask by:
uint32_t net_mask = acc_and ^ acc_or;
Getting enough sampled local IP addresses could take too long, so you can start trying to narrow it down by:
uint32_t almost_net_mask = acc_and ^ acc_or;
int i = 0;
while ( 1 & almost_net_mask ) {
i++;
almost_net_mask >>= 1;
}
uint32_t net_mask = 0xFFffFFff;
while( i-- ) {
net_mask <<=1;
}
This would find you the last 1 bit set. The lowest 0 bit is obviously not part of the local portion of the address. Alternately you could get the same i by:
i = ffs( ~ ( acc_and ^ acc_or ) ) ; // assuming that int on your system is 32 bit. if not use ffsl
If you really really want to be sure of your netmask and network address now you can try some other things. You can try to see if the gateway will attempt to forward packets you send it to what you think should be local addresses to them. For this would would have to go ahead and assign yourself an IP address, which is risky if your netmask isn't right.
choosing an IP for yourself
Try to choose the lowest legal IP address that you haven't seen traffic to or from to increase the chance that it is not out of range. You could spoof an ARP request for that address and look for any replies (you could use your own real MAC address but possibly make up an IP for this. doesn't have to be your IP, which might confuse things for a little bit). After you have found an IP address that you can't ARP claim it for yourself.
It is possible that you ended up with an IP address that is actually too high. This is difficult to know for sure since your netmask is still just a good guess. You're out of luck if this is the case because there is not free slot for you to live.
back to the netmask
To see if your netmask needs adjusting try sending IP packets with destination addresses that you haven't see in the local network but that could be in the local network based on your guesses at the netmask and the network address. If these guesses are off they should have too many bits assigned to the network address, so sending to addresses which change the lower bits from the netmasked portion of the (so far) best guessed network address is what you want. You can try sending these as you normally would by ARPing the IP address and seeing if anyone replies, but since you are guessing addresses and likely to miss, it might be better to try creating the packet(s) with the destination MAC address set to the gateway to see if it would forward it. It may be configured not to, so you could try to see if it would by first doing this for a member of the network which you have already observed in the network and then see if it forwards it for you. If the gateway forwards the packet then you can rely on its idea of the netmask to narrow down your idea of the netmask. If the gateway will not forward for packets destine for known members of the local network then you can either just keep using your idea of the local netmask and network until you have a reason to adjust it or send out ARP requests for addresses in that range (which will only answer your question as either "yes" or "maybe" with no possibility of a sure "no").
if you are using an Ethernet switch, rather than a HUB
If you are using an Ethernet switch things get much more difficult because the switch will not forward Ethernet frames to you if it knows that the frames should go somewhere else, so you will not see them. It will forward many ARP requests, though, since they are broadcast unless the sender still has an entry for that IP in its ARP cache and is just trying for an early renewal of that entry (this may not be done by all systems). This will make building your idea of the network and its members much more difficult, but you will likely be able to do a decent job. You will probably have to rely on the quantity of ARP request for the gateway and from the gateway being higher than the other systems to spot it, though. This is error prone as a file server could have similar traffic.
Could be done but not in easy way (according to my poor knowledge).
Solution I would analyze further:
Detect Gateway - 2 possibilities (all off them related with data sniffing):
capture DHCP offer packets and
retrieve gateway
look for packet which is addressed to outer network (e.g. do arp request on each target IP address - if no answer you have probable mac address of the default gateway - you need to find IP address of this gateway (analyze arp packets, all packets, etc..)
one you have ip address do a arp requests and find you free ip.
sniffing library I think is libpcap or winpcap.
Never done even something similiar but If I wouldn't find any references on google I would go this way I think.
On a Windows network, you could use IP 0.0.0.0 and open a UDP socket with port 67 (or maybe 68) and wait for SMB broadcast messages to learn an IP address. From there, you can use ARP packets to search for devices on addresses close to that IP.
Try to find an unused address that as close to the original IP (identical upper bits) in case the network uses a netmask smaller than 255.255.255.0. Once you find an unused IP, claim it for your device and send a gratuitous ARP.
Now continue your ARP scan, and whenever you find a device that responds, temporarily set it as your default gateway and try to ping a known IP address of a device on the Internet that will respond. If you get a response, you've found your gateway.
That's a lot of work when using DHCP will get you there 99% of the time.

Resources