I'm trying to ping over IPv6, I need it to get a MAC address from a remote host. As far as I understand, IPv6 does not use the ARP table, but with the help of the so-called ICMPv6 it is possible to get the MAC address, I used tcpdump and saw MAC addresses. If I'm wrong about something, please correct me.
So, I am using Windows and C++,I'm trying to use the Icmp6SendEcho2() function from IcmpAPI.h
and ran into an error 11050 which says that "IP_GENERAL_FAILURE, A general failure. This error can be returned for some malformed ICMP packets.", and I don't know what to do next.
ULONG icmpReplyCount = 0;
HANDLE icmpHandle = INVALID_HANDLE_VALUE;
CHAR icmpEchoBuffer[2048] = { 0 };
IP_OPTION_INFORMATION pingOptions = { 255, 0, IP_FLAG_DF, 0 };
CHAR icmpReplyBuffer [2048];
ULONG icmpReplyLength = 0;
DWORD timeout = 5;
DWORD lastErr = 0;
icmpHandle = Icmp6CreateFile();
struct sockaddr_storage sockStoreLocal = {};
sockaddr_in6* icmp6LocalAddr = (struct sockaddr_in6*)&sockStoreLocal;
icmp6LocalAddr->sin6_family = AF_INET6;
icmp6LocalAddr->sin6_flowinfo = 0;
icmp6LocalAddr->sin6_port = htons(0);
inet_pton(AF_INET6, "fc00:1:1::48", &icmp6LocalAddr->sin6_addr);
struct sockaddr_storage sockStoreRemoute = {};
sockaddr_in6* icmp6RemoteAddr = (struct sockaddr_in6*)&sockStoreRemoute;
icmp6RemoteAddr->sin6_family = AF_INET6;
icmp6RemoteAddr->sin6_port = htons(58);
inet_pton(AF_INET6, "fc00:1:1::109", &icmp6RemoteAddr->sin6_addr);
icmpReplyCount = Icmp6SendEcho2(icmpHandle, NULL, NULL, NULL, icmp6LocalAddr, icmp6RemoteAddr, icmpEchoBuffer, sizeof(icmpEchoBuffer), &pingOptions, icmpReplyBuffer, sizeof(icmpReplyBuffer), timeout);
lastErr = GetLastError();
Maybe I'm converting the IPv6 address incorrectly or what I'm doing wrong with the buffers.
icmpReplyCount = 0 and lastErr = 11050
I found the answer, the buffers were not large enough, I increased the size and everything worked, no errors returned)))
Related
I have code to send a UDP packet from a specific source IP (see below).
This worky nicely on all system I tried so far, including FreeBSD.
Unfortunately on a client system sendmsg() fails with "invalid argument" error and I'm unable to figure out why.
The FreeBSD versions are the same, tests on all system use the same kind of IPv4 addresses for source and destination.
I did a ktrace, but only shows part of the paramers used (the sockaddr_in6), but those seem fine. Valgrind also didn't complain (on my system).
How do I find this ? Is there a tool that displays the full msghdr struct for sendmsg() calls ?
Update: Please focus on the tools or techniques I could use. You can look at the code snippet, but it won't compile without the surounding code.
ssize_t UDPSendWithSourceIP(int fd, void * data, size_t len, const sockaddr_in6 & toAddress)
{
struct sockaddr_in6 dest = toAddress;
// set source address
PIPSocket::Address src = RasServer::Instance()->GetLocalAddress(toIP);
struct msghdr msgh = { };
struct cmsghdr *cmsg;
struct iovec iov = { };
char cbuf[256];
memset(&cbuf, 0, sizeof(cbuf));
// Set up iov and msgh structures
memset(&msgh, 0, sizeof(struct msghdr));
iov.iov_base = data;
iov.iov_len = len;
msgh.msg_iov = &iov;
msgh.msg_iovlen = 1;
msgh.msg_name = (struct sockaddr*)&dest;
// must pass short len when sending to IPv4 address on Solaris 11, OpenBSD and NetBSD
// sizeof(dest) is OK on Linux and FreeBSD
size_t addr_len = sizeof(sockaddr_in);
if (toIP.GetVersion() == 6)
addr_len = sizeof(sockaddr_in6);
msgh.msg_namelen = addr_len;
if ((((struct sockaddr*)&dest)->sa_family == AF_INET6)) {
struct in6_pktinfo *pkt;
msgh.msg_control = cbuf;
msgh.msg_controllen = CMSG_SPACE(sizeof(*pkt));
cmsg = CMSG_FIRSTHDR(&msgh);
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(*pkt));
pkt = (struct in6_pktinfo *) CMSG_DATA(cmsg);
memset(pkt, 0, sizeof(*pkt));
pkt->ipi6_addr = src;
msgh.msg_controllen = cmsg->cmsg_len;
} else
{
#ifdef IP_SENDSRCADDR // FreeBSD
struct in_addr *in;
msgh.msg_control = cbuf;
msgh.msg_controllen = CMSG_SPACE(sizeof(*in));
cmsg = CMSG_FIRSTHDR(&msgh);
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_SENDSRCADDR;
cmsg->cmsg_len = CMSG_LEN(sizeof(*in));
in = (struct in_addr *) CMSG_DATA(cmsg);
*in = src;
#endif // IP_SENDSRCADDR
}
ssize_t bytesSent = sendmsg(fd, &msgh, 0);
if (bytesSent < 0) {
cerr << "RTP\tSend error " << strerror(errno) << endl;
}
return bytesSent;
}
It turns out FreeBSD is very picky when it allows the use of IP_SENDSRCADDR on a UDP socket. If the socket is bound to INADDR_ANY my code works fine. If the socket is bound to a single IP, then sendmsg() returns EINVAL (invalid argument).
I am getting an error when I try to connect to my ipv4 server. Currently the ios app users are required to enter their sever's an IP address, port, and account information.
The ios app then calls Connect on the SocketSender class (included in the header search path) which in turns calls the connect function of Socket.h and then checks the results.
Connect - SocketSender.cpp
bool SocketSender::Connect (const char *host, int port, CApiError &err)
{
errno = 0;
struct hostent *hostinfo;
hostinfo = gethostbyname (host);
if (!hostinfo) {
#ifdef PLATFORM_WIN32
m_nLastErrorNo = SOCKET_ERRNO();
err.SetSystemError(m_nLastErrorNo);
#else
/* Linux stores the gethostbyname error in h_errno. */
m_nLastErrorNo = EINVAL; // h_errno value is incompatible with the "normal" error codes
err.SetError(FIX_SN(h_errno, hstrerror(h_errno)), CATEGORY_SYSTEM | ERR_TYPE_ERROR);
#endif
return false;
}
socket_fd = socket (AF_INET, SOCK_STREAM, 0);
if (socket_fd == -1) {
m_nLastErrorNo = SOCKET_ERRNO();
err.SetSystemError(m_nLastErrorNo);
return false;
}
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons (port);
address.sin_addr = *(struct in_addr *) *hostinfo->h_addr_list;
int result;
SetSocketOptions();
result = connect (socket_fd, (struct sockaddr *) &address, sizeof (address));
if (result == -1) {
if (IS_IN_PROGRESS()) {
fd_set f1,f2,f3;
struct timeval tv;
/* configure the sets */
FD_ZERO(&f1);
FD_ZERO(&f2);
FD_ZERO(&f3);
FD_SET(socket_fd, &f2);
FD_SET(socket_fd, &f3);
/* we will have a timeout period */
tv.tv_sec = 5;
tv.tv_usec = 0;
int selrez = select(socket_fd + 1,&f1,&f2,&f3,&tv);
if (selrez == -1) { // socket error
m_nLastErrorNo = SOCKET_ERRNO();
Disconnect(true);
err.SetSystemError(m_nLastErrorNo);
return false;
}
if (FD_ISSET(socket_fd, &f3)) { // failed to connect ..
int sockerr = 0;
#ifdef PLATFORM_WIN32
int sockerr_len = sizeof(sockerr);
#else
socklen_t sockerr_len = sizeof(sockerr);
#endif
getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &sockerr_len);
if (sockerr != 0) {
m_nLastErrorNo = sockerr;
} else {
#ifdef PLATFORM_WIN32
m_nLastErrorNo = ERROR_TIMEOUT; // windows actually does not specify the error .. is this ok?
#else
m_nLastErrorNo = ETIMEDOUT;
#endif
}
Disconnect(true);
err.SetSystemError(m_nLastErrorNo);
return false;
}
if (!FD_ISSET(socket_fd, &f2)) { // cannot read, so some (unknown) error occured (probably time-out)
int sockerr = 0;
#ifdef PLATFORM_WIN32
int sockerr_len = sizeof(sockerr);
#else
socklen_t sockerr_len = sizeof(sockerr);
#endif
getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&sockerr, &sockerr_len);
if (sockerr != 0) {
m_nLastErrorNo = sockerr;
} else {
#ifdef PLATFORM_WIN32
m_nLastErrorNo = ERROR_TIMEOUT; // windows actually does not specify the error .. is this ok?
#else
m_nLastErrorNo = ETIMEDOUT;
#endif
}
Disconnect(true);
err.SetSystemError(m_nLastErrorNo);
return false;
}
#ifndef PLATFORM_WIN32 // FIXME: is the same needed for windows ?
// unix always marks socket as "success", however error code has to be double-checked
int error = 0;
socklen_t len = sizeof(error);
if (getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
err.SetSystemError();
return false;
}
if(error != 0) {
m_nLastErrorNo = error;
Disconnect(true);
err.SetSystemError(m_nLastErrorNo);
return false;
}
#endif
} else {
m_nLastErrorNo = SOCKET_ERRNO();
Disconnect(true);
err.SetSystemError(m_nLastErrorNo);
return false;
}
}
m_nIP = ntohl(address.sin_addr.s_addr);
m_bServerSocket = false;
return true;
}
That is the original version that worked without any problems. When i changed the above to use AF_INET6 and in_addr6->sin6_addr, i kept getting errors and the application failed to connect. I tried using getaddrinfo but this still did not connect.
struct addrinfo hints, *res, *res0;
int error;
const char *cause = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_DEFAULT;
error = getaddrinfo(host, "PORT", &hints, &res0);
if (error) {
errx(1, "%s", gai_strerror(error));
/*NOTREACHED*/
}
socket_fd = -1;
printf("IP addresses for %s:\n\n", host);
int result;
void *addr;
char *ipver;
for (res = res0; res!=NULL; res = res->ai_next) {
socket_fd = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (socket_fd < 0) {
cause = "socket";
continue;
}
if ((result = connect(socket_fd, res->ai_addr, res->ai_addrlen)) < 0) {
cause = "connect";
close(socket_fd);
socket_fd = -1;
continue;
}
// get the pointer to the address itself,
// different fields in IPv4 and IPv6:
if (res->ai_family == AF_INET) { // IPv4
struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else { // IPv6
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
}
SetSocketOptions();
break; /* okay we got one */
}
I need to make it backwards compatible with ipv6 and ipv4. Any help would be much appreciated as i have been stuck testing this for the past week. Also if anyone knows how to debug the SocketSender.cpp on XCode that would be alot of help.
So after two weeks of testing out different approaches and familiarizing myself with networking (POSIX) I finally got this to work mainly due to #user102008 suggestion.
This is relevant to Client-Server applications.My application is a client application that connects to a IPv4 server/system at a remote location. We have yet to support IPv6 for our products which include clients(iOS,android,windows,unix) and servers (windows & unix), but will support upon future releases. The reason for this support was solely due to Apple changing their apple review process environment.
Approach, Tips and Issues
Apple has provided a way to test IPv6 compatibility with your app. This is sharing your connection from your Ethernet using NAT64/DNS64. This failed many times for me. After researching and resetting my SMC, I came across this article and realized i may have been messing with the configuration too much. So I reset my SMC, restarted and created the internet sharing host. Always remember to turn off WiFi before making any changes to internet sharing.
Users were required to connect to the server with a IPv4 IP address. The Application ran perfectly on an IPv4 networking but failed in an IPv6 network. This was due to the app not resolving the IP address literal. The networking library my application uses was a cpp library that was included as a preprocess macro. One of the biggest annoyance was trying to debug, because you cant debug compile time code. So what I did was move over my cpp files with their headers to the project (luckily it was only 3 files).
IMPORTANT TO AYONE PASSING PORT NUMBERS. This is tied to #2 and resolving the IPv4 literal. I used apples exact implementation on the networking overview (listing 10-1). Every time i tested, the connect function was returning -1, meaning it did not connect. Thanks to #user102008 providing me with this article, I realized apple implementation of getaddrinfo was broken when trying to pass a string literal for the port. Yes, they ask for a constant char, even when trying c_str() it would still return port number of 0. For this reason, an apple developer who Ive noticed answer and addresses countless networking problems provided a work around. This fixed my issue of the port continuously returning 0, code is posted below as well. What i did, was simply add this into my networking class (SocketSender.cpp) and instead of calling getaddrinfo in Connect, i called get getaddrinfo_compat. This allowed me to connect perfectly in IPv4 and IPv6 networks.
static int getaddrinfo_compat(
const char * hostname,
const char * servname,
const struct addrinfo * hints,
struct addrinfo ** res
) {
int err;
int numericPort;
// If we're given a service name and it's a numeric string, set `numericPort` to that,
// otherwise it ends up as 0.
numericPort = servname != NULL ? atoi(servname) : 0;
// Call `getaddrinfo` with our input parameters.
err = getaddrinfo(hostname, servname, hints, res);
// Post-process the results of `getaddrinfo` to work around <rdar://problem/26365575>.
if ( (err == 0) && (numericPort != 0) ) {
for (const struct addrinfo * addr = *res; addr != NULL; addr = addr->ai_next) {
in_port_t * portPtr;
switch (addr->ai_family) {
case AF_INET: {
portPtr = &((struct sockaddr_in *) addr->ai_addr)->sin_port;
} break;
case AF_INET6: {
portPtr = &((struct sockaddr_in6 *) addr->ai_addr)->sin6_port;
} break;
default: {
portPtr = NULL;
} break;
}
if ( (portPtr != NULL) && (*portPtr == 0) ) {
*portPtr = htons(numericPort);
}
}
}
return err;
}
I actually save IP (address.sin_addr.s_addr) in a long data type that is a private variable, m_nIP. problem was i didnt need the IPv6 as our entire product groups use IPv4. Solved this using the code is below.
const uint8_t *bytes = ((const struct sockaddr_in6 *)addrPtr)->sin6_addr.s6_addr;
bytes += 12;
struct in_addr addr = { *(const in_addr_t *)bytes };
m_nIP = ntohl(addr.s_addr);
RELEVANT GUIDES Beej's Guide to Network Programming, UserLevel IPv6 Intro, Porting Applications to IPv6
I have the following UDP client communication code:
clUDPPort::clUDPPort(int prt,string hostname){ //client
nServerPort = prt;
szHostName = hostname;
nSocketId = socket (AF_INET , SOCK_DGRAM, 0 ) ;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(nServerPort) ;
host = gethostbyname(szHostName.c_str()) ;
memcpy( (char*)&serverAddr.sin_addr,(char*) host->h_addr , host->h_length ) ;
//nSize = sizeof(serverAddr);
}
This code is part of a CGI application that is installed on an embedded linux device. When testing on my PC, the host name is passed as local address like 192.168.2.50, when deployed to the device the host name is 127.0.0.1 because there is a service program that my application connects with.
The problem is a linking warning saying:
/home/abdalla/XML_Communication/udpport.cpp:24: warning: gethostbyname is obsolescent, use getnameinfo() instead.
This is generated by KDevelop 4.7 on openSUSE 13.2 64-bit. I tried to use getnameinfo but could not figure out how to make it work because according to the documentation it asks for host name and server name. Apparently in my code the host and the server are the same. Can you help me making getnameinfo working for my code ? Thanks.
What you actually need to translate a name into address is probably getaddrinfo. See the linux man page for details which include sample code.
Here is my code which is using getaddrinfo().
///the common socket address. we will use this structure to describe all Internet address.
struct nw_sockaddr_t
{
struct sockaddr addr; //currently 16 bytes
unsigned int addrlen;
};
bool nw_sock_addr_fill(nw_sockaddr_t& addr, const char* node, const char* service)
{
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = addr.addr.sa_family;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo* ai = NULL, *ai_o = NULL;
bool good = false;
do
{
if(0!=getaddrinfo(node, service, &hints, &ai))
break;
ai_o = ai;
if(NULL!=ai)
{
if(NULL==ai->ai_next)
{
memcpy(&addr.addr, ai->ai_addr, sizeof(addr.addr));
addr.addrlen = ai->ai_addrlen;
good = true;
}
else
{
while(NULL!=ai->ai_next)
{
if(ai->ai_family==AF_INET || ai->ai_family==AF_INET6)
break;
ai = ai->ai_next;
}
if(NULL!=ai)
ai = ai_o;
memcpy(&addr.addr, ai->ai_addr, sizeof(addr.addr));
addr.addrlen = ai->ai_addrlen;
good = true;
}
}
}while(false);
if(NULL!=ai_o)
freeaddrinfo(ai_o);
if(good)
{
if(AF_INET==addr.addr.sa_family)
{
struct sockaddr_in* in = (sockaddr_in*)(&addr.addr);
if(0==in->sin_port)
in->sin_port = htons(ut_n2u32(service, strlen(service)));
}
else if(AF_INET6==addr.addr.sa_family)
{
struct sockaddr_in6* in = (sockaddr_in6*)(&addr.addr);
if(0==in->sin6_port)
in->sin6_port = htons(ut_n2u32(service, strlen(service)));
}
}
return good;
}
Usage:
nw_sockaddr_t addr;
if(nw_sock_addr_fill(addr, "127.0.0.1", "80"))
{
//here you will get the ipv4 address
struct sockaddr_in* addr_in = (struct sockaddr_in*)(&addr.addr);
}
I needed to open a socket from a specific local network card using WinSock. I asked about this and got an answer here. In short, the answer advises that you first bind to the local interface, then call connect.
However, when I do this, I get a WSAEADDRNOTAVAIL (10049) "The requested address is not valid in its context.". Why is this?
Assuming the sample code below is part of an application running on the local box 192.168.1.3 and is attempting to connect to remote server 192.168.1.4. I've checked and double-checked that the local and remote addresses are correct. I can ping both ways (from local to remote and remote to local).
I've tried ports other than 0 for the local; no difference. If I remove the bind before the connect, it then works, but I'm then not able to specify a network interface.
So, any idea why I keep getting WSAEADDRNOTAVAIL ?
addrinfo localhints = {0};
localhints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
localhints.ai_family = AF_INET;
localhints.ai_socktype = SOCK_STREAM;
localhints.ai_protocol = IPPROTO_TCP;
addrinfo *localaddr = NULL;
getaddrinfo("192.168.1.3", "0", &localhints, &localaddr);
bind(s, localaddr->ai_addr, localaddr->ai_addrlen);
freeaddrinfo(localaddr);
addrinfo remotehints = {0};
remotehints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
remotehints.ai_family = AF_INET;
remotehints.ai_socktype = SOCK_STREAM;
remotehints.ai_protocol = IPPROTO_TCP;
addrinfo *remoteaddr = NULL;
getaddrinfo("192.168.1.4", "12345", &remotehints, &remoteaddr);
connect(s, remoteaddr->ai_addr, remoteaddr->ai_addrlen);
freeaddrinfo(remoteaddr);
EDIT: This sample code intentionally has no error checking, so that my intent could be communicated in the most efficient way.
EDIT 2: A bind to 192.168.1.3 causes connect to fail. A bind to 127.0.0.1 works. Yes, I'm 100% sure that 192.168.1.3 is the correct local IP.
EDIT 3: Right! On a whim, I tried the test app on my home PC and it works fine. So, at least the code does work, and the trouble must be related to my work PC.
OK, turns out #claptrap was right.
I had disabled my firewall, but unknown to me, our IT department has some other firewally-thingy tightly integrated into the enterprise virus scanner (which I can't uninstall or disable). Once I managed to get IT to temporarily disabled it, everything worked as expected.
Always check error codes when calling API functions, eg:
addrinfo localhints = {0};
localhints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
localhints.ai_family = AF_INET;
localhints.ai_socktype = SOCK_STREAM;
localhints.ai_protocol = IPPROTO_TCP;
addrinfo *localaddr = NULL;
int ret = getaddrinfo("192.168.1.3", "0", &localhints, &localaddr);
if (ret == 0)
{
ret = bind(s, localaddr->ai_addr, localaddr->ai_addrlen);
freeaddrinfo(localaddr);
if (ret == 0)
{
addrinfo remotehints = {0};
remotehints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
remotehints.ai_family = AF_INET;
remotehints.ai_socktype = SOCK_STREAM;
remotehints.ai_protocol = IPPROTO_TCP;
addrinfo *remoteaddr = NULL;
ret = getaddrinfo("192.168.1.4", "12345", &remotehints, &remoteaddr);
if (ret == 0)
{
ret = connect(s, remoteaddr->ai_addr, remoteaddr->ai_addrlen);
freeaddrinfo(remoteaddr);
if (ret == 0)
{
// connect succeeded...
// can use getsockname() here to discover which port was actually bound to, if needed.
// On some OS versions, bind() does not perform the actual binding immediately,
// connect() does the actual binding instead since it can make more informed choices based on the target address being connected to...
}
else
{
// connect failed...
}
}
else
{
// getaddrinfo() failed
}
}
else
{
// bind failed
}
}
else
{
// getaddrinfo() failed...
}
How far does the code actually get?
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;
}