| /* |
| * Copyright (C) 2004-2023 Savoir-faire Linux Inc. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| */ |
| #include "ip_utils.h" |
| #include "sip_utils.h" |
| #include "string_utils.h" |
| |
| #include <fmt/format.h> |
| |
| extern "C" { |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <limits.h> |
| |
| #ifdef _WIN32 |
| #define InetPtonA inet_pton |
| WINSOCK_API_LINKAGE INT WSAAPI InetPtonA(INT Family, LPCSTR pStringBuf, PVOID pAddr); |
| #else |
| #include <arpa/inet.h> |
| #include <arpa/nameser.h> |
| #include <resolv.h> |
| #include <netdb.h> |
| #include <netinet/ip.h> |
| #include <net/if.h> |
| #include <ifaddrs.h> |
| #include <sys/ioctl.h> |
| #endif |
| } |
| |
| #ifndef HOST_NAME_MAX |
| #ifdef MAX_COMPUTERNAME_LENGTH |
| #define HOST_NAME_MAX MAX_COMPUTERNAME_LENGTH |
| #else |
| // Max 255 chars as per RFC 1035 |
| #define HOST_NAME_MAX 255 |
| #endif |
| #endif |
| |
| namespace dhtnet { |
| |
| namespace sip_utils { |
| std::string_view |
| sip_strerror(pj_status_t code) |
| { |
| thread_local char err_msg[PJ_ERR_MSG_SIZE]; |
| return sip_utils::as_view(pj_strerror(code, err_msg, sizeof err_msg)); |
| } |
| } |
| |
| std::string |
| ip_utils::getHostname() |
| { |
| char hostname[HOST_NAME_MAX]; |
| if (gethostname(hostname, HOST_NAME_MAX)) |
| return {}; |
| return hostname; |
| } |
| |
| ip_utils::IpInterfaceAddress |
| ip_utils::getHostName() |
| { |
| IpInterfaceAddress ret; |
| char tempstr[INET_ADDRSTRLEN]; |
| const char* p = NULL; |
| #ifdef _WIN32 |
| struct hostent* h = NULL; |
| struct sockaddr_in localAddr; |
| memset(&localAddr, 0, sizeof(localAddr)); |
| char out[256]; |
| if (gethostname(out, sizeof(out)) == SOCKET_ERROR) { |
| return {}; |
| } |
| h = gethostbyname(out); |
| if (h != NULL) { |
| memcpy(&localAddr.sin_addr, h->h_addr_list[0], sizeof(localAddr.sin_addr)); |
| p = inet_ntop(AF_INET, &localAddr.sin_addr, tempstr, sizeof(tempstr)); |
| if (p) { |
| ret.address = p; |
| } |
| } |
| #elif (defined(BSD) && BSD >= 199306) || defined(__FreeBSD_kernel__) |
| struct ifaddrs* ifap; |
| struct ifaddrs* ifa; |
| if (getifaddrs(&ifap) != 0) { |
| return {}; |
| } |
| // Cycle through available interfaces. |
| for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { |
| // Skip loopback, point-to-point and down interfaces. |
| // except don't skip down interfaces if we're attempting to get |
| // a list of configurable interfaces. |
| if ((ifa->ifa_flags & IFF_LOOPBACK) || (!(ifa->ifa_flags & IFF_UP))) { |
| continue; |
| } |
| auto family = ifa->ifa_addr->sa_family; |
| if (family == AF_INET) { |
| void* addr = &((struct sockaddr_in*)ifa->ifa_addr)->sin_addr; |
| if (((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) { |
| continue; |
| } |
| ret.interface = ifa->ifa_name; |
| p = inet_ntop(family, addr, tempstr, sizeof(tempstr)); |
| if (p) { |
| ret.address = p; |
| } |
| break; |
| } |
| } |
| freeifaddrs(ifap); |
| #else |
| struct ifconf ifConf; |
| struct ifreq ifReq; |
| struct sockaddr_in localAddr; |
| char szBuffer[MAX_INTERFACE * sizeof(struct ifreq)]; |
| int nResult; |
| int localSock; |
| memset(&ifConf, 0, sizeof(ifConf)); |
| memset(&ifReq, 0, sizeof(ifReq)); |
| memset(szBuffer, 0, sizeof(szBuffer)); |
| memset(&localAddr, 0, sizeof(localAddr)); |
| // Create an unbound datagram socket to do the SIOCGIFADDR ioctl on. |
| localSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); |
| if (localSock == INVALID_SOCKET) { |
| return {}; |
| } |
| /* Get the interface configuration information... */ |
| ifConf.ifc_len = sizeof(szBuffer); |
| ifConf.ifc_buf = (caddr_t)szBuffer; |
| nResult = ioctl(localSock, SIOCGIFCONF, &ifConf); |
| if (nResult < 0) { |
| close(localSock); |
| return {}; |
| } |
| unsigned int i; |
| unsigned int j = 0; |
| // Cycle through the list of interfaces looking for IP addresses. |
| for (i = 0u; i < ifConf.ifc_len && j < MIN_INTERFACE;) { |
| struct ifreq* pifReq = (struct ifreq*)((caddr_t)ifConf.ifc_req + i); |
| i += sizeof *pifReq; |
| // See if this is the sort of interface we want to deal with. |
| memset(ifReq.ifr_name, 0, sizeof(ifReq.ifr_name)); |
| strncpy(ifReq.ifr_name, pifReq->ifr_name, sizeof(ifReq.ifr_name)); |
| ioctl(localSock, SIOCGIFFLAGS, &ifReq); |
| // Skip loopback, point-to-point and down interfaces. |
| // except don't skip down interfaces if we're attempting to get |
| // a list of configurable interfaces. |
| if ((ifReq.ifr_flags & IFF_LOOPBACK) || (!(ifReq.ifr_flags & IFF_UP))) { |
| continue; |
| } |
| if (pifReq->ifr_addr.sa_family == AF_INET) { |
| if (((sockaddr_in*)&pifReq->ifr_addr)->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) { |
| // We don't want the loopback interface. Go to the next one. |
| continue; |
| } |
| ret.interface = pifReq->ifr_name; |
| p = inet_ntop(pifReq->ifr_addr.sa_family, |
| &((sockaddr_in*)&pifReq->ifr_addr)->sin_addr, |
| tempstr, |
| sizeof(tempstr)); |
| if (p) |
| ret.address = p; |
| } |
| j++; // Increment j if we found an address which is not loopback and is up. |
| } |
| close(localSock); |
| #endif |
| return ret; |
| } |
| |
| std::string |
| ip_utils::getGateway(std::string_view localHost, ip_utils::subnet_mask prefix) |
| { |
| if (prefix == ip_utils::subnet_mask::prefix_32bit) |
| return std::string(localHost); |
| std::string defaultGw {}; |
| // Make a vector of each individual number in the ip address. |
| std::vector<std::string_view> tokens = split_string(localHost, '.'); |
| // Build a gateway address from the individual ip components. |
| for (unsigned i = 0; i <= (unsigned) prefix; i++) |
| defaultGw = fmt::format("{:s}{:s}.", defaultGw, tokens[i]); |
| for (unsigned i = (unsigned) ip_utils::subnet_mask::prefix_32bit; |
| i > (unsigned) prefix + 1; |
| i--) |
| defaultGw += "0."; |
| defaultGw += "1"; |
| return defaultGw; |
| } |
| |
| IpAddr |
| ip_utils::getLocalGateway() |
| { |
| char localHostBuf[INET_ADDRSTRLEN]; |
| auto hostInfo = ip_utils::getHostName(); |
| if (hostInfo.address.empty()) { |
| // JAMI_WARN("Unable to find local host"); |
| return {}; |
| } else { |
| return IpAddr(ip_utils::getGateway(hostInfo.address, ip_utils::subnet_mask::prefix_24bit)); |
| } |
| } |
| |
| std::vector<IpAddr> |
| ip_utils::getAddrList(std::string_view name, pj_uint16_t family) |
| { |
| std::vector<IpAddr> ipList; |
| if (name.empty()) |
| return ipList; |
| if (IpAddr::isValid(name, family)) { |
| ipList.emplace_back(name); |
| return ipList; |
| } |
| |
| static constexpr unsigned MAX_ADDR_NUM = 128; |
| pj_addrinfo res[MAX_ADDR_NUM]; |
| unsigned addr_num = MAX_ADDR_NUM; |
| const pj_str_t pjname(sip_utils::CONST_PJ_STR(name)); |
| auto status = pj_getaddrinfo(family, &pjname, &addr_num, res); |
| if (status != PJ_SUCCESS) { |
| // JAMI_ERR("Error resolving %.*s : %s", |
| // (int) name.size(), |
| // name.data(), |
| // sip_utils::sip_strerror(status).c_str()); |
| return ipList; |
| } |
| |
| for (unsigned i = 0; i < addr_num; i++) { |
| bool found = false; |
| for (const auto& ip : ipList) |
| if (!pj_sockaddr_cmp(&ip, &res[i].ai_addr)) { |
| found = true; |
| break; |
| } |
| if (!found) |
| ipList.emplace_back(res[i].ai_addr); |
| } |
| |
| return ipList; |
| } |
| |
| bool |
| ip_utils::haveCommonAddr(const std::vector<IpAddr>& a, const std::vector<IpAddr>& b) |
| { |
| for (const auto& i : a) { |
| for (const auto& j : b) { |
| if (i == j) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| IpAddr |
| ip_utils::getLocalAddr(pj_uint16_t family) |
| { |
| IpAddr ip_addr {}; |
| pj_status_t status = pj_gethostip(family, ip_addr.pjPtr()); |
| if (status == PJ_SUCCESS) { |
| return ip_addr; |
| } |
| // JAMI_WARN("Unable to get preferred address family (%s)", |
| // (family == pj_AF_INET6()) ? "IPv6" : "IPv4"); |
| family = (family == pj_AF_INET()) ? pj_AF_INET6() : pj_AF_INET(); |
| status = pj_gethostip(family, ip_addr.pjPtr()); |
| if (status == PJ_SUCCESS) { |
| return ip_addr; |
| } |
| // JAMI_ERR("Unable to get local IP"); |
| return ip_addr; |
| } |
| |
| IpAddr |
| ip_utils::getInterfaceAddr(const std::string& interface, pj_uint16_t family) |
| { |
| if (interface == DEFAULT_INTERFACE) |
| return getLocalAddr(family); |
| |
| IpAddr addr {}; |
| |
| #ifndef _WIN32 |
| const auto unix_family = family == pj_AF_INET() ? AF_INET : AF_INET6; |
| |
| int fd = socket(unix_family, SOCK_DGRAM, 0); |
| if (fd < 0) { |
| // JAMI_ERR("Unable to open socket: %m"); |
| return addr; |
| } |
| |
| if (unix_family == AF_INET6) { |
| int val = family != pj_AF_UNSPEC(); |
| if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*) &val, sizeof(val)) < 0) { |
| // JAMI_ERR("Unable to setsockopt: %m"); |
| close(fd); |
| return addr; |
| } |
| } |
| |
| ifreq ifr; |
| strncpy(ifr.ifr_name, interface.c_str(), sizeof ifr.ifr_name); |
| // guarantee that ifr_name is NULL-terminated |
| ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0'; |
| |
| memset(&ifr.ifr_addr, 0, sizeof(ifr.ifr_addr)); |
| ifr.ifr_addr.sa_family = unix_family; |
| |
| ioctl(fd, SIOCGIFADDR, &ifr); |
| close(fd); |
| |
| addr = ifr.ifr_addr; |
| if (addr.isUnspecified()) |
| return getLocalAddr(addr.getFamily()); |
| #else // _WIN32 |
| struct addrinfo hints; |
| struct addrinfo* result = NULL; |
| struct sockaddr_in* sockaddr_ipv4; |
| struct sockaddr_in6* sockaddr_ipv6; |
| |
| ZeroMemory(&hints, sizeof(hints)); |
| |
| DWORD dwRetval = getaddrinfo(interface.c_str(), "0", &hints, &result); |
| if (dwRetval != 0) { |
| // JAMI_ERR("getaddrinfo failed with error: %lu", dwRetval); |
| return addr; |
| } |
| |
| switch (result->ai_family) { |
| sockaddr_ipv4 = (struct sockaddr_in*) result->ai_addr; |
| addr = sockaddr_ipv4->sin_addr; |
| break; |
| case AF_INET6: |
| sockaddr_ipv6 = (struct sockaddr_in6*) result->ai_addr; |
| addr = sockaddr_ipv6->sin6_addr; |
| break; |
| default: |
| break; |
| } |
| |
| if (addr.isUnspecified()) |
| return getLocalAddr(addr.getFamily()); |
| #endif // !_WIN32 |
| |
| return addr; |
| } |
| |
| std::vector<std::string> |
| ip_utils::getAllIpInterfaceByName() |
| { |
| std::vector<std::string> ifaceList; |
| ifaceList.push_back("default"); |
| #ifndef _WIN32 |
| static ifreq ifreqs[20]; |
| ifconf ifconf; |
| |
| ifconf.ifc_buf = (char*) (ifreqs); |
| ifconf.ifc_len = sizeof(ifreqs); |
| |
| int sock = socket(AF_INET6, SOCK_STREAM, 0); |
| |
| if (sock >= 0) { |
| if (ioctl(sock, SIOCGIFCONF, &ifconf) >= 0) |
| for (unsigned i = 0; i < ifconf.ifc_len / sizeof(ifreq); ++i) |
| ifaceList.push_back(std::string(ifreqs[i].ifr_name)); |
| |
| close(sock); |
| } |
| |
| #else |
| // JAMI_ERR("Not implemented yet. (iphlpapi.h problem)"); |
| #endif |
| return ifaceList; |
| } |
| |
| std::vector<std::string> |
| ip_utils::getAllIpInterface() |
| { |
| pj_sockaddr addrList[16]; |
| unsigned addrCnt = PJ_ARRAY_SIZE(addrList); |
| |
| std::vector<std::string> ifaceList; |
| if (pj_enum_ip_interface(pj_AF_UNSPEC(), &addrCnt, addrList) == PJ_SUCCESS) { |
| for (unsigned i = 0; i < addrCnt; i++) { |
| char addr[PJ_INET6_ADDRSTRLEN]; |
| pj_sockaddr_print(&addrList[i], addr, sizeof(addr), 0); |
| ifaceList.emplace_back(addr); |
| } |
| } |
| |
| return ifaceList; |
| } |
| |
| std::vector<IpAddr> |
| ip_utils::getLocalNameservers() |
| { |
| std::vector<IpAddr> res; |
| #if defined __ANDROID__ || defined _WIN32 || TARGET_OS_IPHONE |
| #ifdef _MSC_VER |
| #pragma message(__FILE__ "(" STR2(__LINE__) ") : -NOTE- " \ |
| "Not implemented") |
| #else |
| #warning "Not implemented" |
| #endif |
| #else |
| if (not(_res.options & RES_INIT)) |
| res_init(); |
| res.insert(res.end(), _res.nsaddr_list, _res.nsaddr_list + _res.nscount); |
| #endif |
| return res; |
| } |
| |
| bool |
| IpAddr::isValid(std::string_view address, pj_uint16_t family) |
| { |
| const pj_str_t pjstring(sip_utils::CONST_PJ_STR(address)); |
| pj_str_t ret_str; |
| pj_uint16_t ret_port; |
| int ret_family; |
| auto status = pj_sockaddr_parse2(pj_AF_UNSPEC(), 0, &pjstring, &ret_str, &ret_port, &ret_family); |
| if (status != PJ_SUCCESS || (family != pj_AF_UNSPEC() && ret_family != family)) |
| return false; |
| |
| char buf[PJ_INET6_ADDRSTRLEN]; |
| pj_str_t addr_with_null = {buf, 0}; |
| pj_strncpy_with_null(&addr_with_null, &ret_str, sizeof(buf)); |
| struct sockaddr sa; |
| return inet_pton(ret_family == pj_AF_INET6() ? AF_INET6 : AF_INET, buf, &(sa.sa_data)) == 1; |
| } |
| |
| bool |
| IpAddr::isUnspecified() const |
| { |
| switch (addr.addr.sa_family) { |
| case AF_INET: |
| return IN_IS_ADDR_UNSPECIFIED(&addr.ipv4.sin_addr); |
| case AF_INET6: |
| return IN6_IS_ADDR_UNSPECIFIED(reinterpret_cast<const in6_addr*>(&addr.ipv6.sin6_addr)); |
| default: |
| return true; |
| } |
| } |
| |
| bool |
| IpAddr::isLoopback() const |
| { |
| switch (addr.addr.sa_family) { |
| case AF_INET: { |
| auto addr_host = ntohl(addr.ipv4.sin_addr.s_addr); |
| uint8_t b1 = (uint8_t)(addr_host >> 24); |
| return b1 == 127; |
| } |
| case AF_INET6: |
| return IN6_IS_ADDR_LOOPBACK(reinterpret_cast<const in6_addr*>(&addr.ipv6.sin6_addr)); |
| default: |
| return false; |
| } |
| } |
| |
| bool |
| IpAddr::isPrivate() const |
| { |
| if (isLoopback()) { |
| return true; |
| } |
| switch (addr.addr.sa_family) { |
| case AF_INET: { |
| auto addr_host = ntohl(addr.ipv4.sin_addr.s_addr); |
| uint8_t b1, b2; |
| b1 = (uint8_t)(addr_host >> 24); |
| b2 = (uint8_t)((addr_host >> 16) & 0x0ff); |
| // 10.x.y.z |
| if (b1 == 10) |
| return true; |
| // 172.16.0.0 - 172.31.255.255 |
| if ((b1 == 172) && (b2 >= 16) && (b2 <= 31)) |
| return true; |
| // 192.168.0.0 - 192.168.255.255 |
| if ((b1 == 192) && (b2 == 168)) |
| return true; |
| return false; |
| } |
| case AF_INET6: { |
| const pj_uint8_t* addr6 = reinterpret_cast<const pj_uint8_t*>(&addr.ipv6.sin6_addr); |
| if (addr6[0] == 0xfc) |
| return true; |
| return false; |
| } |
| default: |
| return false; |
| } |
| } |
| } // namespace dhtnet |