How to determine IP version in C++? - c++

I want to support both versions IPv4 and IPv6. Currently. I only support IPv4, but in order to set different behaviour for each version of IP I need to know, what version of IP I am working with.
Currenly I am using gethostbyname function, and depending on h_addrtype field of hostent struct I set whether it is IPv4 or IPv6, but I am wondering is that really correct? And if it is not, what are possible ways to get IP version ? And if it is correct, what should I do, if this function fails ?
Thanks on advance.

gethostbyname is deprecated, you should actually use getaddrinfo, one of the reasons it's being deprecated are IPv4/IPv6 issues.
That being said, yes, checking h_addrtype is correct.

The h_addrtype field of an IPv6 address should be AF_INET6 (instead of AF_INET) and testing that field is the correct method when using gethostbyname.
You should however consider using getaddrinfo instead of gethostbyname in new applications.

You can use getsockname to determine IP version,
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
Below method can be employed,
/* Function to detect family of a socket */
// Return AF_INET or AF_INET6
unsigned short GetSocketFamily(int sockfd)
{
unsigned short sa[16]; // 32 bytes is enough for sockaddr version for any family
// 16 bytes for IPv4 and 28 bytes for IPv6
socklen_t lth = sizeof(sa);
getsockname(sockfd, (struct sockaddr *)&sa, &lth);
return sa[0]; // In any case (IPv4 or IPv6) family is the first halfword of
// address structure
}

Related

c++ - What does ptr->ai_family do vs AF_INET

I am going through msdn's "Getting Started With Winsock" and they open a socket with the parameters
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
iResult = getaddrinfo(
argv[1],
DEFAULT_PORT,
&hints,
&result
);
ptr=result;
ConnectSocket = socket(
ptr->ai_family, // Address Family (address families like ipv6 ipv4)
ptr->ai_socktype, // Type (Like tcp, udp ect)
ptr->ai_protocol // Protocol to use (0 = service provider chooses)
);
But binarytides "Winsock tutorial" does it like this (They are using C but I have seen people do this in c++)
s = socket(
AF_INET ,
SOCK_STREAM ,
0
)
What does ptr-> do?
and why use it over just setting it like AF_INET?
Also If you have free time and know sockets well I would appreciate some help.
socket(ptr->ai_family,ptr->ai_socktype, ptr->ai_protocol);
passes in variables to create the socket, instead of hard coding the values. The advantage you get is that the code works for both IPv4 and IPv6.
ptr->ai_family is just an integer, a member of a struct addrinfo. (And if you are wondering about the particular syntax of ptr->, you can go through this question ), it will have a value of either AF_INET or AF_INET6 (Or in theory any other supported protocol)
The call to getaddrinfo() will look up the host name, and resolve it to either an IPv4 or IPv6, and you pass in the result to socket() to create a socket of the proper type. If the hostname resolves to an IPv4 host, you create a socket that can deal with IPv4, If it resolves to IPv6, you create an IPv6 socket.
If you instead hard coded the values, e.g. as AF_INET, you would only support IPv4, whilst ptr->ai_family could be either AF_INET or AF_INET6.

How to check if a TCP connection is local?

For a project, I need to know whether the network connection is from the local computer or from a remote computer.
How to achieve this?
This can be achieved by utilizing the getpeername and the getsockname functions.
This snipped does exactly what I need it to:
bool checkForLocalConnection(SOCKET Sock) {
sockaddr_in RemAddr, LocAddr;
int Len = sizeof(RemAddr);
getpeername(Sock, (sockaddr *)&RemAddr, &Len);
getsockname(Sock, (sockaddr *)&LocAddr, &Len);
return (RemAddr.sin_addr.S_un.S_addr == LocAddr.sin_addr.S_un.S_addr);
}
The endianess of the result is always the same, which is why you don't even have to convert it to native endianess.
Why this works and why it's necessary:
If you connect to localhost or 127.0.0.1, getpeername will always yield the address 127.0.0.1 (converted to an unsigned long, obviously).
That means, you could just check for htonl(2130706433); and be done with it (Minding the endianess). However if you enter the actual address...or any of your other local addresses your NIC might have, getpeername will return that address, instead of 127.0.0.1.
getsockname will return the local interface this socket is connected on, which means it will choose the correct interface and tell you its address, which is equal only if you're connected from a local machine.
I hope this will help someone, since I had to search forever to find that little info.
It should work for most common cases. (There are some exceptions)
List of exceptions:
Multi-Address network cards. These are on the same machine but either not on the same NIC or bound to a different IP. There isn't that much you can do about that.
Calling localhost on a different IP than 127.0.0.1. getsockname will always return 127.0.0.1, regardless of which 127.x.x.x you're calling. As a 'guard' against that, you can check specifically for the 127 in the first octet of the peer address.
Many thanks for the help with this goes to harper.

connect() returns "invalid argument" with ipv6 address

I have this simple client-server application pair. The code is pretty simple, I'm using only new, advised methods like getaddinfo etc and everything works just fine for ipv4. Even for ipv6 loopback (::1) it works. Problems start when it comes to some other ipv6 addresses... I have two machines in a network, everything works fine when I pass their ipv4 addresses, but when I give my client ipv6 address, I get an error on connect function: invalid argument.
Hey, don't I already know this? I do! When I try to ping6 this ipv6 address, I get the same error:
connect: Invalid argument
But there is a way to overcome this block - one should choose an interface with a -I switch and it all runs smoothly since then. But how can I achieve the same in my client app? What should I do? My client code looks like this:
struct addrinfo hints;
struct addrinfo *server;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
int status;
if((status = getaddrinfo(argv[1], argv[2], &hints, &server) != 0))
{
perror("getaddrinfo error");
return 1;
}
int sock_fd;
struct addrinfo *ptr;
for(ptr=server;ptr!=NULL;ptr=ptr->ai_next)
{
if( (sock_fd = socket(ptr->ai_family,ptr->ai_socktype,ptr->ai_protocol)) == -1)
{
perror("socket error");
continue;
}
if( connect(sock_fd, ptr->ai_addr,ptr->ai_addrlen) == -1 )
{
perror("connect error");
continue;
}
break;
}
You need to specify the interface for IPv6 ping (i.e. -I eth0):
ping6 -I eth0 fe80::208:54ff:fe34:22ae
Using link-local addresses for an IPv6 ping, requires to define what device it must send/receive the packet - each device has a link-local address.
Trying without this, will result in error message like:
--> # ping6 fe80::208:54ff:fe34:22ae
connect: Invalid argument
In this case you have to specify the interface additionally like shown here:
--> # ping6 -I eth0 fe80::208:54ff:fe34:22ae
PING fe80::208:54ff:fe34:22ae(fe80::208:54ff:fe34:22ae) from fe80::208:54ff:fe34:22ae eth0: 56 data bytes
64 bytes from fe80::208:54ff:fe34:22ae: icmp_seq=0 ttl=64 time=0.027 ms
64 bytes from fe80::208:54ff:fe34:22ae: icmp_seq=1 ttl=64 time=0.030 ms
64 bytes from fe80::208:54ff:fe34:22ae: icmp_seq=2 ttl=64 time=0.036 ms
One similar approach you must to follow in your client APP..
Addresses starting with ff... are multicast addresses. Connecting a stream to a multicast address does not work.
Addresses starting with fe80... are link-local addresses, which have an interface identifier associated with them. Try looking at the sockaddr returned from getaddrinfo, is the scope field filled out?
My recommendation is that you turn on the IP6 protocol in the interface/network connection, in addition throw out the ip4 protocol if you still have the error.
On my Linux Box this happened too when i had an ip4-interface active and my application tried to use the ip4-interface with ip6 settings. The same should also be valid for windows.
If something is not clear ask.

How can I get IP information from a UDP socket (Windows C++)?

I tried to find the IP address that my UDP socket is bound to (assuming I don't want to use another method to find the computer's IP address). How can this be done? The code below works for the PORT number, but always returns 0.0.0.0 for the address:
struct sockaddr_in sin;
int addrlen = sizeof(sin);
if(getsockname(clientSock, (struct sockaddr *)&sin, &addrlen) == 0 &&
sin.sin_family == AF_INET &&
addrlen == sizeof(sin)){
printf("RETURNING ADDR: %s: len = %d\n", inet_ntoa(sin.sin_addr),
strlen(inet_ntoa(sin.sin_addr)));
}
The socket was bound using the following code:
sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;//inet_addr("127.0.0.1");
local.sin_port = 0; //assign given port
result = bind(clientSock, (sockaddr*)&local, sizeof(local));
Thank you for any and all help. I appreciate your time!
0.0.0.0 is INADDR_ANY, meaning the socket is bound to all local addresses on the host, not just one address. You are asking for one address, but you are not bound to one address, so getsockname() cannot report a specific address.
If you want getsockname() to report a specific address, you have to bind() to that specific address. So use GetAdaptersAddresses to enumerate all interfaces on the local host and bind() a separate socket to each address, instead of binding INADDR_ANY on a single socket.
Otherwise, you can bind() a single socket to INADDR_ANY, and then use WSARecvMsg() (instead of recv(), recvfrom(), or WSARecvFrom()) to read the incoming packets. WSARecvMsg() can report details about each packet's arrival interface and destination address, if you enable the appropriate options with setsockopt().

C++ Linux getpeername and getsockname return only port

In my Linux C++ application I'm using getpeername and getsockname.
when IPv6 enabled on the OS, both getpeername and getsockname return only port!
code:
int GetSockAndPeer(int sock)
{
struct sockaddr_storage ss;
socklen_t salen = sizeof(ss);
struct sockaddr *sa;
struct addrinfo hints, *paddr, *paddrp;
sa = (struct sockaddr *)&ss;
if (getpeername(sock, sa, &salen) != 0)
{
return -1;
}
if (getsockname(sock, sa, &salen) != 0)
{
return -1;
}
}
sa variable hold after the systemcalls in sa_data only the sa_data[0] and sa_data[1] which means port. all the other bytes are 0;
Any help???
Related to RFC2553 you have to use the IN6_IS_ADDR_V4MAPPED and IN6_IS_ADDR_V4COMPAT macros to identify if there is any usable IPv4 information available within yours socket_storage, or to be exact the sockaddr_in6 structure:
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* transport layer port # */
uint32_t sin6_flowinfo; /* IPv6 traffic class & flow info */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* set of interfaces for a scope */
};
If both macros returns true, the IPv4 address is in sockaddr_in6.sin6_addr[12-15]:
printf("%u.%u.%u.%u\n", sockaddr_in6.sin6_addr[12], sockaddr_in6.sin6_addr[13], \
sockaddr_in6.sin6_addr[14], sockaddr_in6.sin6_addr[15])
It's important to remember that, unless a socket is connected (or, for a connectionless socket, has transferred data), there may not be any IP addresses, local or remote, associated with the socket.
Let's say the computer is multihomed and has both local and Internet IP addresses. Maybe even multiple local network IP addresses. If you choose to bind a socket to "any" local address (using an INADDR_ANY-type flag), or never call bind() in the first place, the socket API does not have a single local IP address associated with the socket, just a port number at the most. When you call connect() on a socket, the system chooses which local IP to use based on who you are connecting to. So if you connect to a machine over the Internet, your Internet IP is associated with the socket, and if you connect to a machine on the local network, your LAN IP address is used.
So may sure that you connect() to a remote computer or bind() to a specific local IP before you use getsockname(). I wonder if enabling IPv6 has caused your machine to see multiple potential local IPs to use. Obviously you much be connected to a machine to use getpeername().