Trying to do functional programming in C++ and getting bit by some kind of mutation rule.
I have this code:
bpf_u_int32 netp, maskp;
struct in_addr addr;
// ...code
auto set_address_string = [&](auto &do_assign) {
if ((do_assign = inet_ntoa(addr)) == NULL)
cerr << "error - inet_ntoa() failed" << endl;
else
cout << "Some IP: " << do_assign << endl;
};
char *net, *mask;
addr.s_addr = netp;
set_address_string(net);
addr.s_addr = maskp;
set_address_string(mask);
cout << string(net) << endl;
Which is printing the value of mask when I expected the contents of net to be printed, say that net is supposed to be "10.0.0.0" and mask is supposed to be "255.255.255.0".
I assume this has to do with the & I used for the variable do_assign?
EDIT:
Actual output is:
Some IP: 10.0.0.0
Some IP: 255.255.255.0
255.255.255.0
but expected was:
Some IP: 10.0.0.0
Some IP: 255.255.255.0
10.0.0.0
From the manual:
The inet_ntoa() function converts the Internet host address in, given
in network byte order, to a string in IPv4 dotted-decimal notation.
The string is returned in a statically allocated buffer, which
subsequent calls will overwrite.
Both net and mask will point to the same buffer, and, of course, the buffer will contain the result of the last call.
Related
Edit: Upon Checking on IPv6 Test I Found out My ISP does not provide IPv6... otherwise this code is good
getaddrinfo() always fails for IPv6 with the error code 11268096, but it is successful for IPv4.
Setting Hint.ai_family = AF_INET6; is what triggers the error, but I do not know why.
Also, how do I get the sin_port/sin6_port in numbers? I am always getting port 0.(As #Remy Lebeau pointed out I am only asking for IP of the Domain so it won't output port...)
void GetAddrInfoFromHostNameIPV6(const char* DomainName, addrinfo* Result, bool& IsSuccessful)
{
IsSuccessful = false;
addrinfo Hint;
addrinfo* Return = nullptr;
int ErrorCode;
memset(&Hint, 0, sizeof(Hint));
Hint.ai_family = AF_INET6;
Hint.ai_socktype = SOCK_STREAM;
//Hint.ai_socktype = SOCK_DGRAM;
ErrorCode = getaddrinfo(DomainName, NULL, &Hint, &Return) << '\n';
if (ErrorCode != 0)
{
std::cout << "\n Error GetAddrInfoFromHostNameIPV6() Failed with Error Code: " << ErrorCode << " in GetAddrInfoFromHostName In: NW_P!";
}
else
{
*Result = *Return;
char IpAddress[INET6_ADDRSTRLEN];
uint16_t Port;
inet_ntop(AF_INET6, &((sockaddr_in6*)((Result)->ai_addr))->sin6_addr, IpAddress, INET6_ADDRSTRLEN);
Port = *(&((sockaddr_in6*)(Result->ai_addr))->sin6_port);
std::cout << "\n IPV6 Address of Domain '" << DomainName << "' Is " << IpAddress << " With Port: " << Port;
IsSuccessful = true;
}
if (!IsSuccessful)// For the safe of readability
{
std::cout << "\n Error GetAddrInfoFromHostName() Failed in NW_P!\n";
}
}
You are bit-shifting the error code 10 bits to the left before assigning it to ErrorCode.
Decimal 11268096 is binary 101010111111000000000000. Notice all those extra zeros on the right?
You need to get rid of << '\n' after getaddrinfo() returns, it doesn't belong there, as you are not outputting the error code to std::cout on that line of code.
Removing the bit shifting, the real error code is 11004 (binary 10101011111100) which is WSANO_DATA:
Valid name, no data record of requested type.The requested name is valid and was found in the database, but it does not have the correct associated data being resolved for. The usual example for this is a host name-to-address translation attempt (using gethostbyname or WSAAsyncGetHostByName) which uses the DNS (Domain Name Server). An MX record is returned but no A record—indicating the host itself exists, but is not directly reachable.
You can pass the error code to gai_strerror() to get a human-readable string for your error message output, eg:
std::cout << "\n Error GetAddrInfoFromHostNameIPV6() Failed with Error Code: " << ErrorCode << " (" << gai_strerror(ErrorCode) << ") in GetAddrInfoFromHostName In: NW_P!";
As for the port number being 0, you are not asking getaddrinfo() to parse any service name/port string as input (the pServiceName parameter is NULL), you are only asking for translating a domain name into an IP, so it is not going to output any port number. Port numbers are not used by Domains themselves. Port numbers are used by services (HTTP, etc) that are running on servers where domains/IPs point to.
On a side note, you are leaking the addrinfo list that getaddrinfo() outputs. You need to call freeaddrinfo() when you are done using the list.
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.
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;
I'm astonished by the lack of documentation on miniupnp, I believe there's a lot of people using it, but almost no documentation at all, I found a piece of code in the source of RakNet to guide me.
Now I'm having a conceptual issue...
I'm developing an app that connects to a server via UDP (the server should be accessible, the server UDP port is a specific one which is open, and I can test this using any open port checker), then the server puts two or more clients talking to each other (p2p), so I need to circumvent NAT in the clients for that to work.
I already have NAT punch through working, and that already solves lots of cases.
Now, I want to add the UPNP functionality, to attack the NAT issue with this too.
I'm using miniupnpc, and I handle the connections with Boost.Asio.
struct UPNPDev * devlist = 0;
devlist = upnpDiscover(2000, 0, 0, 0, 0, 0);
if (devlist) {
std::cout << "\nList of UPNP devices found on the network :\n";
struct UPNPDev * device;
for(device = devlist; device; device = device->pNext) {
std::cout << "\ndesc: " << device->descURL << "\n st: " << device->st << "\n";
}
char lanaddr[64]; /* my ip address on the LAN */
struct UPNPUrls urls;
struct IGDdatas data;
if (UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr)) == 1) {
string port = lexical_cast<string>(socket->local_endpoint().port());
int r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
port.c_str(), port.c_str(), lanaddr, 0, "UDP", 0, "0");
if (r != UPNPCOMMAND_SUCCESS) {
std::cout << "\nupnp fail";
}
char intPort[6];
char intClient[16];
char desc[128];
char enabled[128];
char leaseDuration[128];
r = UPNP_GetSpecificPortMappingEntry(urls.controlURL,
data.first.servicetype,
port.c_str(), "UDP", 0,
intClient, intPort,
desc, enabled, leaseDuration);
if (r != UPNPCOMMAND_SUCCESS) {
std::cout << "\nupnp fail";
}else {
std::cout << "\nupnp success on port " << port;
}
}
}
As you can see, I execute the UPNP after having bound the socket (I bind it without an specific port, like this:)
asio::udp::socket socket(*this->ioService, asio::udp::endpoint());
My question is, does this UPNP execution makes any sense? Will this socket actually use the UPNP port map if I execute the UPNP on the random port bound to the socket AFTER I bind it?
Thanks guys!
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