BSD Sockets ip6 inet_pton and how to retrieve the scope ID - c++

I am currently working on an IPv6 class and use inet_pton to retrieve the actual binary representation of the IP from a string i.e.:
AdressV6::AdressV6(const String & _ip)
{
int result = inet_pton(AF_INET6, _ip.c_str(), &(m_nativeAdress));
if(result <= 0)
//throw...
//How can I retrieve the sope ID from that?
}
Is there a common way to do that? Do you just manually parse the string and look for the "%" that does not sound very bullet proof :(
Thank you!
I tried manual parsing for now which seems to work. Still, if there is a better way please let me know:
//retrieve scope ID
uint32 scopeId = 0;
size_t pos = _ip.find("%");
if(pos != String::npos)
{
String theId = _ip.substr(pos+1);
scopeId = atoi(theId.c_str());
}
m_scopeId = scopeId;

On BSD and BSD based systems (this includes MacOS X for example), the scope ID is embedded into the address itself for link local addresses as the second 16 bit word. Please refer to the FreeBSD Handbook and search for "8.1.1.3 Scope Index" (without the quotes).
So assuming that intf1 has scope ID 1 and intf2 has scope ID 2, inet_pton() will convert the strings as follows on these platforms:
"fe80::1234%intf1" -> fe80:1::1234
"fe80::1234%intf2" -> fe80:2::1234
"fe80::1234" -> fe80::1234
The last address is simply unscoped and thus cannot be really used for sending out data.
Please note that this is non-standard; inet_pton() does not work that way on Linux or Windows based systems. However, I think even on Linux and Windows based systems, inet_pton() allows a scope ID at the end, it will simply ignore it, though.
For non-link-local address, this trick doesn't work, of course, yet those addresses are usually not scoped. They can be scoped, but usually every interface has an own, unique interface IPv6 address, based on its interface identifier (even if you use DHCPv6, in which case it has a DHCP address assigned by the DHCP server, as well as the auto generated IPv6 interface address, unless this auto generation has been forbidden).
The struct sockaddr_in6 structure has a field for the scope ID but the RFC that defines this field (RFC 2553 - Section 3.3) does not really give much detail how this field is to be interpreted. It only says:
The mapping of sin6_scope_id to an interface or set of interfaces is
left to implementation and future specifications on the subject of
site identifiers.
So this field is entirely implementation specific.
If you want this field to be filled in correctly, and your code should be as cross-platform as possible, you should use getaddrinfo():
struct addrinfo hints;
struct addrinfo * result;
memset(&hints, 0, sizeof(hints));
// AI_NUMERICHOST prevents usage of DNS servers,
// it tells getaddrinfo that the input string is a numeric IP address.
hints.flags = AI_NUMERICHOST;
if (getaddrinfo("fe80::1234%intf1", NULL, &hints, &result) == 0) {
// result->ai_addr claims to be a pointer to struct sockaddr,
// in fact it will be a pointer to a struct sockaddr_in6 in our case.
struct sockaddr_in6 * so = (struct sockaddr_in6 *)result->ai_addr;
// It will be prefilled like this:
//
// so->sin6_family ==> AF_INET6;
// so->sin6_port ==> 0
// so->sin6_flowinfo ==> 0
// so->sin6_addr ==> fe80::1234
// so->sin6_scope_id ==> "intf1" as scope ID
// Do something with that sockaddr,
// e.g. set a port number and connect a socket to that address.
freeaddrinfo(result);
}
One extra tip: If you want to use the returned getaddrinfo() for a server socket (a socket that you want to bind locally and then call accept() on it), you should also set the passive flag:
hints.flags = AI_NUMERICHOST | AI_PASSIVE;
Not that it will play a role in most case but that is the correct way of using getaddrinfo().

inet_pton() does not support scope IDs. I don't know about other platforms, but on Windows you can use RtlIpv6StringToAddressEx() instead.

inet_pton() semi-supports scope identifiers, the scope is that it will not raise an error when parsing an address with one. The major limitation is that the parameter to the call is a struct in6_addr which does not contain a field for the scope identifier, the super structure struct sockaddr_in6 is required for that.
Easy way forward is to wrap getnameinfo() and getaddrinfo() with struct sockaddr parameters for convenience. For example,
socklen_t
sockaddr_len (
const struct sockaddr* sa
)
{
socklen_t sa_len;
switch (sa->sa_family) {
case AF_INET: sa_len = sizeof(struct sockaddr_in); break;
case AF_INET6: sa_len = sizeof(struct sockaddr_in6); break;
default: sa_len = 0; break;
}
return sa_len;
}
int
sockaddr_ntop (
const struct sockaddr* restrict sa,
char* restrict host,
size_t hostlen
)
{
return getnameinfo (sa, sockaddr_len (sa),
host, hostlen,
NULL, 0,
NI_NUMERICHOST);
}
int
sockaddr_pton (
const char* restrict src,
struct sockaddr* restrict dst /* will error on wrong size */
)
{
struct addrinfo hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM, /* not really */
.ai_protocol = IPPROTO_TCP, /* not really */
.ai_flags = AI_NUMERICHOST
}, *result = NULL;
const int status = getaddrinfo (src, NULL, &hints, &result);
if (0 == status) {
memcpy (dst, result->ai_addr, result->ai_addrlen);
freeaddrinfo (result);
return 1;
}
return 0;
}
To answer the original premise but given a struct sockaddr, an additional API may be warranted, for example:
uint32_t
sockaddr_scope_id (
const struct sockaddr* sa
)
{
uint32_t scope_id;
if (AF_INET6 == sa->sa_family) {
struct sockaddr_in6 s6;
memcpy (&s6, sa, sizeof(s6));
scope_id = s6.sin6_scope_id;
} else
scope_id = 0;
return scope_id;
}

Related

Sockaddr union and getaddrinfo()

I'm writing a UDP socket program which is provided an address from the command line.
To make sending and writing easier, I'm using getaddrinfo to convert the address to a sockaddr struct: either sockaddr_in or sockaddr_in6. Now I understand that I should use a union of sockaddrs:
typedef union address
{
struct sockaddr s;
struct sockaddr_in s4;
struct sockaddr_in6 s6;
struct sockaddr_storage ss;
} address_t;
As I understand they can't be pointers to avoid hiding strict aliasing problems. I'm having trouble seamlessly putting the information from getaddrinfo's addrinfo into this address_t:
struct addrinfo hint, *serv = NULL;
address_t addr;
hint.ai_family = AF_UNSPEC;
hint.ai_flags = 0;
hint.ai_socktype = SOCK_DGRAM;
hint.ai_protocol = IPPROTO_UDP;
ret = getaddrinfo(address_sr.c_str(), s_port.c_str(), &hint, &serv);
//address_sr and s_port are strings with the address and port
switch (serv->ai_addr) {
case AF_INET: {
addr->s4 = * (sockaddr_in*) serv->ai_addr;
//Here I want to fill the address_t struct with information
//This line causes a segfault
}
break;
case AF_INET6: {
addr->s6 = * (sockaddr_in6*) serv->ai_addr;
//Conversion here
}
break;
Also, copying the memory:
memcpy(&addr, serv->ai_addr, serv->ai_addrlen);
Causes a segfault too.
How exactly should I do this? I tried a dozen different ways and I just can't figure it out. How do I put an address from addrinfo to this union? Do I use sockaddr_storage or the sockaddr_ins?
EDIT: Editing for clarity and additional code information.
You need to de-reference the pointer.
addr->s4 = *(sockaddr_in*) serv->ai_addr;
I think you're not getting getaddrinfo right.
About the third argument, :
const struct addrinfo *hints
The hints argument points to an addrinfo structure that specifies
criteria for selecting the socket address structures returned in the
list pointed to by res. If hints is not NULL it points to an addrinfo
structure whose ai_family, ai_socktype, and ai_protocol specify
criteria that limit the set of socket addresses returned by
getaddrinfo()[...]
For example, you can ask for IPv4 address family only, and/or for datagram sockets only (which could be fine given your attempt to use UDP).
Basically, you provide an addrinfo instance, set the fields of interest, then pass a pointer to it to the function, as its third argument:
struct addrinfo hints;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
ret = getaddrinfo(address_sr.c_str(), s_port.c_str(), &hint, &serv);
In this example, the function could return not just one, but a whole list of address structures:
The getaddrinfo() function allocates and initializes a linked list of
addrinfo structures, one for each network address that matches node
and service, subject to any restrictions imposed by hints, and returns
a pointer to the start of the list in res. The items in the linked
list are linked by the ai_next field.
So you have to loop through the function result this way:
for (rp = serv; rp != NULL; rp = rp->ai_next)
I strongly suggest to carefully read the documentation at the link I provided. There is also a long and detailed example which could alone solve your issues.
sockaddr_storage is large enough to hold any sockaddr_... type, thus making address_t just as large. So, I would just memcpy() the entire serv->ai_addr in one operation and get rid of the switch, eg:
struct addrinfo hints = {};
struct addrinfo *addrs, *serv;
address_t addr;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
...
ret = getaddrinfo(address_sr.c_str(), s_port.c_str(), &hint, &addrs);
if (ret == 0)
{
for (serv = addrs; serv != NULL; serv = serv->ai_next)
{
memcpy(&addr, serv->ai_addr, serv->ai_addrlen);
...
}
freeaddrinfo(addrs);
}

getaddrinfo returns previous fixed IP address

Context: My C++ application needs to know on which computer it is running in order to do specific behavior. So my application gets the IP address of the computer and then check that address in a configuration file with an IP list in order to determine the computer's role. All computers have a fixed IP address. I am using gethostname, getaddrinfo and inet_ntop in order to do that.
Problem: On some target platform's computers, getaddrinfo returns the old IP address (the address before I set the fixed value). But if I am doing "ipconfig" in the cmd, the expected fixed value is shown. It is also pingable by other computers. All computers have only 1 network card, IPv6 is disabled.
Platform: Windows 7 x64.
IDE: Visual Studio 2015.
I would like to have the actual fixed IP address.
Thank you for your help!
Code sample:
// [Some stuff...]
addrinfo hints;
addrinfo *pResults;
int returnedCode;
char hostName[1024];
if (0 != (returnedCode = gethostname(hostName, sizeof hostName)))
{
// Error handling stuff.
}
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; // Only want IPv4.
hints.ai_socktype = SOCK_DGRAM; // UDP stream sockets.
hints.ai_flags = AI_PASSIVE; // Fill in my IP for me.
if (0 != (returnedCode = getaddrinfo(hostName, NULL, &hints, &pResults)))
{
// Error handling.
}
struct addrinfo* res;
char buffer[INET_ADDRSTRLEN];
std::string localIP;
for (res = pResults; res != NULL; res = res->ai_next)
{
if (res->ai_family == AF_INET)
{
const char* ip = inet_ntop(AF_INET,
&((struct sockaddr_in *)res->ai_addr)->sin_addr, buffer, INET_ADDRSTRLEN);
if ((NULL == ip) || (nullptr == ip))
{
// Error handling.
}
else
{
localIP = std::string(ip);
}
}
}
freeaddrinfo(pResults); // free the linked-list
WSACleanup();
// [More stuff...]

Howto set the UDP source address on Windows

There seems to be no portable way to set the source IP for sending UDP datagrams from sockets bound to INADDR_ANY, but at least on Linux and FreeBSD it can be done using sendmsg() and the IP_PKTINFO (Linux) or IP_SENDSRCADDR (FreeBSD) option. (See this question.)
Is there an equivalent option to set the UDP source IP on Windows ?
From MSDN:
WSASendMsg function
[...]
On an IPv4 socket of type SOCK_DGRAM or SOCK_RAW, an application can specific the local IP source address to use for sending with the WSASendMsg function. One of the control data objects passed in the WSAMSG structure to the WSASendMsg function may contain an in_pktinfo structure used to specify the local IPv4 source address to use for sending.
The same applies to an IPv6 socket with the in6_pktinfo structure.
For dual-mode sockets, it is important that an IPv4 source address is not specified as an IPv4-mapped IPv6 address in the in6_pktinfo, but as an IPv4 address in the in_pktinfo structure.
Example:
union {
char in[WSA_CMSG_SPACE(sizeof(struct in_pktinfo))];
char in6[WSA_CMSG_SPACE(sizeof(struct in6_pktinfo))];
} cdata;
WSAMSG msg;
memset(&msg, 0, sizeof(msg));
msg.name = &remote_sysaddr.addr.generic;
msg.namelen = remote_sysaddr.len;
msg.lpBuffers = &buf;
msg.dwBufferCount = 1;
msg.Control.buf = (char *)&cdata;
msg.Control.len = sizeof(cdata);
int sum = 0;
WSACMSGHDR *cmsg = WSA_CMSG_FIRSTHDR(&msg);
...
memset(cmsg, 0, WSA_CMSG_SPACE(sizeof(struct in_pktinfo)));
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(struct in_pktinfo));
struct in_pktinfo *pktinfo = (struct in_pktinfo *)WSA_CMSG_DATA(cmsg);
pktinfo->ipi_addr.s_addr = local_addr->ipv4;
sum += WSA_CMSG_SPACE(sizeof(struct in_pktinfo));
...
msg.Control.len = sum;
if (bs->WSASendMsg(bs->socket, &msg, 0, &bytes, NULL, NULL) != 0) {
...
I think this is it.
UdpClient Constructor (IPEndPoint)
This constructor creates a new UdpClient and binds it to the IPEndPoint specified by the localEP parameter. Before you call this constructor, you must create an IPEndPoint using the IP address and port number from which you intend to send and receive data. You do not need to specify a local IP address and port number for sending and receiving data. If you do not, the underlying service provider will assign the most appropriate local IP address and port number.

How to use getnameinfo instead of gethostbyname?

in code :
if ((host = (struct hostent*) gethostbyname(address) ) == 0) // address is a string
I've got warning when cross compiling (generic arm architecture) on 4.5.x gcc :
(.text+0x1558): warning: gethostbyname is obsolescent, use getnameinfo() instead.
getnameinfo is:
int WSAAPI getnameinfo(
__in const struct sockaddr FAR *sa,
__in socklen_t salen,
__out char FAR *host,
__in DWORD hostlen,
__out char FAR *serv,
__in DWORD servlen,
__in int flags
);
And it got more parameters... And I'm confused with it, I just need it work as gethostbyname were working. What parameter to pass to keep it simple stupid as it was with gethostbyname?
Finally here is my try:
struct sockaddr_in servAddr;
struct hostent *host; /* Structure containing host information */
/* open socket */
if ((handle = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
return LILI_ERROR;
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr(address.ptr());
servAddr.sin_port = htons(port);
char servInfo[NI_MAXSERV];
if ( ( host = (hostent*) getnameinfo(
(struct sockaddr *) &servAddr
,sizeof (struct sockaddr)
,address.ptr(), address.size()
,servInfo, NI_MAXSERV
,NI_NUMERICHOST | NI_NUMERICSERV ) ) == 0)
return LILI_ERROR;
if (::connect(handle, (struct sockaddr *) &servAddr, sizeof(servAddr)) < 0)
return LILI_ERROR;
It compiles well and no segmentation fault on start up but I can't connect my server with it :(
gethostbyname() does a name→IP lookup. It should be replaced with getaddrinfo(), which can do the same.
This means the warning is completely wrong. getnameinfo() is the replacement of gethostbyaddr(), both for IP→name lookups. The reverse.
name→IP: gethostbyname(), getaddrinfo()
IP→name: gethostbyaddr(), getnameinfo()
The newer functions can do more: they handle IPv6 and can translate strings like 'http' to 80 (port). In the future they can also determine if e.g. TCP should be used for the service in question or SCTP. The interface is ready.
Beej's explains it pretty good. gethostbyname() does not works well with IPV6 and thus you should use getnameinfo() instead. All you have to do is to fill in the required informations, i.e.
getnameinfo(
&sa, // Pointer to your struct sockaddr
sizeof sa, // Size of this struct
host, // Pointer to hostname string
sizeof host, // Hostname string buffer length
service, // Pointer to service name string
sizeof service, // Service name string buffer length
0 // No flags given
);
Edit: After some research, I've found that
getnameinfo(&sa, sizeof(sa), hostname, size_hostname, NULL, 0, 0);
should be sufficient.
Edit #2 I've noticed you are trying to use the return value of getnameinfo as hostname. But that is not correct, the hostname is saved within the provided host pointer. The return value indicates whether the operation was sufficient. Also have a look at the man page.

How to get your own (local) IP-Address from an udp-socket (C/C++)

You have multiple network adapters.
Bind a UDP socket to an local port, without specifying an address.
Receive packets on one of the adapters.
How do you get the local ip address of the adapter which received the packet?
The question is, "What is the ip address from the receiver adapter?" not the address from the sender which we get in the
receive_from( ..., &senderAddr, ... );
call.
You could enumerate all the network adapters, get their IP addresses and compare the part covered by the subnet mask with the sender's address.
Like:
IPAddress FindLocalIPAddressOfIncomingPacket( senderAddr )
{
foreach( adapter in EnumAllNetworkAdapters() )
{
adapterSubnet = adapter.subnetmask & adapter.ipaddress;
senderSubnet = adapter.subnetmask & senderAddr;
if( adapterSubnet == senderSubnet )
{
return adapter.ipaddress;
}
}
}
The solution provided by timbo assumes that the address ranges are unique and not overlapping. While this is usually the case, it isn't a generic solution.
There is an excellent implementation of a function that does exactly what you're after provided in the Steven's book "Unix network programming" (section 20.2)
This is a function based on recvmsg(), rather than recvfrom(). If your socket has the IP_RECVIF option enabled then recvmsg() will return the index of the interface on which the packet was received. This can then be used to look up the destination address.
The source code is available here. The function in question is 'recvfrom_flags()'
G'day,
I assume that you've done your bind using INADDR_ANY to specify the address.
If this is the case, then the semantics of INADDR_ANY is such that a UDP socket is created on the port specified on all of your interfaces. The socket is going to get all packets sent to all interfaces on the port specified.
When sending using this socket, the lowest numbered interface is used. The outgoing sender's address field is set to the IP address of that first outgoing interface used.
First outgoing interface is defined as the sequence when you do an ifconfig -a. It will probably be eth0.
HTH.
cheers,
Rob
Unfortunately the sendto and recvfrom API calls are fundamentally broken when used with sockets bound to "Any IP" because they have no field for local IP information.
So what can you do about it?
You can guess (for example based on the routing table).
You can get a list of local addresses and bind a seperate socket to each local address.
You can use newer APIs that support this information. There are two parts to this, firstly you have to use the relavent socket option (ip_recvif for IPv4, ipv6_recvif for IPv6) to tell the stack you want this information. Then you have to use a different function (recvmsg on linux and several other unix-like systems, WSArecvmsg on windows) to receive the packet.
None of these options are great. Guessing will obviously produce wrong answers soemtimes. Binding seperate sockets increases the complexity of your software and causes problems if the list of local addresses changes will your program is running. The newer APIs are the correct techical soloution but may reduce portability (in particular it looks like WSArecvmsg is not available on windows XP) and may require modifications to the socket wrapper library you are using.
Edit looks like I was wrong, it seems the MS documentation is misleading and that WSArecvmsg is available on windows XP. See https://stackoverflow.com/a/37334943/5083516
In Linux environment, you can use recvmsg to get local ip address.
//create socket and bind to local address:INADDR_ANY:
int s = socket(PF_INET,SOCK_DGRAM,0);
bind(s,(struct sockaddr *)&myAddr,sizeof(myAddr)) ;
// set option
int onFlag=1;
int ret = setsockopt(s,IPPROTO_IP,IP_PKTINFO,&onFlag,sizeof(onFlag));
// prepare buffers
// receive data buffer
char dataBuf[1024] ;
struct iovec iov = {
.iov_base=dataBuf,
.iov_len=sizeof(dataBuf)
} ;
// control buffer
char cBuf[1024] ;
// message
struct msghdr msg = {
.msg_name=NULL, // to receive peer addr with struct sockaddr_in
.msg_namelen=0, // sizeof(struct sockaddr_in)
.msg_iov=&iov,
.msg_iovlen=1,
.msg_control=cBuf,
.msg_controllen=sizeof(cBuf)
} ;
while(1) {
// reset buffers
msg.msg_iov[0].iov_base = dataBuf ;
msg.msg_iov[0].iov_len = sizeof(dataBuf) ;
msg.msg_control = cBuf ;
msg.msg_controllen = sizeof(cBuf) ;
// receive
recvmsg(s,&msg,0);
for( struct cmsghdr* pcmsg=CMSG_FIRSTHDR(&msg);
pcmsg!=NULL; pcmsg=CMSG_NXTHDR(&msg,pcmsg) ) {
if(pcmsg->cmsg_level==IPPROTO_IP && pcmsg->cmsg_type==IP_PKTINFO) {
struct in_pktinfo * pktinfo=(struct in_pktinfo *)CMSG_DATA(pcmsg);
printf("ifindex=%d ip=%s\n", pktinfo->ipi_ifindex, inet_ntoa(pktinfo->ipi_addr)) ;
}
}
}
The following does not work in asymmetric routing environment.
you can first set SO_REUSEADDR to true
BOOL bOptVal = 1;
setsockopt(so, SOL_SOCKET, SO_REUSEADDR, (char *)&boOptVal, sizeof(bOptVal));
after receive_from( ..., &remoteAddr, ... ); create another socket, and connect back to remoteAddr. Then call getsockname can get the ip address.
SOCKET skNew = socket( )
// Same local address and port as that of your first socket
// INADDR_ANY
bind(skNew, , )
// set SO_REUSEADDR to true again
setsockopt(skNew, SOL_SOCKET, SO_REUSEADDR, (char *)&boOptVal, sizeof(bOptVal));
// connect back
connect(skNew, remoteAddr)
// get local address of the socket
getsocketname(skNew, )
ssize_t
recvfrom(int socket, void *restrict buffer, size_t length, int flags,
struct sockaddr *restrict address, socklen_t *restrict address_len);
ssize_t
recvmsg(int socket, struct msghdr *message, int flags);
[..]
If address is not a null pointer and the socket is not connection-oriented, the
source address of the message is filled in.
Actual code:
int nbytes = recvfrom(sock, buf, MAXBUFSIZE, MSG_WAITALL, (struct sockaddr *)&bindaddr, &addrlen);
fprintf(stdout, "Read %d bytes on local address %s\n", nbytes, inet_ntoa(bindaddr.sin_addr.s_addr));
hope this helps.
Try this:
gethostbyname("localhost");