diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bb8c58927..646a1a08b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,7 +8,9 @@ "jetmartin.bats", "ms-vscode.cpptools", "ms-vscode.cmake-tools", - "eamodio.gitlens" + "eamodio.gitlens", + "github.copilot", + "ms-python.python" ] } }, diff --git a/.github/.codespellignore b/.github/.codespellignore index 0dd61bef4..5ca20bf8c 100644 --- a/.github/.codespellignore +++ b/.github/.codespellignore @@ -9,3 +9,5 @@ punycode bitap mmapped dnsmasq +iif +prefered diff --git a/src/api/api.c b/src/api/api.c index 406ba7c1a..8d21de2f3 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -90,6 +90,7 @@ static struct { { "/api/config", "/{element}", api_config, { API_PARSE_JSON, 0 }, true, HTTP_GET }, { "/api/config", "/{element}/{value}", api_config, { API_PARSE_JSON, 0 }, true, HTTP_DELETE | HTTP_PUT }, { "/api/network/gateway", "", api_network_gateway, { API_PARSE_JSON, 0 }, true, HTTP_GET }, + { "/api/network/routes", "", api_network_routes, { API_PARSE_JSON, 0 }, true, HTTP_GET }, { "/api/network/interfaces", "", api_network_interfaces, { API_PARSE_JSON, 0 }, true, HTTP_GET }, { "/api/network/devices", "", api_network_devices, { API_PARSE_JSON, 0 }, true, HTTP_GET }, { "/api/network/devices", "/{device_id}", api_network_devices, { API_PARSE_JSON, 0 }, true, HTTP_DELETE }, diff --git a/src/api/api.h b/src/api/api.h index e9db54097..793da3c62 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -80,6 +80,7 @@ int api_logs(struct ftl_conn *api); // Network methods int api_network_gateway(struct ftl_conn *api); +int api_network_routes(struct ftl_conn *api); int api_network_interfaces(struct ftl_conn *api); int api_network_devices(struct ftl_conn *api); int api_client_suggestions(struct ftl_conn *api); diff --git a/src/api/docs/content/specs/main.yaml b/src/api/docs/content/specs/main.yaml index 1762aed4f..6aa7dc795 100644 --- a/src/api/docs/content/specs/main.yaml +++ b/src/api/docs/content/specs/main.yaml @@ -241,6 +241,9 @@ paths: /network/gateway: $ref: 'network.yaml#/components/paths/gateway' + /network/routes: + $ref: 'network.yaml#/components/paths/routes' + /network/interfaces: $ref: 'network.yaml#/components/paths/interfaces' diff --git a/src/api/docs/content/specs/network.yaml b/src/api/docs/content/specs/network.yaml index 1c497a65f..079e8238c 100644 --- a/src/api/docs/content/specs/network.yaml +++ b/src/api/docs/content/specs/network.yaml @@ -10,6 +10,10 @@ components: operationId: "get_gateway" description: | This API hook returns infos about the gateway of your Pi-hole. + + If the optional parameter `detailed` is set to `true`, the response will include detailed information about the individual interfaces and routes. Note that the available information is dependent on the interface type and state. + parameters: + - $ref: 'network.yaml#/components/parameters/devices/detailed' responses: '200': description: OK @@ -27,14 +31,47 @@ components: allOf: - $ref: 'common.yaml#/components/errors/unauthorized' - $ref: 'common.yaml#/components/schemas/took' + routes: + get: + summary: Get info about the routes of your Pi-hole + tags: + - "Network information" + operationId: "get_routes" + parameters: + - $ref: 'network.yaml#/components/parameters/devices/detailed' + description: | + This API hook returns infos about the networking routes of your Pi-hole. Note that not all described fields are applicable to any routing type. Users must not rely on the presence of any field without checking the route type first. + + If the optional parameter `detailed` is set to `true`, the response will include more detailed information about the individual routes where the available information is dependent on the route type and state. + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: 'network.yaml#/components/schemas/routes' + - $ref: 'common.yaml#/components/schemas/took' + '401': + description: Unauthorized + content: + application/json: + schema: + allOf: + - $ref: 'common.yaml#/components/errors/unauthorized' + - $ref: 'common.yaml#/components/schemas/took' interfaces: get: summary: Get info about the interfaces of your Pi-hole tags: - "Network information" operationId: "get_interfaces" + parameters: + - $ref: 'network.yaml#/components/parameters/devices/detailed' description: | - This API hook returns infos about the networking interfaces of your Pi-hole. + This API hook returns infos about the networking interfaces of your Pi-hole. Note that not all described fields are applicable to any routing type. Users must not rely on the presence of any field without checking the route type first. + + If the optional parameter `detailed` is set to `true`, the response will include more detailed information about the individual interfaces where the available information is dependent on the interface type and state. responses: '200': description: OK @@ -119,14 +156,144 @@ components: gateway: type: object properties: - address: - type: string - description: Address of the gateway - example: "192.168.0.1" - interface: - type: string - description: Interface of your Pi-hole connected to the gateway - example: "eth0" + gateway: + type: array + items: + type: object + properties: + family: + type: string + description: Address family + interface: + type: string + description: Interface name + address: + type: string + description: Gateway address + local: + type: array + description: Local interface addresses + items: + type: string + example: + - family: "inet" + interface: "eth0" + address: "192.168.0.1" + local: + - "192.168.0.22" + - family: "inet6" + interface: "eth0" + address: "fe80::3587:2fff:f11a:1" + local: + - "fe80::3587:2fff:f11a:4321" + routes: + type: object + properties: + routes: + type: array + description: Array of routes + items: + type: object + properties: + gateway: + type: string + description: Gateway address + family: + type: string + enum: [ "inet", "inet6", "link", "mpls", "bridge", "???" ] + description: Address family + table: + type: integer + description: Routing table ID (0 = unspecified, 253 = default, 254 = local, 255 = local, other = user-defined) + protocol: + type: string + description: Routing protocol + scope: + type: string + description: Routing scope + type: + type: string + description: Routing type + flags: + type: array + description: Array of route flags + items: + type: string + oif: + type: string + description: Outgoing interface + iif: + type: string + description: Incoming interface + dst: + type: string + description: Destination address (or "default" for the default route) + src: + type: string + description: Source address + prefsrc: + type: string + description: Preferred source address + priority: + type: integer + description: Route priority + pref: + type: integer + description: Route preference + + example: + - family: "inet" + table: 254 + protocol: "static" + scope: "universe" + type: "unicast" + flags: [] + gateway: "192.168.0.1" + oif: "eth0" + - family: "inet" + table: 254 + protocol: "boot" + scope: "link" + type: "unicast" + flags: [] + dst: "10.1.0.0" + oif: "wg0" + - family: "inet" + table: 255 + protocol: "kernel" + scope: "host" + type: "local" + flags: [] + dst: "127.0.0.1" + prefsrc: "127.0.0.1" + oif: "lo" + - family: "inet6" + table: 255 + protocol: "kernel" + scope: "universe" + type: "local" + flags: [] + dst: "::1" + priority: 0 + oif: "eth0" + - family: "inet6" + table: 254 + protocol: "static" + scope: "universe" + type: "unicast" + flags: [] + gateway: "fe80::3587:2fff:f11a:4321" + oif: "eth0" + - family: "inet6" + table: 255 + protocol: "kernel" + scope: "universe" + type: "multicast" + flags: [] + dst: "fd00:4711::" + priority: 5 + oif: "wg0" + interfaces: type: object properties: @@ -138,84 +305,259 @@ components: properties: name: type: string - nullable: true description: Interface name - default: - type: boolean - description: If the interface is the default gateway - carrier: - type: boolean - description: If the interface is connected speed: type: integer - description: Speed of the interface in Mbit/s (-1 if not applicable) - tx: - type: object - properties: - num: - type: number - description: Number of transmitted data since boot - unit: - type: string - description: Unit of transmitted data since boot - rx: - type: object - properties: - num: - type: number - description: Number of received data since boot - unit: - type: string - description: Unit of received data since boot - ipv4: - type: array nullable: true - description: Array of associated IPv4 addresses + description: Speed of the interface in Mbit/s (`null` if not applicable) + carrier: + type: boolean + description: Whether the interface is connected + type: + type: string + description: Type of the interface + flags: + type: array + description: Array of address flags items: type: string - ipv6: + state: + type: string + description: State of the interface + proto_down: + type: boolean + description: Whether the interface is administratively down + address: + type: string + description: Interface hardware address + broadcast: + type: string + description: Interface broadcast address + perm_address: + type: string + description: Interface permanent hardware address + stats: + type: object + properties: + rx_bytes: + type: object + description: Interface received bytes + properties: + value: + type: number + description: Number of received bytes + unit: + type: string + description: Unit of the received bytes + tx_bytes: + type: object + description: Interface transmitted bytes + properties: + value: + type: number + description: Number of transmitted bytes + unit: + type: string + description: Unit of the transmitted bytes + addresses: type: array nullable: true - description: Array of associated IPv6 addresses + description: Array of associated IPv addresses items: - type: string + type: object + properties: + address: + type: string + description: Interface address + address_type: + type: string + description: Type of the interface address + broadcast: + type: string + description: Interface broadcast address + broadcast_type: + type: string + description: Type of the broadcast address + local: + type: string + description: Local address + local_type: + type: string + description: Type of the local address + label: + type: string + description: Interface label + family: + type: string + enum: [ "inet", "inet6", "link", "mpls", "bridge", "???" ] + description: Address family + flags: + type: array + description: Array of address flags + items: + type: string + prefixlen: + type: integer + description: Prefix length of the interface address + scope: + type: string + description: Address scope + prefered: + type: integer + description: Preferred lifetime of the address (`4294967295` = forever) + valid: + type: integer + description: Valid lifetime of the address (`4294967295` = forever) + cstamp: + type: number + description: Creation timestamp of the address + tstamp: + type: number + description: Updated timestamp of the address example: - - name: "eth0" - default: true + - name: "lo" + speed: null + type: "loopback" + flags: [ "up", "loopback", "running", "lower_up" ] + state: "unknown" carrier: true + address: "00:00:00:00:00:00" + broadcast: "00:00:00:00:00:00" + stats: + rx_bytes: + value: 81.6571641 + unit: "MB" + tx_bytes: + value: 648.818 + unit: "MB" + addresses: + - address: "127.0.0.1" + address_type: "loopback" + local: "127.0.0.1" + local_type: "loopback" + family: "inet" + scope: "host" + flags: [ "permanent" ] + prefixlen: 8 + label: "lo" + prefered: 4294967295 + valid: 4294967295 + cstamp: 1720989931 + tstamp: 1720989931 + - address: "::1" + address_type: "loopback" + local: "::1" + local_type: "loopback" + family: "inet6" + scope: "host" + flags: [ "permanent" ] + prefixlen: 128 + label: "lo" + prefered: 4294967295 + valid: 4294967295 + cstamp: 1720989931.1 + tstamp: 1720989931.1 + - name: "eth0" speed: 1000 - tx: - num: 10.4 - unit: "MB" - rx: - num: 8.1 - unit: "MB" - ipv4: ["192.168.0.123"] - ipv6: ["fe80::1234:5678:9abc:def0", "2001:db8::1234:5678:9abc:def0"] - - name: "wlan0" - default: false - carrier: false - speed: -1 - tx: - num: 0 - unit: "B" - rx: - num: 0 - unit: "B" - ipv4: [] - ipv6: [] + type: "ether" + flags: [ "up", "broadcast", "running", "multicast", "lower_up" ] + state: "up" + carrier: true + address: "00:11:22:33:44:55" + broadcast: "ff:ff:ff:ff:ff:ff" + perm_address: "00:11:22:33:44:55" + stats: + rx_bytes: + value: 15.5585 + unit: "GB" + tx_bytes: + value: 1.55858 + unit: "GB" + addresses: + - address: "192.168.0.123" + address_type: "private" + local: "192.168.0.123" + local_type: "private" + family: "inet" + scope: "universe" + flags: [ "permanent" ] + prefixlen: 24 + label: "eth0" + prefered: 4294967295 + valid: 4294967295 + cstamp: 1720989931.1 + tstamp: 1720989931.1 + - address: "2001:db8::1234:5678:9abc:def0" + address_type: "global (GUA)" + family: "inet6" + scope: "universe" + flags: [] + prefixlen: 64 + label: "eth0" + prefered: 3461 + valid: 7061 + cstamp: 2789057.25 + tstamp: 2789057.25 + - address: "fd29:db8::1234:5678:9abc:def0" + address_type: "site-local (ULA)" + family: "inet6" + scope: "universe" + flags: [] + prefixlen: 64 + label: "eth0" + prefered: 3461 + valid: 7061 + cstamp: 1720989931.1 + tstamp: 1720989931.1 + - address: "fe80::1234:5678:9abc:def0" + address_type: "link-local (LL)" + family: "inet6" + scope: "link" + flags: [ "permanent" ] + prefixlen: 64 + label: "eth0" + prefered: 4294967295 + valid: 4294967295 + cstamp: 1720989931.1 + tstamp: 1720989931.1 - name: "wg0" - default: false + speed: null + type: "none" + flags: [ "up", "pointopoint", "running", "noarp", "lower_up" ] + state: "unknown" carrier: true - speed: -1 - tx: - num: 170.3 - unit: "kB" - rx: - num: 222.3 - unit: "kB" - ipv4: ["10.1.0.1"] - ipv6: ["fd00:4711::1"] + stats: + rx_bytes: + value: 458.44598 + unit: "MB" + tx_bytes: + value: 5.5895 + unit: "MB" + addresses: + - address: "10.1.0.1" + address_type: "private" + local: "10.1.0.1" + local_type: "private" + family: "inet" + scope: "universe" + flags: [ "permanent" ] + prefixlen: 24 + label: "wg0" + prefered: 4294967295 + valid: 4294967295 + cstamp: 1720989931.1 + tstamp: 1720989931.1 + - address: "fd00:4711::1" + address_type: "site-local (ULA)" + family: "inet6" + scope: "global" + flags: [ "permanent" ] + prefixlen: 64 + label: "wg0" + prefered: 4294967295 + valid: 4294967295 + cstamp: 1720989931.1 + tstamp: 1720989931.1 devices: type: object properties: @@ -304,3 +646,11 @@ components: type: integer required: true example: 1 + detailed: + in: query + description: (Optional) Detailed interface/routing information + name: detailed + schema: + type: boolean + required: false + example: false diff --git a/src/api/network.c b/src/api/network.c index 532a985e1..6ff36a62e 100644 --- a/src/api/network.c +++ b/src/api/network.c @@ -24,292 +24,133 @@ #include "database/query-table.h" // config struct #include "config/config.h" +// PRIx64 +#include +#include +// IFA_LINK and friends +#include +// nlroutes(), nladdrs(), nllinks() +#include "tools/netlink.h" -static bool getDefaultInterface(char iface[IF_NAMESIZE], in_addr_t *gw) +int api_network_gateway(struct ftl_conn *api) { - // Get IPv4 default route gateway and associated interface - unsigned long dest_r = 0, gw_r = 0; - unsigned int flags = 0u; - int metric = 0, minmetric = __INT_MAX__; + // Get ?detailed parameter + bool detailed = false; + get_bool_var(api->request->query_string, "detailed", &detailed); + + // Get routing information + cJSON *routes = JSON_NEW_ARRAY(); + nlroutes(routes, detailed); - FILE *file; - if((file = fopen("/proc/net/route", "r"))) + // Get interface information ... + cJSON *interfaces = JSON_NEW_ARRAY(); + nllinks(interfaces, detailed); + // ... and enrich them with addresses + nladdrs(interfaces, detailed); + + cJSON *gateway = JSON_NEW_ARRAY(); + // Search through routes for the default gateway + // They are the ones with "dst" == "default" + cJSON *route = NULL; + cJSON_ArrayForEach(route, routes) { - // Parse /proc/net/route - the kernel's IPv4 routing table - char buf[1024] = { 0 }; - while(fgets(buf, sizeof(buf), file)) + cJSON *dst = cJSON_GetObjectItem(route, "dst"); + if(dst != NULL && + cJSON_IsString(dst) && + strcmp(cJSON_GetStringValue(dst), "default") == 0) { - char iface_r[IF_NAMESIZE] = { 0 }; - if(sscanf(buf, "%15s %lx %lx %x %*i %*i %i", iface_r, &dest_r, &gw_r, &flags, &metric) != 5) - continue; - - // Only analyze routes which are UP and whose - // destinations are a gateway - if(!(flags & RTF_UP) || !(flags & RTF_GATEWAY)) - continue; - - // Only analyze "catch all" routes (destination 0.0.0.0) - if(dest_r != 0) - continue; - - // Store default gateway, overwrite if we find a route with - // a lower metric - if(metric < minmetric) - { - minmetric = metric; - *gw = gw_r; - strcpy(iface, iface_r); + cJSON *gwobj = JSON_NEW_OBJECT(); - log_debug(DEBUG_API, "Reading interfaces: flags: %u, addr: %s, iface: %s, metric: %i, minmetric: %i", - flags, inet_ntoa(*(struct in_addr *) gw), iface, metric, minmetric); + // Extract and add family + const char *family = cJSON_GetStringValue(cJSON_GetObjectItem(route, "family")); + JSON_REF_STR_IN_OBJECT(gwobj, "family", family); + + // Extract and add interface name + const char *iface_name = cJSON_GetStringValue(cJSON_GetObjectItem(route, "oif")); + JSON_COPY_STR_TO_OBJECT(gwobj, "interface", iface_name); + + // Extract and add gateway address + const char *gw_addr = cJSON_GetStringValue(cJSON_GetObjectItem(route, "gateway")); + JSON_COPY_STR_TO_OBJECT(gwobj, "address", gw_addr); + + // Extract and add local interface address + cJSON *local = JSON_NEW_ARRAY(); + cJSON *iface = NULL; + cJSON_ArrayForEach(iface, interfaces) + { + const char *ifname = cJSON_GetStringValue(cJSON_GetObjectItem(iface, "name")); + if(ifname != NULL && strcmp(ifname, iface_name) == 0) + { + cJSON *addr = NULL; + cJSON *addrs = cJSON_GetObjectItem(iface, "addresses"); + cJSON_ArrayForEach(addr, addrs) + { + // Skip addresses belonging to another address family + const char *ifamily = cJSON_GetStringValue(cJSON_GetObjectItem(addr, "family")); + if(ifamily == NULL || strcmp(ifamily, family) != 0) + continue; + + const char *addr_str = cJSON_GetStringValue(cJSON_GetObjectItem(addr, "address")); + if(addr_str != NULL) + JSON_COPY_STR_TO_ARRAY(local, addr_str); + } + break; + } } + + // Add local addresses array to gateway object + JSON_ADD_ITEM_TO_OBJECT(gwobj, "local", local); + + cJSON_AddItemToArray(gateway, gwobj); } - fclose(file); + } + + // Send gateway information + cJSON *json = JSON_NEW_OBJECT(); + JSON_ADD_ITEM_TO_OBJECT(json, "gateway", gateway); + + if(detailed) + { + JSON_ADD_ITEM_TO_OBJECT(json, "routes", routes); + JSON_ADD_ITEM_TO_OBJECT(json, "interfaces", interfaces); } else - log_err("Cannot read /proc/net/route: %s", strerror(errno)); + { + // Free arrays + cJSON_Delete(routes); + cJSON_Delete(interfaces); + } - // Return success based on having found the default gateway's address - return gw != 0; + JSON_SEND_OBJECT(json); } -int api_network_gateway(struct ftl_conn *api) +int api_network_routes(struct ftl_conn *api) { - in_addr_t gw = 0; - char iface[IF_NAMESIZE] = { 0 }; + // Get ?detailed parameter + bool detailed = false; + get_bool_var(api->request->query_string, "detailed", &detailed); - // Get default interface - getDefaultInterface(iface, &gw); - - // Generate JSON response + // Add routing information + cJSON *routes = JSON_NEW_ARRAY(); + nlroutes(routes, detailed); cJSON *json = JSON_NEW_OBJECT(); - const char *gwaddr = inet_ntoa(*(struct in_addr *) &gw); - JSON_COPY_STR_TO_OBJECT(json, "address", gwaddr); - JSON_REF_STR_IN_OBJECT(json, "interface", iface); + JSON_ADD_ITEM_TO_OBJECT(json, "routes", routes); JSON_SEND_OBJECT(json); } int api_network_interfaces(struct ftl_conn *api) { - cJSON *json = JSON_NEW_OBJECT(); - - // Get interface with default route - in_addr_t gw = 0; - char default_iface[IF_NAMESIZE] = { 0 }; - getDefaultInterface(default_iface, &gw); - - // Enumerate and list interfaces - // Loop over interfaces and extract information - DIR *dfd; - FILE *f; - struct dirent *dp; - size_t tx_sum = 0, rx_sum = 0; - char fname[64 + IF_NAMESIZE] = { 0 }; - char readbuffer[1024] = { 0 }; - - // Open /sys/class/net directory - if ((dfd = opendir("/sys/class/net")) == NULL) - { - log_err("API: Cannot access /sys/class/net"); - return 500; - } - - // Get IP addresses of all interfaces on this machine - struct ifaddrs *ifap = NULL; - if(getifaddrs(&ifap) == -1) - log_err("API: Cannot get interface addresses: %s", strerror(errno)); + // Get ?detailed parameter + bool detailed = false; + get_bool_var(api->request->query_string, "detailed", &detailed); cJSON *interfaces = JSON_NEW_ARRAY(); - // Walk /sys/class/net directory - while ((dp = readdir(dfd)) != NULL) - { - // Skip "." and ".." - if(strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) - continue; + // Get links ... + nllinks(interfaces, detailed); + // ... and enrich them with addresses + nladdrs(interfaces, detailed); - // Create new interface record - cJSON *iface = JSON_NEW_OBJECT(); - - // Extract interface name - const char *iface_name = dp->d_name; - JSON_COPY_STR_TO_OBJECT(iface, "name", iface_name); - - // Is this the default interface? - const bool is_default_iface = strcmp(iface_name, default_iface) == 0; - JSON_ADD_BOOL_TO_OBJECT(iface, "default", is_default_iface); - - // Extract carrier status - bool carrier = false; - snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/carrier", iface_name); - if((f = fopen(fname, "r")) != NULL) - { - if(fgets(readbuffer, sizeof(readbuffer)-1, f) != NULL) - carrier = readbuffer[0] == '1'; - fclose(f); - } - else - log_err("Cannot read %s: %s", fname, strerror(errno)); - JSON_ADD_BOOL_TO_OBJECT(iface, "carrier", carrier); - - // Extract link speed (may not be possible, e.g., for WiFi devices with dynamic link speeds) - int speed = -1; - snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/speed", iface_name); - if((f = fopen(fname, "r")) != NULL) - { - if(fscanf(f, "%i", &(speed)) != 1) - speed = -1; - fclose(f); - } - else - log_err("Cannot read %s: %s", fname, strerror(errno)); - JSON_ADD_NUMBER_TO_OBJECT(iface, "speed", speed); - - // Get total transmitted bytes - ssize_t tx_bytes = -1; - snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/statistics/tx_bytes", iface_name); - if((f = fopen(fname, "r")) != NULL) - { - if(fscanf(f, "%zi", &(tx_bytes)) != 1) - tx_bytes = -1; - fclose(f); - } - else - log_err("Cannot read %s: %s", fname, strerror(errno)); - - // Format transmitted bytes - double tx = 0.0; - char tx_unit[3] = { 0 }; - format_memory_size(tx_unit, tx_bytes, &tx); - if(tx_unit[0] != '\0') - tx_unit[1] = 'B'; - - // Add transmitted bytes to interface record - cJSON *tx_json = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(tx_json, "num", tx); - JSON_COPY_STR_TO_OBJECT(tx_json, "unit", tx_unit); - JSON_ADD_ITEM_TO_OBJECT(iface, "tx", tx_json); - - // Get total received bytes - ssize_t rx_bytes = -1; - snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/statistics/rx_bytes", iface_name); - if((f = fopen(fname, "r")) != NULL) - { - if(fscanf(f, "%zi", &(rx_bytes)) != 1) - rx_bytes = -1; - fclose(f); - } - else - log_err("Cannot read %s: %s", fname, strerror(errno)); - - // Format received bytes - double rx = 0.0; - char rx_unit[3] = { 0 }; - format_memory_size(rx_unit, rx_bytes, &rx); - if(rx_unit[0] != '\0') - rx_unit[1] = 'B'; - - // Add received bytes to JSON object - cJSON *rx_json = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(rx_json, "num", rx); - JSON_COPY_STR_TO_OBJECT(rx_json, "unit", rx_unit); - JSON_ADD_ITEM_TO_OBJECT(iface, "rx", rx_json); - - // Get IP address(es) of this interface - if(ifap) - { - // Walk through linked list of interface addresses - cJSON *ipv4 = JSON_NEW_ARRAY(); - cJSON *ipv6 = JSON_NEW_ARRAY(); - for(struct ifaddrs *ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) - { - // Skip interfaces without an address and those - // not matching the current interface - if(ifa->ifa_addr == NULL || strcmp(ifa->ifa_name, iface_name) != 0) - continue; - - // If we reach this point, we found the correct interface - const sa_family_t family = ifa->ifa_addr->sa_family; - char host[NI_MAXHOST] = { 0 }; - if(family == AF_INET || family == AF_INET6) - { - // Get IP address - const int s = getnameinfo(ifa->ifa_addr, - (family == AF_INET) ? - sizeof(struct sockaddr_in) : - sizeof(struct sockaddr_in6), - host, NI_MAXHOST, - NULL, 0, NI_NUMERICHOST); - if (s != 0) - { - log_warn("API: getnameinfo() failed: %s\n", gai_strerror(s)); - continue; - } - - if(family == AF_INET) - { - JSON_COPY_STR_TO_ARRAY(ipv4, host); - } - else if(family == AF_INET6) - { - JSON_COPY_STR_TO_ARRAY(ipv6, host); - } - } - } - JSON_ADD_ITEM_TO_OBJECT(iface, "ipv4", ipv4); - JSON_ADD_ITEM_TO_OBJECT(iface, "ipv6", ipv6); - } - - // Sum up transmitted and received bytes - if(tx_bytes > 0) - tx_sum += tx_bytes; - if(rx_bytes > 0) - rx_sum += rx_bytes; - - // Add interface to array - JSON_ADD_ITEM_TO_ARRAY(interfaces, iface); - } - - freeifaddrs(ifap); - closedir(dfd); - - cJSON *sum = JSON_NEW_OBJECT(); - JSON_COPY_STR_TO_OBJECT(sum, "name", "sum"); - JSON_ADD_BOOL_TO_OBJECT(sum, "carrier", true); - JSON_ADD_NUMBER_TO_OBJECT(sum, "speed", 0); - - // Format transmitted bytes - double tx = 0.0; - char tx_unit[3] = { 0 }; - format_memory_size(tx_unit, tx_sum, &tx); - if(tx_unit[0] != '\0') - tx_unit[1] = 'B'; - - // Add transmitted bytes to interface record - cJSON *tx_json = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(tx_json, "num", tx); - JSON_COPY_STR_TO_OBJECT(tx_json, "unit", tx_unit); - JSON_ADD_ITEM_TO_OBJECT(sum, "tx", tx_json); - - // Format received bytes - double rx = 0.0; - char rx_unit[3] = { 0 }; - format_memory_size(rx_unit, rx_sum, &rx); - if(rx_unit[0] != '\0') - rx_unit[1] = 'B'; - - // Add received bytes to JSON object - cJSON *rx_json = JSON_NEW_OBJECT(); - JSON_ADD_NUMBER_TO_OBJECT(rx_json, "num", rx); - JSON_COPY_STR_TO_OBJECT(rx_json, "unit", rx_unit); - JSON_ADD_ITEM_TO_OBJECT(sum, "rx", rx_json); - - cJSON *ipv4 = JSON_NEW_ARRAY(); - cJSON *ipv6 = JSON_NEW_ARRAY(); - JSON_ADD_ITEM_TO_OBJECT(sum, "ipv4", ipv4); - JSON_ADD_ITEM_TO_OBJECT(sum, "ipv6", ipv6); - - // Add interface to array - JSON_ADD_ITEM_TO_ARRAY(interfaces, sum); + cJSON *json = JSON_NEW_OBJECT(); JSON_ADD_ITEM_TO_OBJECT(json, "interfaces", interfaces); JSON_SEND_OBJECT(json); } diff --git a/src/dnsmasq/network.c b/src/dnsmasq/network.c index 9e009f770..4d35478bc 100644 --- a/src/dnsmasq/network.c +++ b/src/dnsmasq/network.c @@ -1861,3 +1861,46 @@ void newaddress(time_t now) relay->iface_index = 0; #endif } + + +static int callback_v4(struct in_addr local, int if_index, char *label, + struct in_addr netmask, struct in_addr broadcast, void *vparam) + { + log_info("callback_v4"); + // Log the interface information + log_info("Interface: %s", label); + log_info("IP Address: %s", inet_ntoa(local)); + log_info("Netmask: %s", inet_ntoa(netmask)); + log_info("Broadcast: %s", inet_ntoa(broadcast)); + log_info("Interface Index: %d", if_index); + return 1; + } + + +static int callback_v6(struct in6_addr *local, int prefix, + int scope, int if_index, int flags, + int preferred, int valid, void *vparam) + { + log_info("callback_v6"); + // Log the interface information + char ip[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, local, ip, INET6_ADDRSTRLEN); + log_info("IP Address: %s", ip); + log_info("Prefix: %d", prefix); + log_info("Scope: %d", scope); + log_info("Interface Index: %d", if_index); + log_info("Flags: %d", flags); + log_info("Preferred: %d", preferred); + log_info("Valid: %d", valid); + return 1; + } + +extern int iface_enumerate(int family, void *parm, int (*callback)()); +void test_enumerate(void) +{ + log_info("test_enumerate 4"); + iface_enumerate(AF_INET, NULL, callback_v4); + log_info("test_enumerate 6"); + iface_enumerate(AF_INET6, NULL, callback_v6); + log_info("test_enumerate done"); +}; diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 0fd77dd95..8a5f15af2 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -1001,7 +1001,7 @@ void _FTL_iface(struct irec *recviface, const union all_addr *addr, const sa_fam // MUSL defines it differently than GNU C uint8_t bytes[2]; memcpy(&bytes, &iface->addr.in6.sin6_addr, 2); - // Global Unicast Address (2000::/3, RFC 4291) + // Global Unicast Address (2000::/3, RFC 4291) isGUA = (bytes[0] & 0x70) == 0x20; // Unique Local Address (fc00::/7, RFC 4193) isULA = (bytes[0] & 0xfe) == 0xfc; diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 47b9e5e10..ce5d42442 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -15,6 +15,9 @@ set(tools_sources dhcp-discover.h gravity-parseList.c gravity-parseList.h + netlink_consts.h + netlink.c + netlink.h ) add_library(tools OBJECT ${tools_sources}) diff --git a/src/tools/netlink.c b/src/tools/netlink.c new file mode 100644 index 000000000..1de4d67ef --- /dev/null +++ b/src/tools/netlink.c @@ -0,0 +1,1152 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2024 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* Network implementation for netlink +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "FTL.h" +#include "netlink.h" +#include "netlink_consts.h" +#include "log.h" +#include +#include +#include +#include + +// defined in src/dnsmasq/rfc1035.c +extern int private_net(struct in_addr addr, int ban_localhost); + +static bool nlrequest(int fd, struct sockaddr_nl *sa, int nlmsg_type) +{ + char buf[BUFLEN] = { 0 }; + // Assemble the message according to the netlink protocol + struct nlmsghdr *nl; + nl = (struct nlmsghdr*)(void*)buf; + nl->nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT; + + if(nlmsg_type == RTM_GETADDR) + { + // Request address information + nl->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + + struct ifaddrmsg *ifa; + ifa = (struct ifaddrmsg*)NLMSG_DATA(nl); + ifa->ifa_family = AF_LOCAL; + } + else if(nlmsg_type == RTM_GETROUTE) + { + // Request route information + nl->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + + struct rtmsg *rt; + rt = (struct rtmsg*)NLMSG_DATA(nl); + rt->rtm_family = AF_LOCAL; + } + else if(nlmsg_type == RTM_GETLINK) + { + // Request link information + nl->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + + struct ifinfomsg *link; + link = (struct ifinfomsg*)NLMSG_DATA(nl); + link->ifi_family = AF_UNSPEC; + } + nl->nlmsg_type = nlmsg_type; + + // Prepare struct msghdr for sending + struct iovec iov = { nl, nl->nlmsg_len }; + struct msghdr msg = { 0 }; + msg.msg_name = sa; + msg.msg_namelen = sizeof(*sa); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + // Send netlink message to kernel + return sendmsg(fd, &msg, 0) >= 0; +} + +static ssize_t nlgetmsg(int fd, struct sockaddr_nl *sa, void *buf, size_t len) +{ + struct iovec iov; + struct msghdr msg; + iov.iov_base = buf; + iov.iov_len = len; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = sa; + msg.msg_namelen = sizeof(*sa); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + return recvmsg(fd, &msg, 0); +} + +static int nlparsemsg_route(struct rtmsg *rt, void *buf, size_t len, cJSON *routes, const bool detailed) +{ + char ifname[IF_NAMESIZE]; + cJSON *route = cJSON_CreateObject(); + cJSON_AddNumberToObject(route, "table", rt->rtm_table); + cJSON_AddStringReferenceToObject(route, "family", family_name(rt->rtm_family)); + + // Print human-readable protocol + for(unsigned int i = 0; i < sizeof(rtprots)/sizeof(rtprots[0]); i++) + if (rtprots[i].flag == rt->rtm_protocol) + { + cJSON_AddStringReferenceToObject(route, "protocol", rtprots[i].name); + break; + } + // If the protocol is not found, add it as a number + if (cJSON_GetObjectItem(route, "protocol") == NULL) { + cJSON_AddNumberToObject(route, "protocol", rt->rtm_protocol); + } + + // Print human-readable scope + for(unsigned int i = 0; i < sizeof(rtscopes)/sizeof(rtscopes[0]); i++) + if (rtscopes[i].flag == rt->rtm_scope) + { + cJSON_AddStringReferenceToObject(route, "scope", rtscopes[i].name); + break; + } + // If the scope is not found, add it as a number + if (cJSON_GetObjectItem(route, "scope") == NULL) + cJSON_AddNumberToObject(route, "scope", rt->rtm_scope); + + // Print human-readable type + for(unsigned int i = 0; i < sizeof(rttypes)/sizeof(rttypes[0]); i++) + if (rttypes[i].flag == rt->rtm_type) + { + cJSON_AddStringReferenceToObject(route, "type", rttypes[i].name); + break; + } + // If the type is not found, add it as a number + if (cJSON_GetObjectItem(route, "type") == NULL) + cJSON_AddNumberToObject(route, "type", rt->rtm_type); + + // Add array of human-readable flags + cJSON *flags = cJSON_CreateArray(); + for(unsigned int i = 0; i < sizeof(rtmflags)/sizeof(rtmflags[0]); i++) + if (rtmflags[i].flag & rt->rtm_flags) + cJSON_AddStringReferenceToArray(flags, rtmflags[i].name); + for(unsigned int i = 0; i < sizeof(rtnhflags)/sizeof(rtnhflags[0]); i++) + if (rtnhflags[i].flag & rt->rtm_flags) + cJSON_AddStringReferenceToArray(flags, rtnhflags[i].name); + cJSON_AddItemToObject(route, "flags", flags); + if(detailed) + cJSON_AddNumberToObject(route, "iflags", rt->rtm_flags); + + // Parse the route attributes + struct rtattr *rta = NULL; + static char ip[INET6_ADDRSTRLEN]; + for_each_rattr(rta, buf, len) + { + switch (rta->rta_type) + { + case RTA_DST: // route destination address + case RTA_SRC: // route source address + case RTA_GATEWAY: // gateway of the route + case RTA_PREFSRC: // preferred source address + case RTA_NEWDST: // change package destination address + inet_ntop(rt->rtm_family, RTA_DATA(rta), ip, INET6_ADDRSTRLEN); + cJSON_AddStringToObject(route, rtaTypeToString(rta->rta_type), ip); + break; + + case RTA_IIF: // incoming interface + case RTA_OIF: // outgoing interface + { + const uint32_t ifidx = *(uint32_t*)RTA_DATA(rta); + if_indextoname(ifidx, ifname); + cJSON_AddStringToObject(route, rtaTypeToString(rta->rta_type), ifname); + break; + } + + case RTA_FLOW: // route realm + case RTA_METRICS: // route metric + case RTA_MARK: // route mark + case RTA_EXPIRES: // route expires (in seconds) + case RTA_UID: // user id + case RTA_TTL_PROPAGATE: // propagate TTL + case RTA_IP_PROTO: // IP protocol + case RTA_SPORT: + case RTA_DPORT: + case RTA_NH_ID: + { + if(!detailed) + break; + const uint32_t number = *(uint32_t*)RTA_DATA(rta); + cJSON_AddNumberToObject(route, rtaTypeToString(rta->rta_type), number); + break; + } + + case RTA_TABLE: // routing table id + // Already added above + break; + + case RTA_PRIORITY: // route priority + case RTA_PREF: // route preference + { + const uint32_t num = *(uint32_t*)RTA_DATA(rta); + cJSON_AddNumberToObject(route, rtaTypeToString(rta->rta_type), num); + break; + } + + case RTA_MULTIPATH: // multipath route + { + if(!detailed) + break; + struct rtnexthop *rtnh = (struct rtnexthop *) RTA_DATA (rta); + cJSON *multipath = cJSON_CreateObject(); + cJSON_AddNumberToObject(multipath, "len", rtnh->rtnh_len); // Length of struct + length of RTAs + + // Add array of human-readable nexthop flags + cJSON *nhflags = cJSON_CreateArray(); + for(unsigned int i = 0; i < sizeof(rtnhflags)/sizeof(rtnhflags[0]); i++) + if (rtnhflags[i].flag & rtnh->rtnh_flags) + cJSON_AddStringReferenceToArray(nhflags, rtnhflags[i].name); + cJSON_AddItemToObject(route, "mflags", nhflags); + if(detailed) + cJSON_AddNumberToObject(route, "imflags", rtnh->rtnh_flags); + + cJSON_AddNumberToObject(multipath, "hops", rtnh->rtnh_hops); // Nexthop priority + if_indextoname(rtnh->rtnh_ifindex, ifname); + cJSON_AddStringToObject(multipath, "if", ifname); // Interface for this nexthop + cJSON_AddItemToObject(route, rtaTypeToString(rta->rta_type), multipath); + break; + } + + case RTA_VIA: // next hop address + { + struct rtvia *via = (struct rtvia*)RTA_DATA(rta); + inet_ntop(via->rtvia_family, &via->rtvia_addr, ip, INET6_ADDRSTRLEN); + cJSON_AddStringToObject(route, rtaTypeToString(rta->rta_type), ip); + break; + } + + case RTA_MFC_STATS: // multicast forwarding cache statistics + { + if(!detailed) + break; + struct rta_mfc_stats *mfc = (struct rta_mfc_stats*)RTA_DATA(rta); + cJSON_AddNumberToObject(route, "mfcs_packets", mfc->mfcs_packets); + cJSON_AddNumberToObject(route, "mfcs_bytes", mfc->mfcs_bytes); + cJSON_AddNumberToObject(route, "mfcs_wrong_if", mfc->mfcs_wrong_if); + break; + } + + case RTA_CACHEINFO: + { + if(!detailed) + break; + struct rta_cacheinfo *ci = (struct rta_cacheinfo*)RTA_DATA(rta); + // Get seconds the system is already up ("uptime") + struct timespec wall_clock; + clock_gettime(CLOCK_REALTIME, &wall_clock); + struct timespec boot_clock; + clock_gettime(CLOCK_BOOTTIME, &boot_clock); + const time_t delta_time = wall_clock.tv_sec - boot_clock.tv_sec; + cJSON_AddNumberToObject(route, "cstamp", delta_time + ci->rta_clntref); + cJSON_AddNumberToObject(route, "tstamp", delta_time + ci->rta_lastuse); + cJSON_AddNumberToObject(route, "expires", ci->rta_expires); + cJSON_AddNumberToObject(route, "error", ci->rta_error); + cJSON_AddNumberToObject(route, "used", ci->rta_used); + break; + } + + default: + { + // Unknown rta_type + // Add the rta_type as a number to an array of + // unknown types if in detailed mode + if(!detailed) + break; + + cJSON *unknown = cJSON_GetObjectItem(route, "unknown"); + if(unknown == NULL) + { + unknown = cJSON_CreateArray(); + cJSON_AddItemToObject(route, "unknown", unknown); + } + cJSON_AddNumberToArray(unknown, rta->rta_type); + break; + } + } + } + + // The default route is the one which does not have a "dst" attribute + if(cJSON_GetObjectItem(route, "dst") == NULL) + cJSON_AddStringToObject(route, "dst", "default"); + + cJSON_AddItemToArray(routes, route); + return 0; +} + +static int nlparsemsg_address(struct ifaddrmsg *ifa, void *buf, size_t len, cJSON *links, const bool detailed) +{ + cJSON *addr = cJSON_CreateObject(); + + // Add interface ID + if(detailed) + cJSON_AddNumberToObject(addr, "index", ifa->ifa_index); + + // Add family + cJSON_AddStringReferenceToObject(addr, "family", family_name(ifa->ifa_family)); + + // Print human-readable scope + for(unsigned int i = 0; i < sizeof(rtscopes)/sizeof(rtscopes[0]); i++) + if (rtscopes[i].flag == ifa->ifa_scope) + { + cJSON_AddStringReferenceToObject(addr, "scope", rtscopes[i].name); + break; + } + // If the scope is not found, add it as a number + if (cJSON_GetObjectItem(addr, "scope") == NULL) + cJSON_AddNumberToObject(addr, "scope", ifa->ifa_scope); + + // Add array of human-readable flags + cJSON *flags = cJSON_CreateArray(); + for(unsigned int i = 0; i < sizeof(ifaf_flags)/sizeof(ifaf_flags[0]); i++) + if (ifaf_flags[i].flag & ifa->ifa_flags) + cJSON_AddStringReferenceToArray(flags, ifaf_flags[i].name); + cJSON_AddItemToObject(addr, "flags", flags); + + // Add prefix length + cJSON_AddNumberToObject(addr, "prefixlen", ifa->ifa_prefixlen); + + // Parse the address attributes + struct rtattr *rta = NULL; + char ifname[IF_NAMESIZE] = { 0 }; + for_each_rattr(rta, buf, len){ + switch(rta->rta_type) + { + case IFA_ADDRESS: + case IFA_LOCAL: + case IFA_BROADCAST: + case IFA_ANYCAST: + { + char ip[INET6_ADDRSTRLEN] = { 0 }; + inet_ntop(ifa->ifa_family, RTA_DATA(rta), ip, INET6_ADDRSTRLEN); + cJSON_AddStringToObject(addr, ifaTypeToString(rta->rta_type), ip); + + // Determine and add address type (GUA, ULA, LL, ...) + const char *type_str = "unknown"; + if(rta->rta_type == IFA_ADDRESS) + type_str = "address_type"; + else if(rta->rta_type == IFA_LOCAL) + type_str = "local_type"; + else if(rta->rta_type == IFA_BROADCAST) + type_str = "broadcast_type"; + else if(rta->rta_type == IFA_ANYCAST) + type_str = "anycast_type"; + + if(ifa->ifa_family == AF_INET6) + { + const struct in6_addr *in6 = (struct in6_addr*)RTA_DATA(rta); + if(IN6_IS_ADDR_UNSPECIFIED(in6)) + cJSON_AddStringToObject(addr, type_str, "unspecified"); + else if(IN6_IS_ADDR_LOOPBACK(in6)) + cJSON_AddStringToObject(addr, type_str, "loopback"); + else if(IN6_IS_ADDR_MULTICAST(in6)) + cJSON_AddStringToObject(addr, type_str, "multicast"); + else if(IN6_IS_ADDR_LINKLOCAL(in6)) + cJSON_AddStringToObject(addr, type_str, "link-local (LL)"); + else if(IN6_IS_ADDR_SITELOCAL(in6)) + cJSON_AddStringToObject(addr, type_str, "site-local (ULA)"); + else if(IN6_IS_ADDR_V4MAPPED(in6)) + cJSON_AddStringToObject(addr, type_str, "IPv4-mapped"); + else if(IN6_IS_ADDR_V4COMPAT(in6)) + cJSON_AddStringToObject(addr, type_str, "IPv4-compatible"); + else if(IN6_IS_ADDR_MC_NODELOCAL(in6)) + cJSON_AddStringToObject(addr, type_str, "node-local"); + else if(IN6_IS_ADDR_MC_LINKLOCAL(in6)) + cJSON_AddStringToObject(addr, type_str, "link-local (LL)"); + else if(IN6_IS_ADDR_MC_SITELOCAL(in6)) + cJSON_AddStringToObject(addr, type_str, "site-local (ULA)"); + else if(IN6_IS_ADDR_MC_ORGLOCAL(in6)) + cJSON_AddStringToObject(addr, type_str, "organization-local"); + else if(IN6_IS_ADDR_MC_GLOBAL(in6)) + cJSON_AddStringToObject(addr, type_str, "global (GUA)"); + else + { + uint8_t bytes[2]; + memcpy(&bytes, in6, 2); + // Global Unicast Address (2000::/3, RFC 4291) + if((bytes[0] & 0x70) == 0x20) + cJSON_AddStringToObject(addr, type_str, "global (GUA)"); + // Unique Local Address (fc00::/7, RFC 4193) + else if((bytes[0] & 0xfe) == 0xfc) + cJSON_AddStringToObject(addr, type_str, "site-local (ULA)"); + // Link Local Address (fe80::/10, RFC 4291) + else if((bytes[0] & 0xff) == 0xfe && (bytes[1] & 0x30) == 0) + cJSON_AddStringToObject(addr, type_str, "link-local (LL)"); + else + cJSON_AddStringToObject(addr, type_str, "unknown"); + } + } + else if(ifa->ifa_family == AF_INET) + { + const struct in_addr *in = (struct in_addr*)RTA_DATA(rta); + if(in->s_addr == INADDR_ANY) + cJSON_AddStringToObject(addr, type_str, "unspecified"); + else if(in->s_addr == INADDR_LOOPBACK || + (in->s_addr & htonl(0xff000000)) == htonl(0x7f000000)) + cJSON_AddStringToObject(addr, type_str, "loopback"); + else if((in->s_addr & htonl(0xf0000000)) == htonl(0xe0000000)) + cJSON_AddStringToObject(addr, type_str, "multicast"); + else if(private_net(*in, false)) + cJSON_AddStringToObject(addr, type_str, "private"); + else + cJSON_AddStringToObject(addr, type_str, "public"); + } + else + cJSON_AddStringToObject(addr, type_str, "unknown"); + break; + } + + case IFA_LABEL: + strncpy(ifname, (char*)RTA_DATA(rta), IF_NAMESIZE); + cJSON_AddStringToObject(addr, ifaTypeToString(rta->rta_type), (char*)RTA_DATA(rta)); + break; + + case IFA_CACHEINFO: + { + struct ifa_cacheinfo *ci = (struct ifa_cacheinfo*)RTA_DATA(rta); + cJSON_AddNumberToObject(addr, "prefered", ci->ifa_prefered); + cJSON_AddNumberToObject(addr, "valid", ci->ifa_valid); + // Get seconds the system is already up ("uptime") + struct timespec wall_clock; + clock_gettime(CLOCK_REALTIME, &wall_clock); + struct timespec boot_clock; + clock_gettime(CLOCK_BOOTTIME, &boot_clock); + const time_t delta_time = wall_clock.tv_sec - boot_clock.tv_sec; + cJSON_AddNumberToObject(addr, "cstamp", delta_time + 0.01*ci->cstamp); // created timestamp + cJSON_AddNumberToObject(addr, "tstamp", delta_time + 0.01*ci->tstamp); // updated timestamp + break; + } + + case IFA_FLAGS: + // Already added above, ignore this duplicate + break; + + case IFA_RT_PRIORITY: + { + if(!detailed) + break; + const uint32_t prio = *(uint32_t*)RTA_DATA(rta); + cJSON_AddStringToObject(addr, rtaTypeToString(rta->rta_type), rt_priority(prio)); + break; + } + + case IFA_TARGET_NETNSID: + { + if(!detailed) + break; + const uint32_t number = *(uint32_t*)RTA_DATA(rta); + cJSON_AddNumberToObject(addr, ifaTypeToString(rta->rta_type), number); + break; + } + + default: + { + // Unknown rta_type + // Add the rta_type as a number to an array of + // unknown types if in detailed mode + if(!detailed) + break; + + cJSON *unknown = cJSON_GetObjectItem(addr, "unknown"); + if(unknown == NULL) + { + unknown = cJSON_CreateArray(); + cJSON_AddItemToObject(addr, "unknown", unknown); + } + cJSON_AddNumberToArray(unknown, rta->rta_type); + break; + } + } + } + + // Get the interface name if it is not already set + if(!ifname[0]) + if_indextoname(ifa->ifa_index, ifname); + + // Return early if the interface is not in the list of known interfaces + cJSON *ifobj = cJSON_GetObjectItem(links, ifname); + if(ifobj == NULL) + { + cJSON_Delete(addr); + return 0; + } + + // Ensure there is an addresses object for the interface + if(cJSON_GetObjectItem(ifobj, "addresses") == NULL) + cJSON_AddItemToObject(ifobj, "addresses", cJSON_CreateArray()); + + // Get the addresses object + cJSON *addrsobj = cJSON_GetObjectItem(ifobj, "addresses"); + + // Add the address to the object + cJSON_AddItemToArray(addrsobj, addr); + return 0; +} + +static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON *links, const bool detailed) +{ + cJSON *link = cJSON_CreateObject(); + + // Add ifname at the top of the JSON object + char ifname[IF_NAMESIZE] = { 0 }; + if_indextoname(ifi->ifi_index, ifname); + cJSON_AddStringToObject(link, "name", ifname); + + // Add interface ID and family if detailed + if(detailed) + { + cJSON_AddNumberToObject(link, "index", ifi->ifi_index); + cJSON_AddStringReferenceToObject(link, "family", family_name(ifi->ifi_family)); + } + + // Get link speed (not available through netlink) + // (may not be possible, e.g., for WiFi devices with dynamic link speeds) + int speed = -1; + char fname[64]; + snprintf(fname, sizeof(fname)-1, "/sys/class/net/%s/speed", ifname); + FILE *f = fopen(fname, "r"); + if(f != NULL) + { + if(fscanf(f, "%i", &(speed)) != 1) + speed = -1; + fclose(f); + } + if(speed > -1) + cJSON_AddNumberToObject(link, "speed", speed); + else + cJSON_AddNullToObject(link, "speed"); + + // Add human-readable type + for(unsigned int i = 0; i < sizeof(iflatypes)/sizeof(iflatypes[0]); i++) + if (iflatypes[i].flag == ifi->ifi_type) + { + cJSON_AddStringReferenceToObject(link, "type", iflatypes[i].name); + break; + } + + // Add interface flags + cJSON *flags = cJSON_CreateArray(); + for(unsigned int i = 0; i < sizeof(iff_flags)/sizeof(iff_flags[0]); i++) + if (iff_flags[i].flag & ifi->ifi_flags) + cJSON_AddStringReferenceToArray(flags, iff_flags[i].name); + cJSON_AddItemToObject(link, "flags", flags); + + // Parse the link attributes + struct rtattr *rta = NULL; + cJSON *jstats = NULL, *jstats64 = NULL; + for_each_rattr(rta, buf, len){ + switch(rta->rta_type) + { + case IFLA_ADDRESS: + case IFLA_BROADCAST: + case IFLA_PERM_ADDRESS: + { + char mac[18]; + const unsigned char *addr = RTA_DATA(rta); + snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + + // Addresses may be empty, so only add them if they are not + cJSON_AddStringToObject(link, iflaTypeToString(rta->rta_type), mac); + break; + } + + case IFLA_IFNAME: + case IFLA_ALT_IFNAME: + case IFLA_PHYS_PORT_NAME: + case IFLA_QDISC: + case IFLA_PARENT_DEV_NAME: + case IFLA_PARENT_DEV_BUS_NAME: + { + if(!detailed) + break; + const char *string = (char*)RTA_DATA(rta); + cJSON_AddStringToObject(link, iflaTypeToString(rta->rta_type), string); + break; + } + + case IFLA_CARRIER: + case IFLA_PROTO_DOWN: + { + const uint8_t carrier = *(uint8_t*)RTA_DATA(rta); + cJSON_AddBoolToObject(link, iflaTypeToString(rta->rta_type), carrier == 0 ? false : true); + break; + } + + case IFLA_OPERSTATE: + for(unsigned int i = 0; i < sizeof(ifstates)/sizeof(ifstates[0]); i++) + if (ifstates[i].flag == *(unsigned int*)RTA_DATA(rta)) + { + cJSON_AddStringReferenceToObject(link, "state", ifstates[i].name); + break; + } + break; + + case IFLA_LINK: // Interface index + case IFLA_PHYS_PORT_ID: + case IFLA_PHYS_SWITCH_ID: + case IFLA_CARRIER_CHANGES: + case IFLA_MTU: + case IFLA_MASTER: + case IFLA_TXQLEN: + case IFLA_MAP: + case IFLA_WEIGHT: + case IFLA_LINKMODE: + case IFLA_COST: + case IFLA_PRIORITY: + case IFLA_GROUP: + case IFLA_NET_NS_PID: + case IFLA_NET_NS_FD: + case IFLA_EXT_MASK: + case IFLA_PROMISCUITY: + case IFLA_NUM_TX_QUEUES: + case IFLA_NUM_RX_QUEUES: + case IFLA_CARRIER_UP_COUNT: + case IFLA_CARRIER_DOWN_COUNT: + case IFLA_GSO_MAX_SEGS: + case IFLA_GSO_MAX_SIZE: + case IFLA_NEW_NETNSID: + case IFLA_MIN_MTU: + case IFLA_MAX_MTU: + case IFLA_LINK_NETNSID: + { + if(!detailed) + break; + const uint32_t number = *(uint32_t*)RTA_DATA(rta); + cJSON_AddNumberToObject(link, iflaTypeToString(rta->rta_type), number); + break; + } + + case IFLA_STATS: + { + // See description of the individual statistics + // below in the IFLA_STATS64 case + jstats = JSON_NEW_OBJECT(); + struct rtnl_link_stats *stats = (struct rtnl_link_stats*)RTA_DATA(rta); + { + // Warning: May be overflown if the interface has been up for a long time + // and has transferred a lot of data as 32 bits are used for the counters + // resulting in a maximum of 4 GiB. It is recommended to use the 64 bit + // counters if available. + char prefix[2] = { 0 }; + double formatted_size; + format_memory_size(prefix, stats->rx_bytes, &formatted_size); + cJSON *rx_bytes = cJSON_CreateObject(); + cJSON_AddNumberToObject(rx_bytes, "value", formatted_size); + cJSON_AddStringToObject(rx_bytes, "unit", prefix); + cJSON_AddItemToObject(jstats, "rx_bytes", rx_bytes); + } + { + // Warning: May be overflown if the interface has been up for a long time + // and has transferred a lot of data as 32 bits are used for the counters + // resulting in a maximum of 4 GiB. It is recommended to use the 64 bit + // counters if available. + char prefix[2] = { 0 }; + double formatted_size; + format_memory_size(prefix, stats->tx_bytes, &formatted_size); + cJSON *tx_bytes = cJSON_CreateObject(); + cJSON_AddNumberToObject(tx_bytes, "value", formatted_size); + cJSON_AddStringToObject(tx_bytes, "unit", prefix); + cJSON_AddItemToObject(jstats, "tx_bytes", tx_bytes); + } + cJSON_AddNumberToObject(jstats, "bits", 32); + if(!detailed) + break; + cJSON_AddNumberToObject(jstats, "rx_packets", stats->rx_packets); + cJSON_AddNumberToObject(jstats, "tx_packets", stats->tx_packets); + cJSON_AddNumberToObject(jstats, "rx_errors", stats->rx_errors); + cJSON_AddNumberToObject(jstats, "tx_errors", stats->tx_errors); + cJSON_AddNumberToObject(jstats, "rx_dropped", stats->rx_dropped); + cJSON_AddNumberToObject(jstats, "tx_dropped", stats->tx_dropped); + cJSON_AddNumberToObject(jstats, "multicast", stats->multicast); + cJSON_AddNumberToObject(jstats, "collisions", stats->collisions); + cJSON_AddNumberToObject(jstats, "rx_length_errors", stats->rx_length_errors); + cJSON_AddNumberToObject(jstats, "rx_over_errors", stats->rx_over_errors); + cJSON_AddNumberToObject(jstats, "rx_crc_errors", stats->rx_crc_errors); + cJSON_AddNumberToObject(jstats, "rx_frame_errors", stats->rx_frame_errors); + cJSON_AddNumberToObject(jstats, "rx_fifo_errors", stats->rx_fifo_errors); + cJSON_AddNumberToObject(jstats, "rx_missed_errors", stats->rx_missed_errors); + cJSON_AddNumberToObject(jstats, "tx_aborted_errors", stats->tx_aborted_errors); + cJSON_AddNumberToObject(jstats, "tx_carrier_errors", stats->tx_carrier_errors); + cJSON_AddNumberToObject(jstats, "tx_fifo_errors", stats->tx_fifo_errors); + cJSON_AddNumberToObject(jstats, "tx_heartbeat_errors", stats->tx_heartbeat_errors); + cJSON_AddNumberToObject(jstats, "tx_window_errors", stats->tx_window_errors); + cJSON_AddNumberToObject(jstats, "rx_compressed", stats->rx_compressed); + cJSON_AddNumberToObject(jstats, "tx_compressed", stats->tx_compressed); + cJSON_AddNumberToObject(jstats, "rx_nohandler", stats->rx_nohandler); + break; + } + + case IFLA_STATS64: + { + jstats64 = JSON_NEW_OBJECT(); + struct rtnl_link_stats64 *stats64 = (struct rtnl_link_stats64*)RTA_DATA(rta); + { + char prefix[2] = { 0 }; + double formatted_size; + format_memory_size(prefix, stats64->rx_bytes, &formatted_size); + cJSON *rx_bytes = cJSON_CreateObject(); + cJSON_AddNumberToObject(rx_bytes, "value", formatted_size); + cJSON_AddStringToObject(rx_bytes, "unit", prefix); + // @rx_bytes: Number of good received + // bytes, corresponding to @rx_packets. + cJSON_AddItemToObject(jstats64, "rx_bytes", rx_bytes); + } + { + char prefix[2] = { 0 }; + double formatted_size; + format_memory_size(prefix, stats64->tx_bytes, &formatted_size); + cJSON *tx_bytes = cJSON_CreateObject(); + cJSON_AddNumberToObject(tx_bytes, "value", formatted_size); + cJSON_AddStringToObject(tx_bytes, "unit", prefix); + // @tx_bytes: Number of transmitted bytes, + // corresponding to @tx_packets. + cJSON_AddItemToObject(jstats64, "tx_bytes", tx_bytes); + } + cJSON_AddNumberToObject(jstats64, "bits", 64); + if(!detailed) + break; + // @rx_packets: Number of good packets received + // by the interface. For hardware interfaces + // counts all good packets received from the + // device by the host, including packets which + // host had to drop at various stages of + // processing (even in the driver). + cJSON_AddNumberToObject(jstats64, "rx_packets", stats64->rx_packets); + // @tx_packets: Number of packets successfully + // transmitted. For hardware interfaces counts + // packets which host was able to successfully + // hand over to the device, which does not + // necessarily mean that packets had been + // successfully transmitted out of the device, + // only that device acknowledged it copied them + // out of host memory. + cJSON_AddNumberToObject(jstats64, "tx_packets", stats64->tx_packets); + // @rx_errors: Total number of bad packets + // received on this network device. This counter + // must include events counted by + // @rx_length_errors, @rx_crc_errors, + // @rx_frame_errors and other errors not + // otherwise counted. + cJSON_AddNumberToObject(jstats64, "rx_errors", stats64->rx_errors); + // @tx_errors: Total number of transmit + // problems. This counter must include events + // counter by @tx_aborted_errors, + // @tx_carrier_errors, @tx_fifo_errors, + // @tx_heartbeat_errors, + // @tx_window_errors and other errors not + // otherwise counted. + cJSON_AddNumberToObject(jstats64, "tx_errors", stats64->tx_errors); + // @rx_dropped: Number of packets received but + // not processed, e.g. due to lack of resources + // or unsupported protocol. For hardware + // interfaces this counter may include packets + // discarded due to L2 address filtering but + // should not include packets dropped by the + // device due to buffer exhaustion which are + // counted separately in + // @rx_missed_errors (since procfs folds those + // two counters together). + cJSON_AddNumberToObject(jstats64, "rx_dropped", stats64->rx_dropped); + // @tx_dropped: Number of packets dropped on + // their way to transmission, e.g. due to lack + // of resources. + cJSON_AddNumberToObject(jstats64, "tx_dropped", stats64->tx_dropped); + // @multicast: Multicast packets received. For + // hardware interfaces this statistic is + // commonly calculated at the device level + // (unlike @rx_packets) and therefore may + // include packets which did not reach the host. + cJSON_AddNumberToObject(jstats64, "multicast", stats64->multicast); + // @collisions: Number of collisions during + // packet transmissions. + cJSON_AddNumberToObject(jstats64, "collisions", stats64->collisions); + // @rx_length_errors: Number of packets dropped + // due to invalid length. Part of aggregate + // "frame" errors in `/proc/net/dev`. + cJSON_AddNumberToObject(jstats64, "rx_length_errors", stats64->rx_length_errors); + // @rx_over_errors: Receiver FIFO overflow event + // counter. Historically the count of overflow + // events. Such events may be reported in the + // receive descriptors or via interrupts, and + // may not correspond one-to-one with dropped + // packets. + // + // The recommended interpretation for high speed + // interfaces is - number of packets dropped + // because they did not fit into buffers + // provided by the host, e.g. packets larger + // than MTU or next buffer in the ring was not + // available for a scatter transfer. + // + // Part of aggregate "frame" errors in `/proc/net/dev`. + // + // This statistics was historically used + // interchangeably with @rx_fifo_errors. + // + // This statistic corresponds to hardware events + // and is not commonly used on software devices. + cJSON_AddNumberToObject(jstats64, "rx_over_errors", stats64->rx_over_errors); + // @rx_crc_errors: Number of packets received + // with a CRC error. Part of aggregate "frame" + // errors in `/proc/net/dev`. + cJSON_AddNumberToObject(jstats64, "rx_crc_errors", stats64->rx_crc_errors); + // @rx_frame_errors: Receiver frame alignment + // errors. Part of aggregate "frame" errors in + // `/proc/net/dev`. + cJSON_AddNumberToObject(jstats64, "rx_frame_errors", stats64->rx_frame_errors); + // @rx_fifo_errors: Receiver FIFO error counter. + // + // Historically the count of overflow events. + // Those events may be reported in the receive + // descriptors or via interrupts, and may not + // correspond one-to-one with dropped packets. + // + // This statistics was used interchangeably with + // @rx_over_errors. Not recommended for use in + // drivers for high speed interfaces. + // + // This statistic is used on software devices, + // e.g. to count software packet queue overflow + // (can) or sequencing errors (GRE). + cJSON_AddNumberToObject(jstats64, "rx_fifo_errors", stats64->rx_fifo_errors); + // @rx_missed_errors: Count of packets missed by + // the host. Folded into the "drop" counter in + // `/proc/net/dev`. + // + // Counts number of packets dropped by the device due to lack + // of buffer space. This usually indicates that the host interface + // is slower than the network interface, or host is not keeping up + // with the receive packet rate. + // + // This statistic corresponds to hardware events and is not used + // on software devices. + cJSON_AddNumberToObject(jstats64, "rx_missed_errors", stats64->rx_missed_errors); + // @tx_aborted_errors: Part of aggregate + // "carrier" errors in `/proc/net/dev`. + cJSON_AddNumberToObject(jstats64, "tx_aborted_errors", stats64->tx_aborted_errors); + // @tx_carrier_errors: Number of frame + // transmission errors due to loss of carrier + // during transmission. Part of aggregate + // "carrier" errors in `/proc/net/dev`. + cJSON_AddNumberToObject(jstats64, "tx_carrier_errors", stats64->tx_carrier_errors); + // @tx_fifo_errors: Number of frame transmission + // errors due to device FIFO underrun / + // underflow. This condition occurs when the + // device begins transmission of a frame but is + // unable to deliver the entire frame to the + // transmitter in time for transmission. Part of + // aggregate "carrier" errors in + // `/proc/net/dev`. + cJSON_AddNumberToObject(jstats64, "tx_fifo_errors", stats64->tx_fifo_errors); + // @tx_heartbeat_errors: Number of Heartbeat / + // SQE Test errors for old half-duplex Ethernet. + // Part of aggregate "carrier" errors in + // `/proc/net/dev`. + cJSON_AddNumberToObject(jstats64, "tx_heartbeat_errors", stats64->tx_heartbeat_errors); + // @tx_window_errors: Number of frame + // transmission errors due to late collisions + // (for Ethernet - after the first 64B of + // transmission). Part of aggregate "carrier" + // errors in `/proc/net/dev`. + cJSON_AddNumberToObject(jstats64, "tx_window_errors", stats64->tx_window_errors); + // @rx_compressed: Number of received compressed + // packets. This counters is only meaningful for + // interfaces which support packet compression + // (e.g. CSLIP, PPP). + cJSON_AddNumberToObject(jstats64, "rx_compressed", stats64->rx_compressed); + // @tx_compressed: Number of transmitted + // compressed packets. This counters is only + // meaningful for interfaces which support + // packet compression (e.g. CSLIP, PPP). + cJSON_AddNumberToObject(jstats64, "tx_compressed", stats64->tx_compressed); + // @rx_nohandler: Number of packets received on + // the interface but dropped by the networking + // stack because the device is not designated to + // receive packets (e.g. backup link in a bond). + cJSON_AddNumberToObject(jstats64, "rx_nohandler", stats64->rx_nohandler); + break; + } + + case IFLA_LINKINFO: + { + if(!detailed) + break; + struct rtattr *nlinkinfo = NULL; + size_t nlen = RTA_PAYLOAD(rta); + void *ndata = RTA_DATA(rta); + for_each_rattr(nlinkinfo, ndata, nlen){ + switch(nlinkinfo->rta_type) + { + case IFLA_INFO_KIND: + cJSON_AddStringToObject(link, "link_kind", (char*)RTA_DATA(nlinkinfo)); + break; + case IFLA_INFO_SLAVE_KIND: + cJSON_AddStringToObject(link, "slave_kind", (char*)RTA_DATA(nlinkinfo)); + break; + case IFLA_INFO_DATA: + case IFLA_INFO_SLAVE_DATA: + // Needs a very complex + // disassembler, out of + // scope here + break; + default: + { + // Unknown rta_type + cJSON *unknown = cJSON_GetObjectItem(link, "linkinfo_unknown"); + if(unknown == NULL) + { + unknown = cJSON_CreateArray(); + cJSON_AddItemToObject(link, "linkinfo_unknown", unknown); + } + cJSON_AddNumberToArray(unknown, nlinkinfo->rta_type); + break; + } + } + } + break; + } + + case IFLA_VFINFO_LIST: + { + if(!detailed) + break; + struct rtattr *vfinfo = RTA_DATA(rta); + if (vfinfo->rta_type != IFLA_VF_INFO) + break; + + struct ifla_vf_mac *vf_mac; + struct ifla_vf_broadcast *vf_broadcast; + struct ifla_vf_tx_rate *vf_tx_rate; + struct rtattr *vf[IFLA_VF_MAX + 1] = {}; + + parse_rtattr_nested(vf, IFLA_VF_MAX, vfinfo); + + vf_mac = RTA_DATA(vf[IFLA_VF_MAC]); + vf_broadcast = RTA_DATA(vf[IFLA_VF_BROADCAST]); + vf_tx_rate = RTA_DATA(vf[IFLA_VF_TX_RATE]); + + if (vf[IFLA_VF_BROADCAST]) + { + char mac[18]; + snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x", + vf_broadcast->broadcast[0], vf_broadcast->broadcast[1], + vf_broadcast->broadcast[2], vf_broadcast->broadcast[3], + vf_broadcast->broadcast[4], vf_broadcast->broadcast[5]); + cJSON_AddStringToObject(link, "vf_broadcast", mac); + } + if(vf[IFLA_VF_MAC]) + { + char mac[18]; + snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x", + vf_mac->mac[0], vf_mac->mac[1], vf_mac->mac[2], + vf_mac->mac[3], vf_mac->mac[4], vf_mac->mac[5]); + cJSON_AddStringToObject(link, "vf_mac", mac); + } + if(vf[IFLA_VF_TX_RATE]) + { + cJSON_AddNumberToObject(link, "vf_tx_rate", vf_tx_rate->rate); + } + if(vf[IFLA_VF_LINK_STATE]) + { + const uint32_t link_state = *(uint32_t*)RTA_DATA(vf[IFLA_VF_LINK_STATE]); + cJSON_AddNumberToObject(link, "vf_link_state", link_state); + } + + break; + } + + case IFLA_EVENT: + { + if(!detailed) + break; + const uint32_t event = *(uint32_t*)RTA_DATA(rta); + for(unsigned int i = 0; i < sizeof(link_events)/sizeof(link_events[0]); i++) + if (link_events[i].flag == event) + { + cJSON_AddStringReferenceToObject(link, "event", link_events[i].name); + break; + } + if(cJSON_GetObjectItem(link, "event") == NULL) + cJSON_AddNumberToObject(link, "event", event); + break; + } + + case IFLA_AF_SPEC: + { + if(!detailed) + break; + struct rtattr *af_spec = RTA_DATA(rta); + struct rtattr *inet6_attr = parse_rtattr_one_nested(AF_INET6, af_spec); + if(!inet6_attr) + break; + + struct rtattr *tb[IFLA_INET6_MAX + 1]; + parse_rtattr_nested(tb, IFLA_INET6_MAX, inet6_attr); + + if(tb[IFLA_INET6_ADDR_GEN_MODE]) + { + const uint8_t mode = *(uint8_t*)RTA_DATA(tb[IFLA_INET6_ADDR_GEN_MODE]); + for(unsigned int i = 0; i < sizeof(addr_gen_modes)/sizeof(addr_gen_modes[0]); i++) + if (addr_gen_modes[i].flag == mode) + { + cJSON_AddStringReferenceToObject(link, "addr_gen_mode", addr_gen_modes[i].name); + break; + } + if(cJSON_GetObjectItem(link, "addr_gen_mode") == NULL) + cJSON_AddNumberToObject(link, "addr_gen_mode", mode); + } + cJSON *af_specs = cJSON_CreateArray(); + for(unsigned int i = 0; i < __IFLA_INET6_MAX; i++) + if(tb[i]) + { + cJSON *jaf_spec = cJSON_CreateObject(); + cJSON_AddNumberToObject(jaf_spec, "type", i); + cJSON_AddNumberToObject(jaf_spec, "len", RTA_PAYLOAD(tb[i])); + cJSON_AddItemToArray(af_specs, jaf_spec); + } + cJSON_AddItemToObject(link, "af_specs", af_specs); + break; + } + + case IFLA_XDP: + // Parsing XDP needs a full BPF program + // disassembler which is clearly out of scope + // here + break; + + default: + { + // Unknown rta_type + // Add the rta_type as a number to an array of + // unknown types if in detailed mode + if(!detailed) + break; + + cJSON *unknown = cJSON_GetObjectItem(link, "unknown"); + if(unknown == NULL) + { + unknown = cJSON_CreateArray(); + cJSON_AddItemToObject(link, "unknown", unknown); + } + cJSON_AddNumberToArray(unknown, rta->rta_type); + break; + } + } + } + + // Add 64 bit statistics if available and delete the 32 bit statistics + if(jstats64) + { + cJSON_AddItemToObject(link, "stats", jstats64); + if(jstats) + { + cJSON_Delete(jstats); + jstats = NULL; + } + } + // otherwise add the 32 bit statistics (64 has never been allocated) + else if(jstats) + cJSON_AddItemToObject(link, "stats", jstats); + + // Add the link to the object + cJSON_AddItemToObject(links, ifname, link); + + return 0; +} + +static uint32_t parse_nl_msg(void *buf, size_t len, cJSON *json, const bool detailed) +{ + struct nlmsghdr *nl = NULL; + for_each_nlmsg(nl, buf, len) + { + if (nl->nlmsg_type == NLMSG_ERROR) + { + log_info("error"); + return -1; + } + else if (nl->nlmsg_type == RTM_NEWROUTE) + { + struct rtmsg *rt; + rt = (struct rtmsg*)NLMSG_DATA(nl); + nlparsemsg_route(rt, RTM_RTA(rt), RTM_PAYLOAD(nl), json, detailed); + continue; + } + else if (nl->nlmsg_type == RTM_NEWADDR) + { + struct ifaddrmsg *ifa; + ifa = (struct ifaddrmsg*)NLMSG_DATA(nl); + nlparsemsg_address(ifa, IFA_RTA(ifa), IFA_PAYLOAD(nl), json, detailed); + continue; + } + else if (nl->nlmsg_type == RTM_NEWLINK) + { + struct ifinfomsg *ifi; + ifi = (struct ifinfomsg*)NLMSG_DATA(nl); + nlparsemsg_link(ifi, IFLA_RTA(ifi), IFLA_PAYLOAD(nl), json, detailed); + continue; + } + else + { + log_err("unknown nlmsg_type: %d", nl->nlmsg_type); + } + + } + return nl->nlmsg_type; +} + +static int nlquery(const int type, cJSON *json, const bool detailed) +{ + // First of all, we need to create a socket with the AF_NETLINK domain + const int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if(fd < 0) + { + log_info("socket error: %s", strerror(errno)); + return -1; + } + + struct sockaddr_nl sa; + memset(&sa, 0, sizeof(sa)); + sa.nl_family = AF_NETLINK; + + ssize_t len = nlrequest(fd, &sa, type); + if(len < 0) + { + log_info("nlrequest error: %s", strerror(errno)); + return -1; + } + + char buf[BUFLEN]; + uint32_t nl_msg_type; + do { + len = nlgetmsg(fd, &sa, buf, BUFLEN); + nl_msg_type = parse_nl_msg(buf, len, json, detailed); + } while (nl_msg_type != NLMSG_DONE && nl_msg_type != NLMSG_ERROR); + + return 0; + +} + +bool nlroutes(cJSON *routes, const bool detailed) +{ + return nlquery(RTM_GETROUTE, routes, detailed); +} + +bool nladdrs(cJSON *interfaces, const bool detailed) +{ + return nlquery(RTM_GETADDR, interfaces, detailed); +} + +bool nllinks(cJSON *interfaces, const bool detailed) +{ + return nlquery(RTM_GETLINK, interfaces, detailed); +} diff --git a/src/tools/netlink.h b/src/tools/netlink.h new file mode 100644 index 000000000..1d94f2410 --- /dev/null +++ b/src/tools/netlink.h @@ -0,0 +1,53 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2024 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* Netlink prototypes +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ +#ifndef NETLINK_H +#define NETLINK_H + +#include +#include "webserver/cJSON/cJSON.h" +#include "webserver/json_macros.h" + +// ICMPV6_PREF_LOW, etc. +#include +#include +// IFF_UP, etc. +#include +#include +#include +#include + +bool nlroutes(cJSON *routes, const bool detailed); +bool nladdrs(cJSON *interfaces, const bool detailed); +bool nllinks(cJSON *interfaces, const bool detailed); + + +#define BUFLEN 4096 + +#define for_each_nlmsg(n, buf, len) \ + for (n = (struct nlmsghdr*)buf; \ + NLMSG_OK(n, (uint32_t)len) && n->nlmsg_type != NLMSG_DONE; \ + n = NLMSG_NEXT(n, len)) + +#define for_each_rattr(n, buf, len) \ + for (n = (struct rtattr*)buf; RTA_OK(n, len); n = RTA_NEXT(n, len)) + +struct flag_names { + uint32_t flag; + const char *name; +}; + +// Manually taken from kernel source code in include/net/ipv6.h +#define IFA_GLOBAL 0x0000U +#define IFA_HOST 0x0010U +#define IFA_LINK 0x0020U +#define IFA_SITE 0x0040U +#define IFA_COMPATv4 0x0080U + +#endif // NETLINK_H diff --git a/src/tools/netlink_consts.h b/src/tools/netlink_consts.h new file mode 100644 index 000000000..f6a5e95f2 --- /dev/null +++ b/src/tools/netlink_consts.h @@ -0,0 +1,601 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2024 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* Netlink constants +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "netlink.h" + +static struct flag_names iflatypes[] = +{ + { ARPHRD_NETROM, "netrom" }, + { ARPHRD_ETHER, "ether" }, + { ARPHRD_EETHER, "eether" }, + { ARPHRD_AX25, "ax25" }, + { ARPHRD_PRONET, "pronet" }, + { ARPHRD_CHAOS, "chaos" }, + { ARPHRD_IEEE802, "ieee802" }, + { ARPHRD_ARCNET, "arcnet" }, + { ARPHRD_APPLETLK, "appletlk" }, + { ARPHRD_DLCI, "dlci" }, + { ARPHRD_ATM, "atm" }, + { ARPHRD_METRICOM, "metricom" }, + { ARPHRD_IEEE1394, "ieee1394" }, + { ARPHRD_EUI64, "eui64" }, + { ARPHRD_INFINIBAND, "infiniband" }, + { ARPHRD_SLIP, "slip" }, + { ARPHRD_CSLIP, "cslip" }, + { ARPHRD_SLIP6, "slip6" }, + { ARPHRD_CSLIP6, "cslip6" }, + { ARPHRD_RSRVD, "rsrvd" }, + { ARPHRD_ADAPT, "adapt" }, + { ARPHRD_ROSE, "rose" }, + { ARPHRD_X25, "x25" }, + { ARPHRD_HWX25, "hwx25" }, + { ARPHRD_CAN, "can" }, + { ARPHRD_MCTP, "mctp" }, + { ARPHRD_PPP, "ppp" }, + { ARPHRD_CISCO, "cisco" }, + { ARPHRD_HDLC, "hdlc" }, + { ARPHRD_CISCO, "cisco" }, + { ARPHRD_LAPB, "lapb" }, + { ARPHRD_DDCMP, "ddcmp" }, + { ARPHRD_RAWHDLC, "rawhdlc" }, + { ARPHRD_RAWIP, "rawip" }, + { ARPHRD_TUNNEL, "tunnel" }, + { ARPHRD_TUNNEL6, "tunnel6" }, + { ARPHRD_FRAD, "frad" }, + { ARPHRD_SKIP, "skip" }, + { ARPHRD_LOOPBACK, "loopback" }, + { ARPHRD_LOCALTLK, "localtlk" }, + { ARPHRD_FDDI, "fddi" }, + { ARPHRD_BIF, "bif" }, + { ARPHRD_SIT, "sit" }, + { ARPHRD_IPDDP, "ipddp" }, + { ARPHRD_IPGRE, "ipgre" }, + { ARPHRD_PIMREG, "pimreg" }, + { ARPHRD_HIPPI, "hippi" }, + { ARPHRD_ASH, "ash" }, + { ARPHRD_ECONET, "econet" }, + { ARPHRD_IRDA, "irda" }, + { ARPHRD_FCPP, "fcpp" }, + { ARPHRD_FCAL, "fcal" }, + { ARPHRD_FCPL, "fcpl" }, + { ARPHRD_FCFABRIC, "fcfabric" }, + { ARPHRD_IEEE802_TR, "ieee802_tr" }, + { ARPHRD_IEEE80211, "ieee80211" }, + { ARPHRD_IEEE80211_PRISM, "ieee80211_prism" }, + { ARPHRD_IEEE80211_RADIOTAP, "ieee80211_radiotap" }, + { ARPHRD_IEEE802154, "ieee802154" }, + { ARPHRD_IEEE802154_MONITOR, "ieee802154_monitor" }, + { ARPHRD_PHONET, "phonet" }, + { ARPHRD_PHONET_PIPE, "phonet_pipe" }, + { ARPHRD_CAIF, "caif" }, + { ARPHRD_IP6GRE, "ip6gre" }, + { ARPHRD_NETLINK, "netlink" }, + { ARPHRD_6LOWPAN, "6lowpan" }, + { ARPHRD_VSOCKMON, "vsockmon" }, + { ARPHRD_VOID, "void" }, + { ARPHRD_NONE, "none" }, +}; + +static struct flag_names ifaf_flags[] = { + { IFA_F_SECONDARY, "secondary" }, + { IFA_F_TEMPORARY, "temporary" }, + { IFA_F_NODAD, "nodad" }, + { IFA_F_OPTIMISTIC, "optimistic" }, + { IFA_F_DADFAILED, "dadfailed" }, + { IFA_F_HOMEADDRESS, "homeaddress" }, + { IFA_F_DEPRECATED, "deprecated" }, + { IFA_F_TENTATIVE, "tentative" }, + { IFA_F_PERMANENT, "permanent" }, + { IFA_F_MANAGETEMPADDR, "managetempaddr" }, + { IFA_F_NOPREFIXROUTE, "noprefixroute" }, + { IFA_F_MCAUTOJOIN, "mcautojoin" }, + { IFA_F_STABLE_PRIVACY, "stable_privacy" }, +}; + +static struct flag_names iff_flags[] = { + { IFF_UP, "up" }, + { IFF_BROADCAST, "broadcast" }, + { IFF_DEBUG, "debug" }, + { IFF_LOOPBACK, "loopback" }, + { IFF_POINTOPOINT, "pointopoint" }, + { IFF_NOTRAILERS, "notrailers" }, + { IFF_RUNNING, "running" }, + { IFF_NOARP, "noarp" }, + { IFF_PROMISC, "promisc" }, + { IFF_ALLMULTI, "allmulti" }, + { IFF_MASTER, "master" }, + { IFF_SLAVE, "slave" }, + { IFF_MULTICAST, "multicast" }, + { IFF_PORTSEL, "portsel" }, + { IFF_AUTOMEDIA, "automedia" }, + { IFF_DYNAMIC, "dynamic" }, +#ifdef IFF_LOWER_UP + { IFF_LOWER_UP, "lower_up" }, +#endif +#ifdef IFF_DORMANT + { IFF_DORMANT, "dormant" }, +#endif +#ifdef IFF_ECHO + { IFF_ECHO, "echo" }, +#endif +}; + +static struct flag_names rtprots[] = { + { RTPROT_UNSPEC, "unspec" }, + { RTPROT_REDIRECT, "redirect" }, + { RTPROT_KERNEL, "kernel" }, + { RTPROT_BOOT, "boot" }, + { RTPROT_STATIC, "static" }, + { RTPROT_GATED, "gated" }, + { RTPROT_RA, "ra" }, + { RTPROT_MRT, "mrt" }, + { RTPROT_ZEBRA, "zebra" }, + { RTPROT_BIRD, "bird" }, + { RTPROT_DNROUTED, "dnrouted" }, + { RTPROT_XORP, "xorp" }, + { RTPROT_NTK, "ntk" }, + { RTPROT_DHCP, "dhcp" }, + { RTPROT_MROUTED, "mrouted" }, + { RTPROT_KEEPALIVED, "keepalived" }, + { RTPROT_BABEL, "babel" }, + { RTPROT_OPENR, "openr" }, + { RTPROT_BGP, "bgp" }, + { RTPROT_ISIS, "isis" }, + { RTPROT_OSPF, "ospf" }, + { RTPROT_RIP, "rip" }, + { RTPROT_EIGRP, "eigrp" }, +}; + +static struct flag_names rtscopes[] = { + { RT_SCOPE_UNIVERSE, "universe" }, + { RT_SCOPE_SITE, "site" }, + { RT_SCOPE_LINK, "link" }, + { RT_SCOPE_HOST, "host" }, + { RT_SCOPE_NOWHERE, "nowhere" }, +}; + +static struct flag_names rttypes[] = { + { RTN_UNSPEC, "unspec" }, + { RTN_UNICAST, "unicast" }, + { RTN_LOCAL, "local" }, + { RTN_BROADCAST, "broadcast" }, + { RTN_ANYCAST, "anycast" }, + { RTN_MULTICAST, "multicast" }, + { RTN_BLACKHOLE, "blackhole" }, + { RTN_UNREACHABLE, "unreachable" }, + { RTN_PROHIBIT, "prohibit" }, + { RTN_THROW, "throw" }, + { RTN_NAT, "nat" }, + { RTN_XRESOLVE, "xresolve" }, +}; + +static struct flag_names rtmflags[] = { + { RTM_F_NOTIFY, "notify" }, + { RTM_F_CLONED, "cloned" }, + { RTM_F_EQUALIZE, "equalize" }, + { RTM_F_PREFIX, "prefix" }, + { RTM_F_LOOKUP_TABLE, "lookup_table" }, + { RTM_F_FIB_MATCH, "fib_match" }, + { RTM_F_OFFLOAD, "offload" }, + { RTM_F_TRAP, "trap" }, + { RTM_F_OFFLOAD_FAILED, "offload_failed" }, +}; + +static struct flag_names rtnhflags[] = { + { RTNH_F_DEAD, "dead" }, + { RTNH_F_PERVASIVE, "pervasive" }, + { RTNH_F_ONLINK, "onlink" }, + { RTNH_F_OFFLOAD, "offload" }, + { RTNH_F_LINKDOWN, "linkdown" }, + { RTNH_F_UNRESOLVED, "unresolved" }, + { RTNH_F_TRAP, "trap" }, +}; + +static struct flag_names ifstates[] = { + { IF_OPER_UNKNOWN, "unknown" }, + { IF_OPER_NOTPRESENT, "notpresent" }, + { IF_OPER_DOWN, "down" }, + { IF_OPER_LOWERLAYERDOWN, "lower_layer_down" }, + { IF_OPER_TESTING, "testing" }, + { IF_OPER_DORMANT, "dormant" }, + { IF_OPER_UP, "up" }, +}; + +static struct flag_names link_events[] = { + { IFLA_EVENT_NONE, "none" }, + { IFLA_EVENT_REBOOT, "reboot" }, + { IFLA_EVENT_FEATURES, "feature change" }, + { IFLA_EVENT_BONDING_FAILOVER, "bonding failover" }, + { IFLA_EVENT_NOTIFY_PEERS, "notify peers" }, + { IFLA_EVENT_IGMP_RESEND, "resend igmp" }, + { IFLA_EVENT_BONDING_OPTIONS, "bonding option" }, +}; + +static struct flag_names addr_gen_modes[] = { + { IN6_ADDR_GEN_MODE_EUI64, "eui64" }, + { IN6_ADDR_GEN_MODE_NONE, "none" }, + { IN6_ADDR_GEN_MODE_STABLE_PRIVACY, "stable_secret" }, + { IN6_ADDR_GEN_MODE_RANDOM, "random" }, +}; + +static const char *__attribute__ ((const)) rtaTypeToString(const int rta_type) +{ + switch (rta_type) { + case RTA_UNSPEC: + return "unspec"; + case RTA_DST: + return "dst"; + case RTA_SRC: + return "src"; + case RTA_IIF: + return "iif"; + case RTA_OIF: + return "oif"; + case RTA_GATEWAY: + return "gateway"; + case RTA_PRIORITY: + return "priority"; + case RTA_PREFSRC: + return "prefsrc"; + case RTA_METRICS: + return "metrics"; + case RTA_MULTIPATH: + return "multipath"; + case RTA_PROTOINFO: + return "protoinfo"; + case RTA_FLOW: + return "flow"; + case RTA_CACHEINFO: + return "cacheinfo"; + case RTA_SESSION: + return "session"; + case RTA_MP_ALGO: + return "mp_algo"; + case RTA_TABLE: + return "table"; + case RTA_MARK: + return "mark"; + case RTA_MFC_STATS: + return "mfc_stats"; + case RTA_VIA: + return "via"; + case RTA_NEWDST: + return "newdst"; + case RTA_PREF: + return "pref"; + case RTA_ENCAP_TYPE: + return "encap_type"; + case RTA_ENCAP: + return "encap"; + case RTA_EXPIRES: + return "expires"; + case RTA_PAD: + return "pad"; + case RTA_UID: + return "uid"; + case RTA_TTL_PROPAGATE: + return "ttl_propagate"; + case RTA_IP_PROTO: + return "ip_proto"; + case RTA_SPORT: + return "sport"; + case RTA_DPORT: + return "dport"; + case RTA_NH_ID: + return "nh_id"; + default: + return "unknown"; + } +} + +static const char *__attribute__ ((const)) ifaTypeToString(const int ifa_type) +{ + switch (ifa_type) { + case IFA_ADDRESS: + return "address"; + case IFA_LOCAL: + return "local"; + case IFA_LABEL: + return "label"; + case IFA_BROADCAST: + return "broadcast"; + case IFA_ANYCAST: + return "anycast"; + case IFA_CACHEINFO: + return "cacheinfo"; + case IFA_MULTICAST: + return "multicast"; + case IFA_FLAGS: + return "flags"; + case IFA_RT_PRIORITY: + return "rt_priority"; + case IFA_TARGET_NETNSID: + return "target_netnsid"; + default: + return "unknown"; + } +} + +static const char *__attribute__ ((const)) iflaTypeToString(const int ifla_type) +{ + switch (ifla_type) + { + case IFLA_UNSPEC: + return "unspec"; + case IFLA_ADDRESS: + return "address"; + case IFLA_BROADCAST: + return "broadcast"; + case IFLA_IFNAME: + return "ifname"; + case IFLA_MTU: + return "mtu"; + case IFLA_LINK: + return "link"; + case IFLA_QDISC: + return "qdisc"; + case IFLA_STATS: + return "stats"; + case IFLA_COST: + return "cost"; + case IFLA_PRIORITY: + return "priority"; + case IFLA_MASTER: + return "master"; + case IFLA_WIRELESS: + return "wireless"; + case IFLA_PROTINFO: + return "protinfo"; + case IFLA_TXQLEN: + return "txqlen"; + case IFLA_MAP: + return "map"; + case IFLA_WEIGHT: + return "weight"; + case IFLA_OPERSTATE: + return "operstate"; + case IFLA_LINKMODE: + return "linkmode"; + case IFLA_LINKINFO: + return "linkinfo"; + case IFLA_NET_NS_FD: + return "net_ns_fd"; + case IFLA_IFALIAS: + return "ifalias"; + case IFLA_NUM_VF: + return "num_vf"; + case IFLA_VFINFO_LIST: + return "vfinfo_list"; + case IFLA_STATS64: + return "stats64"; + case IFLA_VF_PORTS: + return "vf_ports"; + case IFLA_PORT_SELF: + return "port_self"; + case IFLA_AF_SPEC: + return "af_spec"; + case IFLA_GROUP: + return "group"; + case IFLA_NET_NS_PID: + return "net_ns_pid"; + case IFLA_EXT_MASK: + return "ext_mask"; + case IFLA_PROMISCUITY: + return "promiscuity"; + case IFLA_NUM_TX_QUEUES: + return "num_tx_queues"; + case IFLA_NUM_RX_QUEUES: + return "num_rx_queues"; + case IFLA_CARRIER: + return "carrier"; + case IFLA_PHYS_PORT_ID: + return "phys_port_id"; + case IFLA_CARRIER_CHANGES: + return "carrier_changes"; + case IFLA_PHYS_SWITCH_ID: + return "phys_switch_id"; + case IFLA_LINK_NETNSID: + return "link_netnsid"; + case IFLA_PHYS_PORT_NAME: + return "phys_port_name"; + case IFLA_PROTO_DOWN: + return "proto_down"; + case IFLA_GSO_MAX_SEGS: + return "gso_max_segs"; + case IFLA_GSO_MAX_SIZE: + return "gso_max_size"; + case IFLA_PAD: + return "pad"; + case IFLA_XDP: + return "xdp"; + case IFLA_EVENT: + return "event"; + case IFLA_NEW_NETNSID: + return "new_netnsid"; + case IFLA_IF_NETNSID: + return "if_netnsid"; + case IFLA_CARRIER_UP_COUNT: + return "carrier_up_count"; + case IFLA_CARRIER_DOWN_COUNT: + return "carrier_down_count"; + case IFLA_NEW_IFINDEX: + return "new_ifindex"; + case IFLA_MIN_MTU: + return "min_mtu"; + case IFLA_MAX_MTU: + return "max_mtu"; + case IFLA_PROP_LIST: + return "prop_list"; + case IFLA_ALT_IFNAME: + return "alt_ifname"; + case IFLA_PERM_ADDRESS: + return "perm_address"; + case IFLA_PROTO_DOWN_REASON: + return "proto_down_reason"; + case IFLA_PARENT_DEV_NAME: + return "parent_dev_name"; + case IFLA_PARENT_DEV_BUS_NAME: + return "parent_dev_bus_name"; + default: + return "unknown"; + } +} + +static const char *__attribute__ ((const)) rt_priority(const uint32_t pref) +{ + switch (pref) { + case ICMPV6_ROUTER_PREF_HIGH: + return "high"; + case ICMPV6_ROUTER_PREF_MEDIUM: + return "medium"; + case ICMPV6_ROUTER_PREF_LOW: + return "low"; + case ICMPV6_ROUTER_PREF_INVALID: + return "invalid"; + default: + return "unknown"; + } +} + +static const char *__attribute__ ((const)) family_name(int family) +{ + switch(family) + { + case PF_UNSPEC: + return "unspec"; + case PF_LOCAL: + return "local"; + case PF_INET: + return "inet"; + case PF_AX25: + return "ax25"; + case PF_IPX: + return "ipx"; + case PF_APPLETALK: + return "appletalk"; + case PF_NETROM: + return "netrom"; + case PF_BRIDGE: + return "bridge"; + case PF_ATMPVC: + return "atmpvc"; + case PF_X25: + return "x25"; + case PF_INET6: + return "inet6"; + case PF_ROSE: + return "rose"; + case PF_DECnet: + return "decnet"; + case PF_NETBEUI: + return "netbeui"; + case PF_SECURITY: + return "security"; + case PF_KEY: + return "key"; + case PF_NETLINK: + return "netlink"; + case PF_PACKET: + return "packet"; + case PF_ASH: + return "ash"; + case PF_ECONET: + return "econet"; + case PF_ATMSVC: + return "atmsvc"; + case PF_RDS: + return "rds"; + case PF_SNA: + return "sna"; + case PF_IRDA: + return "irda"; + case PF_PPPOX: + return "pppox"; + case PF_WANPIPE: + return "wanpipe"; + case PF_LLC: + return "llc"; + case PF_IB: + return "ib"; + case PF_MPLS: + return "mpls"; + case PF_CAN: + return "can"; + case PF_TIPC: + return "tipc"; + case PF_BLUETOOTH: + return "bluetooth"; + case PF_IUCV: + return "iucv"; + case PF_RXRPC: + return "rxrpc"; + case PF_ISDN: + return "isdn"; + case PF_PHONET: + return "phonet"; + case PF_IEEE802154: + return "ieee802154"; + case PF_CAIF: + return "caif"; + case PF_ALG: + return "alg"; + case PF_NFC: + return "nfc"; + case PF_VSOCK: + return "vsock"; + case PF_KCM: + return "kcm"; + case PF_QIPCRTR: + return "qipcrtr"; + case PF_SMC: + return "smc"; + case PF_XDP: + return "xdp"; +#ifdef PF_MCTP + // 2024-July: defined by glibc but not musl + case PF_MCTP: + return "mctp"; +#endif + default: + return "unknown"; + } +} + +// Taken from https://github.com/Gandi/packet-journey/blob/master/lib/libnetlink/netlink.c +#define parse_rtattr_nested(tb, max, rta) \ + (parse_rtattr_flags((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta), 0)) +#define parse_rtattr_one_nested(type, rta) \ + (parse_rtattr_one(type, RTA_DATA(rta), RTA_PAYLOAD(rta))) + +static int parse_rtattr_flags(struct rtattr *tb[], int max, + struct rtattr *rta, int len, + unsigned short flags) +{ + unsigned short type; + + memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); + while (RTA_OK(rta, len)) { + type = rta->rta_type & ~flags; + if ((type <= max) && (!tb[type])) + tb[type] = rta; + rta = RTA_NEXT(rta, len); + } + return 0; +} + +static struct rtattr * __attribute__((pure)) parse_rtattr_one(int type, struct rtattr *rta, int len) +{ + while (RTA_OK(rta, len)) { + if (rta->rta_type == type) + return rta; + rta = RTA_NEXT(rta, len); + } + return NULL; +} diff --git a/src/webserver/json_macros.h b/src/webserver/json_macros.h index 5961c3ac0..6c12c6509 100644 --- a/src/webserver/json_macros.h +++ b/src/webserver/json_macros.h @@ -284,4 +284,14 @@ elem != NULL ? cJSON_IsTrue(elem) : false; \ }) -#define cJSON_AddStringToArray(array, string) cJSON_AddItemToArray(array, cJSON_CreateString(string)) +#define cJSON_AddStringReferenceToObject(object, key, string) \ + cJSON_AddItemToObject(object, key, cJSON_CreateStringReference((const char*)(string))) + +#define cJSON_AddStringReferenceToArray(array, string) \ + cJSON_AddItemToArray(array, cJSON_CreateStringReference((const char*)(string))) + +#define cJSON_AddNumberToArray(array, num) \ + cJSON_AddItemToArray(array, cJSON_CreateNumber(num)) + +#define cJSON_AddStringToArray(array, string) \ + cJSON_AddItemToArray(array, cJSON_CreateString(string)) diff --git a/test/api/libs/responseVerifyer.py b/test/api/libs/responseVerifyer.py index 238ca24cf..27558907e 100644 --- a/test/api/libs/responseVerifyer.py +++ b/test/api/libs/responseVerifyer.py @@ -292,7 +292,6 @@ def verify_property(self, YAMLprops: dict, YAMLexamples: dict, FTLprops: dict, p # Check if the property is defined in the API specs (unless we know there are "any-key" items here) if props[-1] not in YAMLprops: self.errors.append("Property '" + flat_path + "' missing in the API specs (2)") - print(YAMLprop) return False YAMLprop = YAMLprops[props[-1]]