getaddrinfo resolves hostname incorrectly - c++

I try to use getaddrinfo function (http://man7.org/linux/man-pages/man3/getaddrinfo.3.html) to get in_addr structure of IP address, but as far as I see, something is wrong, or I do something wrong. Consider following code snippet.
bool resolve_host (string hostname, in_addr* address) {
cout << "hostname = " << hostname << endl;
addrinfo *res {};
addrinfo hints {0, AF_INET, 0, 0, 0, nullptr, nullptr, nullptr};
auto result = getaddrinfo (hostname.c_str (), nullptr, &hints, &res);
if (!result) {
memcpy (address, &((struct sockaddr_in*) res->ai_addr)->sin_addr, sizeof (in_addr));
freeaddrinfo (res);
cout << "resolved addr = " << inet_ntoa (*address) << endl << endl;
return true;
}
else {
return false;
}
}
With the usage like this:
in_addr addr {};
resolve_host ("www.google.pl", &addr); // 1
resolve_host ("linux-1smb", &addr); // 2
resolve_host ("192.168.100.100", &addr); // 3
resolve_host ("192.168.100.1001", &addr); // 4
resolve_host ("wrong_name", &addr); // 5
Output looks like this:
hostname = www.google.pl
resolved addr = 74.125.71.94
hostname = linux-1smb
resolved addr = 192.168.0.101
hostname = 192.168.100.100
resolved addr = 192.168.100.100
hostname = 192.168.100.1001
hostname = wrong_name
resolved addr = 217.74.65.145
First four calls acts like intended. There is of course an assumption that linux-1smb is a known hostname (added to /etc/hosts). But the last (5) call shows that hostname "wrong_name" (which of course is not added to /etc/hosts) has an IP address 217.74.65.145.
Could you tell me where do I have a mistake (in code, or understanding how getaddrinfo works)?
Content of the /etc/hosts is:
#
# hosts This file describes a number of hostname-to-address
# mappings for the TCP/IP subsystem. It is mostly
# used at boot time, when no name servers are running.
# On small systems, this file can be used instead of a
# "named" name server.
# Syntax:
#
# IP-Address Full-Qualified-Hostname Short-Hostname
#
127.0.0.1 localhost
# special IPv6 addresses
::1 localhost ipv6-localhost ipv6-loopback
fe00::0 ipv6-localnet
ff00::0 ipv6-mcastprefix
ff02::1 ipv6-allnodes
ff02::2 ipv6-allrouters
ff02::3 ipv6-allhosts
192.168.0.101 linux-1smb
225.0.0.37 multi-test
What's worth to be mentioned is, if I change hints, to this:
addrinfo hints {0, AF_UNSPEC, 0, 0, 0, nullptr, nullptr, nullptr};
which is (as far as I know - from documentation) equals to:
getaddrinfo (hostname.c_str (), nullptr, nullptr, &res);
The result of fourth call is:
hostname = 192.168.100.1001
resolved addr = 217.74.65.145
Which is the same IP address like in case of call 5.
Possible solution:
I read in the comment some possible situation and I checked it. It turns out, that the address 217.74.65.145 is an address of my ISP, which basically forwards all unknown hostnames (from browser for example) into this address. I think, that's the reason why getaddrinfo returns this address. I still don't know how to solve this, but now I now why this happens.
Thanks

Related

GetAddrInfo (C++) on Windows not handling IPv6 correctly, returning error code 11004 for domains that resolve fine through other means

Note: I looked extensively at a similar (but not duplicate) question here: GetAddrInfo cannot resolve ipv6.google.com (but nslookup can). This question is 9 years old, the OP is not experiencing the exact same behavior as I am, and the already-accepted answer does not solve my problem—in fact, I am specifically following the advice of that answer to no avail in the sample code below, and I'm getting a different error than on that question. Furthermore, the likelihood that I would succeed in getting anyone to engage on a question that old by merely commenting on it is nil. It is not the same problem as the problem I am having today, so I am created a new, more-detailed question.
I have a Windows 10 machine running Visual Studio 2017 and a Windows Server 2016 machine running Visual Studio 2019. Both of the machines have IPv4 and IPv6 addresses configured. On both machines, nslookup.exe returns the expected results for three different domains:
> nslookup www.google.com
...
Addresses: 2607:f8b0:4002:80a::2004
172.217.3.228
...
> nslookup ipv4.google.com
...
Addresses: 74.125.136.102
...
> nslookup ipv6.google.com
...
Addresses: 2607:f8b0:4002:812::200e
...
Now I'm trying to write an example program that uses GetAddrInfo to lookup these same three domains:
#include <iostream>
#include <sstream>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"WS2_32.Lib")
int main(int argc, char* argv[])
{
WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA wsaData;
int err( WSAStartup(wVersionRequested, &wsaData) );
if (err != 0)
{
std::cout << "WSAStartup failed with error " << err << std::endl << std::flush;
return 1;
}
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM; // so that we get each IP address once, instead of once per socket type
hints.ai_protocol = 0; // we don't care about the protocol
hints.ai_canonname = NULL; // should be NULL for hints
hints.ai_addr = NULL; // should be NULL for hints
hints.ai_next = NULL; // should be NULL for hints
hints.ai_flags = 0;
hints.ai_family = AF_UNSPEC; // I vary this and the hostname below
struct addrinfo* results;
int error = getaddrinfo("ipv6.google.com", NULL, &hints, &results);
if (error != 0)
{
std::ostringstream msg;
std::cout << "Could not resolve hostname due to ";
std::cout << "the following error in getaddrinfo: " << error << ": ";
if (error == -11)
{
std::cout << "EAI_SYSTEM";
}
else
{
std::cout << gai_strerrorA(error);
}
std::cout << std::endl << std::flush;
return 1;
}
for (struct addrinfo* result = results; result != NULL; result = result->ai_next)
{
if (result->ai_family == AF_INET6)
{
char buffer[INET6_ADDRSTRLEN];
std::string ipv6Str(
inet_ntop(
AF_INET6,
&((struct sockaddr_in6*)result->ai_addr)->sin6_addr,
buffer,
sizeof(buffer)));
std::cout << ipv6Str << std::endl << std::flush;
}
else
{
char buffer[INET_ADDRSTRLEN];
std::string ipv4Str(
inet_ntop(
AF_INET,
&((struct sockaddr_in*)result->ai_addr)->sin_addr,
buffer,
sizeof(buffer)));
std::cout << ipv4Str << std::endl << std::flush;
}
}
freeaddrinfo(results);
return 0;
}
For simplicity, I'm just changing the hints.ai_family and hard-coded hostname, then re-compiling and re-running (with Visual Studio). These are the results I get:
Hostname
hints.ai_family
Result
Expected?
www.google.com
AF_UNSPEC
One IPv4 and one IPv6 address
✅
www.google.com
AF_INET
One IPv4 address only
✅
www.google.com
AF_INET6
Err 11004: Name valid, no data
⛔️
ipv4.google.com
AF_UNSPEC
One IPv4 address only
✅
ipv4.google.com
AF_INET
One IPv4 address only
✅
ipv4.google.com
AF_INET6
Err 11004: Name valid, no data
✅
ipv6.google.com
AF_UNSPEC
Err 11004: Name valid, no data
⛔️
ipv6.google.com
AF_INET
Err 11004: Name valid, no data
✅
ipv6.google.com
AF_INET6
Err 11004: Name valid, no data
⛔️
I've hammered at this for a full day now and I can't get getaddrinfo to return the expected results. Why isn't this working? It appears that nslookup.exe using something other than getaddrinfo (perhaps a more low-level query of root and glue name servers), because it works fine there. Also note that this code (with different includes and without the WSA startup procedure) is working just fine in RedHat, CentOS, Ubuntu, and macOS High Sierra.
Some things I've tried other than varying the ai_family, all of which yield the same results as in the table below:
ai_flags = AI_NUMERICSERV
ai_flags = AI_ALL
ai_flags = AI_ALL | AI_NUMERICSERV
ai_socktype = SOCK_DGRAM
ai_socktype = 0
ai_protocol = IPPROTO_TCP
Also, it turns out that ipv6.google.com is actually a CNAME for ipv6.l.google.com, so I tried using that domain, instead, to no avail.

Why does "inet_pton" return 0 when I'm using localhost?

I'm working on a socket interface where an application is trying to connect to another, this is my socket initialisation:
const char* pszLocalHost = "localhost";
int intSocket = socket(AF_INET, SOCK_STREAM, 0);
if ( intSocket == 0 ) {
clsDebugService::exitWhenDebugQueueEmpty("Failed to create socket!");
}
struct hostent* pHostEntry = gethostbyname(pszLocalHost);
if ( pHostEntry == nullptr ) {
clsDebugService::exitWhenDebugQueueEmpty("Unable to resolve ip address!");
}
//Initliase and get address of localhost
struct sockaddr_in srvAddr;
bzero((char*)&srvAddr, sizeof(srvAddr));
//Set-up server address
memcpy(&srvAddr.sin_addr, pHostEntry->h_addr_list[0], pHostEntry->h_length);
srvAddr.sin_family = AF_INET;
srvAddr.sin_port = htons(clsSocketThread::mscuint16Port);
char* pszIP = inet_ntoa(srvAddr.sin_addr);
if ( pszIP != nullptr ) {
qdbg() << "Setting up socket on ip: " << pszIP
<< ", port: " << clsSocketThread::mscuint16Port
<< ((strPurpose.isEmpty() == true) ? "" : strPurpose);
}
socklen_t tSvrAddr = sizeof(srvAddr);
int intRC;
if ( blnIsModule == true ) {
if ( inet_pton(AF_INET, pszLocalHost, &srvAddr.sin_addr) <= 0 ) {
clsDebugService::exitWhenDebugQueueEmpty("Invalid address not supported!");
}
intRC = ::connect(intSocket, (const struct sockaddr*)&srvAddr, tSvrAddr);
}
Using the debugger I have traced an issue to the call to inet_pton, it returns 0, which according to:
https://man7.org/linux/man-pages/man3/inet_pton.3.html
Means:
0 is returned if src does not contain a character string representing a valid network address in the specified address family.
Question is why ?
That same manual clearly states that the first argument must either be an IPv4 address of the form "ddd.ddd.ddd.ddd" or an IPv6 one. Since "localhost" is neither of those, the call fails. Using "127.0.0.1" should work though.
Here's the quote regarding IPv4 from the manual:
This function converts the character string src into a network
address structure in the af address family, then copies the network
address structure to dst. The af argument must be either AF_INET or
AF_INET6. dst is written in network byte order.
The following address families are currently supported:
AF_INET
src points to a character string containing an IPv4 network
address in dotted-decimal format, "ddd.ddd.ddd.ddd", where ddd
is a decimal number of up to three digits in the range 0 to
255. The address is converted to a struct in_addr and copied
to dst, which must be sizeof(struct in_addr) (4) bytes (32
bits) long.

getnameinfo() compile time error - Extracting MAC address in Linux

I have to send to the server the IP address and MAC address of the network interface from which my client socket is connected and communicating with the server.
All machines are on intra-net.
I have extracted the IP of my socket and I am attempting to extract the H/W address.
My strategy :
Extract IP of the socket using getsockname() system call.
Use getifaddrs() system call to list all available network interfaces. Inside a for-loop I am using getnameinfo() system call to find IP for currently iterating interface name and then compare this IP with socket IP (extracted from step 1) to find interface name of the connected socket.
Use ioctl(fd, SIOCGIFHWADDR, &ifr) system call to get H/W address using interface name found out in stage 2.
I am facing problem getnameinfo() system call.
If I don't type cast the first parameter to (struct sockaddr_in*) I get the following error : ai_family not supported
getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST,
NULL, 0, NI_NUMERICHOST);
If I type cast the first parameter to (struct sockaddr_in*) I get the following error : error: cannot convert ‘sockaddr_in*’ to ‘const sockaddr*’ for argument ‘1’ to ‘int getnameinfo(const sockaddr*, socklen_t, char*, socklen_t, char*, socklen_t, int)’
getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST,
NULL, 0, NI_NUMERICHOST);
Kindly advice. I am even open to some alternative strategy to programmatically and dynamically get Socket IP and MAC address.
bool Ethernet::getIp(void)
{
struct sockaddr_in addr;
char bufferIp[INET_ADDRSTRLEN];
socklen_t addrLen = sizeof(addr);
if(getsockname(this->clientSocket, (struct sockaddr*) &addr, &addrLen) == -1)
{
string errStr = strerror(errno);
FileOperations fo;
string str;
str = "Unable to extract IP address of socket";
str += " Error : " + errStr;
fo.printError(str);
return RETURN_FAILURE;
}
if(inet_ntop(AF_INET, &addr.sin_addr, bufferIp, INET_ADDRSTRLEN) == NULL)
{
string errStr = strerror(errno);
FileOperations fo;
string str;
str = "Unable to convert extracted IP address from binary to char* in Ethernet::getInterfaceDetails.";
str += " Error : " + errStr;
fo.printError(str);
return RETURN_FAILURE;
}
this->ip = string(bufferIp);
return RETURN_SUCCESS;
}
bool Ethernet::getMac(void)
{
int fd;
struct ifreq ifr;
// char *iface = "eth0";
unsigned char *mac;
fd = socket(AF_INET, SOCK_DGRAM, 0);
ifr.ifr_addr.sa_family = AF_INET;
strncpy(ifr.ifr_name , this->interfaceName.c_str(), IFNAMSIZ-1);
ioctl(fd, SIOCGIFHWADDR, &ifr);
close(fd);
mac = (unsigned char *)ifr.ifr_hwaddr.sa_data;
//display mac address
printf("Mac : %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n" , mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return RETURN_SUCCESS;
}
bool Ethernet::getInterfaceDetails(void)
{
struct ifaddrs *ifaddr, *ifa;
int s;
char host[NI_MAXHOST];
string tempAddr;
char buffer[INET_ADDRSTRLEN];
if (getifaddrs(&ifaddr) == -1)
{
string errStr = strerror(errno);
FileOperations fo;
string str;
str = "System call 'getifaddrs' failed in Ethernet::getInterfaceDetails.";
str += " Error : " + errStr;
fo.printError(str);
return RETURN_FAILURE;
}
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
{
if (ifa->ifa_addr == NULL)
continue;
this->interfaceName = string(ifa->ifa_name);
if(this->interfaceName == string("lo"))
continue;
s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST,
NULL, 0, NI_NUMERICHOST);
if(s != 0)
{
string errStr = gai_strerror(s);
cout << "Error : " << errStr << endl;
FileOperations fo;
string str;
str = "Unable to convert extracted IP address address from binary to char* in Ethernet::getInterfaceDetails.";
str += " Error : " + errStr;
fo.printError(str);
return RETURN_FAILURE;
}
tempAddr = string(host);
if(tempAddr == this->ip)
{
freeifaddrs(ifaddr);
return RETURN_SUCCESS;
}
}
return RETURN_FAILURE;
}
I see one strange thing regarding your call to getnameinfo: You are assuming that a non-null ifa_addr equals sockaddr_in type, but I could imagine you could get other types, e.g. sockaddr_in6. So you should check ifa_addr->sa_family field to make sure it's AF_INET. Perhaps you should handle IPv6 as well?
My theory here is that calling getnameinfo with a struct size that does not match what would be expected for the address family might be the reason for your error.
Also look at MAC address with getifaddrs for a related discussion.
The getifaddrs manual says
The ifa_addr field points to a structure containing the interface address. (The sa_family subfield should be consulted to determine the format of the address structure.) This field may contain a null pointer.
Thus
check that the field is not a null pointer (if it is, then it is not the IP you're looking for)
the length parameter must match the length of the addresses in sa_family / or filter just AF_INET.
But wouldn't it be easier to just create a datagram socket to the server then ask what's its address, or actually do the http connection and ask what mac address that socket is using?
If this is embedded Linux and the interface is always named the same, just read from /sys/class/net/eth0/address - much easier.
#Antti Haapala, #Mats;
Thanks for the help.
The problem was as you mentioned that other types/families of addresses where present in the interfaces and where causing problems to getnameinfo() system call.
Now I am filtering out other addresses and only allowing AF_INET.
I have added the following validation :
if(ifa->ifa_addr->sa_family != AF_INET)
continue;

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...]

BSD Sockets ip6 inet_pton and how to retrieve the scope ID

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;
}