1

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 :

  1. Extract IP of the socket using getsockname() system call.
  2. 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.
  3. 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;
}
Dark Sorrow
  • 1,681
  • 14
  • 37
  • 1
    Where's the C code? Seems that you're struggling with C++ cast there. – Antti Haapala -- Слава Україні Aug 10 '17 at 11:25
  • *I have to send to the server the IP address and MAC address of the network interface from which my client socket is connected* Why do you need to do this? The server can tell what IP address you're connecting from, and it likely has access to your MAC address also. This seems like it might be [an XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – Andrew Henle Aug 10 '17 at 11:30
  • @AndrewHenle of course it doesn't know it. – Antti Haapala -- Слава Україні Aug 10 '17 at 11:30
  • The program is deployed on embedded hardware (Linux based SBC). Hardware IP and MAC is requested by developers of PC software which I have been asked to provide. – Dark Sorrow Aug 10 '17 at 11:32
  • @AnttiHaapala *of course it doesn't know it* What?!?! [`getpeername()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html) provides the remote IP address of a network connection: "The `getpeername()` function shall retrieve the peer address of the specified socket ..." A *connection* can't work if you don't know who you're talking to. How would a reply packet get back to the sender? – Andrew Henle Aug 10 '17 at 11:33
  • @AndrewHenle of course it doesn't know the *MAC* address. Unless this is some funny layer 2 case. – Antti Haapala -- Слава Україні Aug 10 '17 at 11:35
  • The catch is I have to execute POST request using cUrl library to send this IP, MAC and some large chuck of data (few M.B.) to PHP based server application (web-page). Being a multi-client setup they need the addresses. – Dark Sorrow Aug 10 '17 at 11:36
  • @DarkSorrow `/sys/class/net/eth0/address`? – Antti Haapala -- Слава Україні Aug 10 '17 at 11:55
  • "*Inside a for-loop I am using `getnameinfo()` system call to find IP for currently iterating interface name*" - Why are you using `getnameinfo()` at all? `getifaddrs()` already gives you that information, in the `ifaddrs::ifa_addr` field. You can compare that address to the one obtained from `getsockaddr()`. No `getnameinfo()` call needed. And according to [this answer](https://stackoverflow.com/a/26038501/65863) and [this answer](https://stackoverflow.com/a/12878352/65863), `getifaddrs()` can even give you the MAC addresses as well, so you don't have to go hunting for it. – Remy Lebeau Aug 10 '17 at 20:05

3 Answers3

1

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.

Mats
  • 101
  • 5
0

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.

0

@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;
Dark Sorrow
  • 1,681
  • 14
  • 37