Search code examples
c++c++11network-programmingioctl

Devices networking on C++


I have the need to access the network interfaces on my device. I have the below code that does that but I am missing the ones that are active but with no IP address associated to them. Any idea on how programmatically I could gather the information from those?

The code posted has the functions to get the information from the interface, function to list all interfaces and a main to test.

Thanks

struct iface {
    std::string name;
    std::string address;
    std::string netmask;
    std::string broadcast;
    std::string hwaddr;
    int mtu;
};

void print_iface(iface s) {
    std::cout << s.name << ": <UP, RUNNING>" << std::endl;
    std::cout << "  mtu: " << s.mtu << std::endl;
    std::cout << "  hwaddr: " << s.hwaddr << " " << std::endl;
    std::cout << "  inet: " << s.address << " " << std::endl;
    std::cout << "  netmask: " << s.netmask << " " << std::endl;
    std::cout << "  broadcast: " << s.broadcast << std::endl;
}

std::tuple<char*, int> get_active_interfaces() {
    int socketfd;
    struct ifconf conf;
    char data[4096];

    socketfd = socket(AF_INET, SOCK_DGRAM, 0);
    conf.ifc_len = sizeof(data);
    conf.ifc_buf = (caddr_t) data;
    if (ioctl(socketfd, SIOCGIFCONF, &conf) < 0) {
        fprintf(stderr, "ioctl error: %s", std::strerror(errno));
        return std::make_tuple(nullptr, 0);
    } else {
        return std::make_tuple(data, conf.ifc_len);
    }
}

std::string get_ifname(struct ifreq *ifr) {
    return ifr->ifr_name;
}

std::string get_ipv4_addr(struct ifreq *ifr) {
    std::string addr(45, 0);
    inet_ntop(ifr->ifr_addr.sa_family,
            &((struct sockaddr_in*) &ifr->ifr_addr)->sin_addr, &addr[0],
            addr.size());
    return addr;
}

#define MAC_TEMPLATE "%02X:%02X:%02X:%02X:%02X:%02X"
#define LOOPBACK_TEMPLATE "LOOPBAK INTERFACE"

std::string get_mac_addr(struct ifreq *ifr) {
    std::string addr;

    u_int8_t hd[6];
    struct ifreq ifl;
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    if (sock >= 0) {
        strcpy(ifl.ifr_name, ifr->ifr_name);
        if (ioctl(sock, SIOCGIFFLAGS, &ifl) == 0) {
            if (!(ifl.ifr_flags & IFF_LOOPBACK)) {
                if (ioctl(sock, SIOCGIFHWADDR, &ifl) == 0) {
                    memcpy(hd, ifl.ifr_hwaddr.sa_data, sizeof(hd));
                    size_t n = snprintf(nullptr, 0, MAC_TEMPLATE, hd[0], hd[1],
                            hd[2], hd[3], hd[4], hd[5]);
                    addr.resize(n + 1, 0);
                    sprintf(&addr[0], MAC_TEMPLATE, hd[0], hd[1], hd[2], hd[3],
                            hd[4], hd[5]);
                }
            } else {
                addr = LOOPBACK_TEMPLATE;
            }
        }
    }

    return addr;
}

std::string get_netmask(struct ifreq *ifr) {
    std::string addr(45, 0);

    struct ifreq ifl;
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    if (sock >= 0) {
        strcpy(ifl.ifr_name, ifr->ifr_name);
        if (ioctl(sock, SIOCGIFNETMASK, &ifl) == 0) {
            inet_ntop(ifl.ifr_addr.sa_family,
                    &((struct sockaddr_in*) &ifl.ifr_addr)->sin_addr, &addr[0],
                    addr.size());
        }
    }
    return addr;
}

std::string get_broadcast(struct ifreq *ifr) {
    std::string addr(45, 0);

    struct ifreq ifl;
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    if (sock >= 0) {
        strcpy(ifl.ifr_name, ifr->ifr_name);
        if (ioctl(sock, SIOCGIFFLAGS, &ifl) == 0) {
            if (!(ifl.ifr_flags & IFF_LOOPBACK)) {
                if (ioctl(sock, SIOCGIFBRDADDR, &ifl) == 0) {
                    inet_ntop(ifl.ifr_addr.sa_family,
                            &((struct sockaddr_in*) &ifl.ifr_addr)->sin_addr,
                            &addr[0], addr.size());
                }
            } else {
                addr = LOOPBACK_TEMPLATE;
            }
        }
    }
    return addr;
}

int get_mtu(struct ifreq *ifr) {
    int mtu = 0;

    struct ifreq ifl;
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
    if (sock >= 0) {
        strcpy(ifl.ifr_name, ifr->ifr_name);
        if (ioctl(sock, SIOCGIFMTU, &ifl) == 0) {
            mtu = ifl.ifr_mtu;
        }
    }
    return mtu;
}

/**
 *  Return a list of interface (network layer) addresses.
 *  Accept or return only AF_INET socket addresses are
 *  IP-specific and perhaps should rather be documented in ip(7).
 *  [https://www.man7.org/linux/man-pages/man7/ip.7.html]
 *  This currently means only addresses of the AF_INET (IPv4)
 *  family for compatibility.
 *  The names of interfaces with no addresses or that don't have the
 *  IFF_RUNNING flag set can be found via /proc/net/dev.
 */
std::vector<iface> get_ifce_list() {
    std::vector<iface> ifaces;
    int socketfd;

    printf("Opening socket...");
    socketfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socketfd >= 0) {
        printf(" OK\n");
        printf("Discovering interfaces...\n");
        auto data = get_active_interfaces();

        for (struct ifreq *ifr = (struct ifreq*) std::get<0>(data);
                (char*) ifr < std::get<0>(data) + std::get<1>(data); ifr++) {
            switch (ifr->ifr_addr.sa_family) {
            case AF_INET: {
                iface ifc;
                ifc.name = get_ifname(ifr);
                ifc.address = get_ipv4_addr(ifr);
                ifc.netmask = get_netmask(ifr);
                ifc.broadcast = get_broadcast(ifr);
                ifc.hwaddr = get_mac_addr(ifr);
                ifc.mtu = get_mtu(ifr);

                ifaces.emplace_back(ifc);
        
                break;
            }
#if USE_IPV6
                case AF_INET6:

                    break;
    #endif
            }
        }
        close(socketfd);
    } else {
        printf(" Failed!\n");
    }

    return ifaces;
}

// This is the main function

int main() {
    std::time_t start = std::time(nullptr), stop;
    std::vector<iface> _ifaces = get_ifce_list();
    for (auto &_iface : _ifaces) {
        print_iface(_iface);
    }
    stop = std::time(nullptr);

    std::cout << "End of code. ran for: " << stop - start << " seconds. "
            << std::endl;
    return 0;
}


Solution

  • This code is problematic in a few ways:

    std::tuple<char*, int> get_active_interfaces() {
        int socketfd;
        struct ifconf conf;
        char data[4096];
    
        socketfd = socket(AF_INET, SOCK_DGRAM, 0);
        conf.ifc_len = sizeof(data);
        conf.ifc_buf = (caddr_t) data;
        if (ioctl(socketfd, SIOCGIFCONF, &conf) < 0) {
            fprintf(stderr, "ioctl error: %s", std::strerror(errno));
            return std::make_tuple(nullptr, 0);
        } else {
            return std::make_tuple(data, conf.ifc_len);
        }
    }
    

    The basic problem is that the tuple you return contains a pointer to data, which is a local variable. Any use of that pointer outside the function is an error.

    What you want to do, IIUC, is iterate over ifc_req, which is defined as a list of interfaces no more than ifc_len bytes long.

    The most general approach, according to my Ubuntu man page, is first to make one call with ifc_req == NULL, in which case the system will populate ifc_len with the required size. Compute the number of elements and allocate from the free store with

    size_t N = ifc_len / sizeof(ifc_req[0]);
    conf.ifc_req = new ifreq(N);
    

    I would redefine the function to return std::tuple<struct ifreq *, size_t>, and change the meaning of second to be the number of elements, not the number of bytes. That way, the caller can be organized around an array of structures, and you're done mucking about with bytes and casts. Since you're working in C++, that would let you iterate over the array with std::for_each quite neatly.