connect() returns "invalid argument" with ipv6 address - c++

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.

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.

tcpdump shows nothing for my C++ application?

If I run:
iperf -s -u -B 224.0.31.155
and run
sudo tcpdump -ni any 'host 224.0.31.155'
tcpdump is able to capture something:
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
15:49:15.334484 IP [some ip].14386 > 224.0.31.155.14386: UDP, length 1364
15:49:15.334728 IP [some ip].14386 > 224.0.31.155.14386: UDP, length 1374
15:49:15.375026 IP [some ip].14386 > 224.0.31.155.14386: UDP, length 1058
15:49:15.375184 IP [some ip].14386 > 224.0.31.155.14386: UDP, length 832
However, if I kill my iperf process above, and then start my C++ application that also joins the same group and binds the same port, tcpdump no longer sees the traffic.
Here is the snippet:
struct sockaddr_in mc_addr; /* socket address structure */
struct ip_mreq mc_req; /* multicast request structure */
unsigned int from_len = sizeof(mc_addr); /* source addr length */
/* construct a multicast address structure */
memset(&mc_addr, 0, from_len);
mc_addr.sin_family = AF_INET;
inet_aton(mcastGroup.c_str(), &mc_addr.sin_addr);
mc_addr.sin_port = htons(port);
/* bind to multicast address to socket */
if (bind(s, (struct sockaddr *) &mc_addr, sizeof(mc_addr)) < 0) {
std::cerr << "failed to bind to the port " << port << "|error="
<< strerror(errno) << std::endl;
throw;
}
/* construct an IGMP join request structure */
mc_req.imr_multiaddr.s_addr = inet_addr(mcastGroup.c_str());
mc_req.imr_interface.s_addr = htonl(INADDR_ANY);
/* send an ADD MEMBERSHIP message via setsockopt */
if ((setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*) &mc_req,
sizeof(mc_req))) < 0) {
std::cerr << "failed to set socket option to request for membership"
<< std::endl;
throw;
}
tcpdump details:
$ tcpdump --version
tcpdump version 4.1-PRE-CVS_2012_03_26
libpcap version 1.4.0
I just checked it on one of my production servers and it shows the same behavior but I see that my C++ application is processing data properly.
Any idea what's going on?
One potential problem in your code is that you bind your socket to the multicast address. This is not required and may cause all kinds of weird behaviors.
If you only want to send UDP packets you do not need to bind your socket at all. The OS will do it for you.
If you want to send and receive multicast traffic you most likely want to bind to INADDR_ANY on Linux. This is almost an idiom. bind() on UDP sockets has very non-intuitive semantics on Linux. The IP address just has a filtering role. It neither binds to the specified IP address, nor does it bind to the interface associated with that IP address.
Another odd thing is that you assign to mc_req.imr_interface which should not be a member of ip_mreq. I think this should read mc_req.imr_address, but of course if this compiles then I shall stay silent.
IGMP messages are conserved. If the host is already a member of the group, it won't send out a new IGMP membership report message when another application joins. If you're receiving multicasts, be happy.

How to determine IP version in 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
}

Receiving broadcast packet addressed to 255.255.255.255 in C++

I have a device that is discovered by sending a broadcast packet to 255.255.255.255 on port 4930 and the device responds by sending a packet back to 255.255.255.255 on port 4930.
I have a snippet of C++ code which can send a packet to 255.255.255.255 on port 4930 (both source and destination port), but it can't receive a packet back from the broadcast address 255.255.255.255.
I can see the device is working fine, wireshark can see the packets coming back and forth and the propriety software supplied with the device can discover the device just fine, the problem is with the C++ program so please keep on topic with your responses.
Now, as I have said I can send a packet just find, but firstly I can't bind to the IP address 255.255.255.255 to receive the packets. I can change the multicast address to 239.255.255.250 and the socket will bind but I need the address 255.255.255.255.
My snippet of code is below, I am using VC++2010
bool CPTUProgramDlg::FindPTU(u_short port, const char * Destaddress){
{
//Data to send
char packet_data[10] = {0x44,0x43,0x55,0x44,0x5f,0x50,0x49,0x4e,0x47,0x00};
int packet_size=10;
SOCKET sock;
struct sockaddr_in addr;
sock = socket(AF_INET, SOCK_DGRAM, 0);
// set SO_BROADCAST on a socket to true (1): (so we can transmit to 255 addr)
//In order to use broadcast the options of socket must change
char broadcastON = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcastON, sizeof broadcastON);
if (sock < 0)
return false;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(Destaddress); // Specify dest IP
sendto(sock, packet_data, packet_size, 0, (struct sockaddr*)&addr, sizeof(addr));
if (bind(sock,(struct sockaddr*) &addr,sizeof(addr)) != -1){
char Buff[512];
recv(sock,Buff,512,0);
}
closesocket(sock);
}
return 1;
}
Wireshark screenshot to prove packets are being send:
From the wireshark output its seen that the special device is using broadcast to communicate and will use the same port number as source and destination.
Normal socket communication will require using matching port numbers but broadcast messages cannot be exchanged over the same socket, especially when the port numbers do not match as seen with wireshark.
Binding on 255.255.255.255 (INADDR_BROADCAST) should generally work but may be limited by your OS privileges and permissions.
You may try to solve the problem by using two sockets - one for receiving and one for sending. Of course the listening socket have to be setup first and bound to 0.0.0.0 (INADDR_ANY) and port 4930. In this case there is no easy way to filter by destination address (as I wrongly written in my comment) because most standard socket APIs do not provide a way to get the destination addess from the socket. On Linux there is an exception - IP_PKTINFO at SOL_IP...
By using recvfrom you will get the source unicast address of the responding device(s). You have to note that if you have more that one such device on your network you will get more than one response.

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().