From 172eaa52b9db6fde90a614d62456c11b9bf1479e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 8 Jul 2024 22:48:12 +0200 Subject: [PATCH 1/7] Parse additional kernel info to get more IPv6 address details and laso parse IPv6 routing table Signed-off-by: DL6ER --- src/api/api.c | 1 + src/api/api.h | 1 + src/api/docs/content/specs/main.yaml | 3 + src/api/docs/content/specs/network.yaml | 241 +++++++++- src/api/network.c | 585 +++++++++++++++++++++--- 5 files changed, 747 insertions(+), 84 deletions(-) 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 e8e57964b..5c9bf8c8e 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -74,6 +74,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..ddc7efcdb 100644 --- a/src/api/docs/content/specs/network.yaml +++ b/src/api/docs/content/specs/network.yaml @@ -27,6 +27,31 @@ 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" + description: | + This API hook returns infos about the networking routes of your Pi-hole. + 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 @@ -119,14 +144,158 @@ 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" + ipv4: + type: object + description: IPv4 gateway information + properties: + interface: + type: string + description: Interface + example: "eth0" + address: + type: string + description: Address of the gateway + example: "192.168.0.1" + ipv6: + type: object + description: IPv6 gateway information + properties: + interface: + type: string + description: Interface + example: "eth0" + address: + type: string + description: Address of the gateway + example: "fe80::3587:2fff:f11a:4321" + routes: + type: object + properties: + routes: + type: object + description: Routing table + properties: + ipv4: + type: array + description: Array of IPv4 routes + items: + type: object + properties: + destination: + type: string + description: Destination of the route + gateway: + type: string + description: Gateway of the route + metric: + type: integer + description: Metric of the route + flags: + type: array + description: Array of flags of the route + items: + type: string + interface: + type: string + description: Interface of the route + example: + - destination: "0.0.0.0" + gateway: "192.168.1.1" + metric: 0 + flags: [ "UP", "GATEWAY" ] + interface: "eth0" + - destination: "10.100.0.0" + gateway: "0.0.0.0" + metric: 0 + flags: [ "UP" ] + interface: "wg0" + ipv6: + type: array + description: Array of IPv6 routes + items: + type: object + properties: + destination: + type: object + description: Destination of the route + properties: + address: + type: string + description: IPv6 address + prefix: + type: integer + description: Prefix of the IPv6 address + type: + type: string + enum: [ "LL", "GUA", "ULA", "UNSPEC" ] + description: Type of the IPv6 address + source: + type: object + description: Source of the route + properties: + address: + type: string + description: IPv6 address + prefix: + type: integer + description: Prefix of the IPv6 address + type: + type: string + enum: [ "LL", "GUA", "ULA", "UNSPEC" ] + description: Type of the IPv6 address + gateway: + type: object + description: Gateway of the route + properties: + address: + type: string + description: IPv6 address + type: + type: string + enum: [ "LL", "GUA", "ULA", "UNSPEC" ] + description: Type of the IPv6 address + metric: + type: integer + description: Metric of the route + ref: + type: integer + description: Reference count of the route + use: + type: integer + description: Use count of the route + flags: + type: array + description: Array of flags of the route + items: + type: string + interface: + type: string + description: Interface of the route + example: + - destination: { "address": "2001:db8::", "prefix": 32, "type": "GUA" } + source: { "address": "2001:db8::1", "prefix": 128, "type": "GUA" } + gateway: { "address": "fe80::1", "type": "LL" } + metric: 0 + ref: 0 + use: 0 + flags: [ "UP", "GATEWAY" ] + interface: "eth0" + - destination: { "address": "::1", prefix: 128, "type": "UNSPEC" } + source: { "address": "::", prefix: 0, "type": "UNSPEC" } + gateway: { "address": "::", "type": "UNSPEC" } + metric: 256 + ref: 2 + use: 0 + flags: [ "UP" ] + interface: "lo" + - destination: { "address": "fd00:4711:0", prefix: 64, "type": "ULA" } + source: { "address": "::", prefix: 0, "type": "UNSPEC" } + gateway: { "address": "::", "type": "UNSPEC" } + metric: 256 + ref: 1 + use: 0 + flags: [ "UP" ] + interface: "wg0" interfaces: type: object properties: @@ -140,9 +309,6 @@ components: 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 @@ -167,21 +333,55 @@ components: unit: type: string description: Unit of received data since boot + flags: + type: array + description: Array of interface flags + items: + type: string ipv4: type: array nullable: true description: Array of associated IPv4 addresses items: - type: string + type: object + properties: + address: + type: string + description: IPv4 address + netmask: + type: string + description: Netmask of the IPv4 address ipv6: type: array nullable: true description: Array of associated IPv6 addresses items: - type: string + type: object + properties: + address: + type: string + description: IPv6 address + netmask: + type: string + description: Netmask of the IPv4 address + type: + type: string + enum: [ "LL", "GUA", "ULA", "UNSPEC" ] + description: Type of the IPv6 address + prefix: + type: integer + description: Prefix of the IPv6 address + scope: + type: string + enum: [ "LINK", "GLOBAL", "HOST", "SITE", "COMPATv4", "UNKNOWN" ] + description: Scope of the IPv6 address + flags: + type: array + description: Array of flags of the IPv6 address + items: + type: string example: - name: "eth0" - default: true carrier: true speed: 1000 tx: @@ -190,10 +390,10 @@ components: rx: num: 8.1 unit: "MB" - ipv4: ["192.168.0.123"] - ipv6: ["fe80::1234:5678:9abc:def0", "2001:db8::1234:5678:9abc:def0"] + flags: [ "UP", "BROADCAST", "RUNNING", "MULTICAST" ] + ipv4: [ { "address": "192.168.0.123", "netmask": "255.255.255.0" } ] + ipv6: [ { "address": "fe80::1234:5678:9abc:def0", "netmask": "ffff:ffff:ffff:ffff::", "type": "LL", "prefix": 64, "scope": "LINK", "flags": ["PERMANENT"] }, { "address": "2001:db8::1234:5678:9abc:def0", "netmask": "ffff:ffff:ffff:ffff::", "type": "GUA", "prefix": 64, "scope": "GLOBAL", "flags": [] } ] - name: "wlan0" - default: false carrier: false speed: -1 tx: @@ -202,10 +402,10 @@ components: rx: num: 0 unit: "B" + flags: [] ipv4: [] ipv6: [] - name: "wg0" - default: false carrier: true speed: -1 tx: @@ -214,8 +414,9 @@ components: rx: num: 222.3 unit: "kB" - ipv4: ["10.1.0.1"] - ipv6: ["fd00:4711::1"] + flags: [ "UP", "POINTOPOINT", "RUNNING", "NOARP" ] + ipv4: [ { "address": "10.1.0.1", "netmask": "255.255.255.0" } ] + ipv6: [ { "address": "fd00:4711::1", "netmask": "ffff:ffff:ffff:ffff::", "type": "ULA", "prefix": 64, "scope": "GLOBAL", "flags": [ "PERMANENT" ] } ] devices: type: object properties: diff --git a/src/api/network.c b/src/api/network.c index 532a985e1..d3d7832e2 100644 --- a/src/api/network.c +++ b/src/api/network.c @@ -24,68 +24,484 @@ #include "database/query-table.h" // config struct #include "config/config.h" +// PRIx64 +#include +#include +// IFA_LINK and friends +#include + + +struct flag_names { + uint32_t flag; + const char *name; +}; + +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 ifaf_flags[] = { + { 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 ripv4[] = { + { RTF_UP, "UP" }, + { RTF_GATEWAY, "GATEWAY" }, + { RTF_HOST, "HOST" }, + { RTF_REINSTATE, "REINSTATE" }, + { RTF_DYNAMIC, "DYNAMIC" }, + { RTF_MODIFIED, "MODIFIED" }, + { RTF_MTU, "MTU" }, + { RTF_MSS, "MSS" }, + { RTF_WINDOW, "WINDOW" }, + { RTF_IRTT, "IRTT" }, + { RTF_REJECT, "REJECT" }, + { RTF_STATIC, "STATIC" }, + { RTF_XRESOLVE, "XRESOLVE" }, + { RTF_NOFORWARD, "NOFORWARD" }, + { RTF_THROW, "THROW" }, + { RTF_NOPMTUDISC, "NOPMTUDISC" }, +}; + +static struct flag_names ripv6[] = { + { RTF_DEFAULT, "DEFAULT" }, + { RTF_ALLONLINK, "ALLONLINK" }, + { RTF_ADDRCONF, "ADDRCONF" }, + { RTF_LINKRT, "LINKRT" }, + { RTF_NONEXTHOP, "NONEXTHOP" }, + { RTF_CACHE, "CACHE" }, + { RTF_FLOW, "FLOW" }, + { RTF_POLICY, "POLICY" }, + { RTF_LOCAL, "LOCAL" }, + { RTF_INTERFACE, "INTERFACE" }, + { RTF_MULTICAST, "MULTICAST" }, + { RTF_BROADCAST, "BROADCAST" }, + { RTF_NAT, "NAT" }, + { RTF_ADDRCLASSMASK, "ADDRCLASSMASK" }, +}; + +// 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 + + +static struct flag_names scopes[] = { + { IFA_GLOBAL, "GLOBAL" }, + { IFA_HOST, "HOST" }, + { IFA_LINK, "LINK" }, + { IFA_SITE, "SITE" }, + { IFA_COMPATv4, "COMPATv4" }, +}; + +static bool ipv6_hex_to_human(const char oct[33], char human[INET6_ADDRSTRLEN], const char **addr_type) +{ + strncpy(human, oct, 32); + // Insert ":" into address string + for(size_t i = 1; i < 8; i++) + { + const size_t m = 4*i + i - 1; + memmove(&human[m + 1], &human[m], INET6_ADDRSTRLEN - m); + human[m] = ':'; + } + // Add trailing null byte + human[INET6_ADDRSTRLEN - 1] = '\0'; + + // Format address into most-compact form, e.g. + // "fe80:0000:0000:0000:0042:3dff:feb1:d93d" -> "fe80::42:3dff:feb1:d93d" + // If conversion fails, return false and keep the non-compact form + struct in6_addr addr6 = { 0 }; + if(inet_pton(AF_INET6, human, &addr6)) + { + if(addr_type != NULL) + { + // Extract first byte + // We do not directly access the underlying union as + // MUSL defines it differently than GNU C + uint8_t bytes[2]; + memcpy(&bytes, &addr6, 2); + // Global Unicast Address (2000::/3, RFC 4291) + if((bytes[0] & 0x70) == 0x20) + *addr_type = "GUA"; + // Unique Local Address (fc00::/7, RFC 4193) + if((bytes[0] & 0xfe) == 0xfc) + *addr_type = "ULA"; + // Link Local Address (fe80::/10, RFC 4291) + if((bytes[0] & 0xff) == 0xfe && (bytes[1] & 0x30) == 0) + *addr_type = "LL"; + } -static bool getDefaultInterface(char iface[IF_NAMESIZE], in_addr_t *gw) + return inet_ntop(AF_INET6, &addr6, human, INET6_ADDRSTRLEN); + } + + return false; +} + +static bool read_proc_net_if_inet6(cJSON *addresses) { - // 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__; + // 4.1. if_inet6 + // + // Type: One line per address containing multiple values + // + // Here all configured IPv6 addresses are shown in a special format. The + // example displays for loopback interface only. The meaning is shown + // below (see "net/ipv6/addrconf.c" for more). + // + // # cat /proc/net/if_inet6 + // 00000000000000000000000000000001 01 80 10 80 lo + // +------------------------------+ ++ ++ ++ ++ ++ + // | | | | | | + // 1 2 3 4 5 6 + // + // 1. IPv6 address displayed in 32 hexadecimal chars without colons as separator + // 2. Netlink device number (interface index) in hexadecimal (see "ip addr" , too) + // 3. Prefix length in hexadecimal + // 4. Scope value (see kernel source " include/net/ipv6.h" and "net/ipv6/addrconf.c" for more) + // 5. Interface flags (see "include/linux/rtnetlink.h" and "net/ipv6/addrconf.c" for more) + // 6. Device name + + // Open /proc/net/if_inet6 + FILE *file; + if((file = fopen("/proc/net/if_inet6", "r"))) + { + // Parse /proc/net/if_inet6 - the kernel's IPv6 address table + char buf[1024] = { 0 }; + while(fgets(buf, sizeof(buf), file)) + { + char oct[33] = { 0 }; + unsigned int ifaceid = 0; + char iface[IF_NAMESIZE] = { 0 }; + unsigned int prefix = 0; + unsigned int scope = 0; + unsigned int flags = 0; + + // Parse address information + if(sscanf(buf, "%32s %x %x %x %x %15s", oct, &ifaceid, &prefix, &scope, &flags, iface) != 6) + continue; + + char addr_str[INET6_ADDRSTRLEN] = { 0 }; + const char *addr_type = "UNKNOWN"; + ipv6_hex_to_human(oct, addr_str, &addr_type); + + // Format flags into human-readable array of strings + cJSON *flag_array = cJSON_CreateArray(); + for(size_t i = 0; i < sizeof(ifaf_flags) / sizeof(ifaf_flags[0]); i++) + if(flags & ifaf_flags[i].flag) + cJSON_AddItemToArray(flag_array, cJSON_CreateStringReference(ifaf_flags[i].name)); + + // Create new address record + cJSON *address = cJSON_CreateObject(); + cJSON_AddStringToObject(address, "address", addr_str); + cJSON_AddItemReferenceToObject(address, "type", cJSON_CreateStringReference(addr_type)); + cJSON_AddStringToObject(address, "interface", iface); + cJSON_AddNumberToObject(address, "prefix", prefix); + const char *scope_str = "UNSPEC"; + for(size_t i = 0; i < sizeof(scopes) / sizeof(scopes[0]); i++) + if(scope == scopes[i].flag) + scope_str = scopes[i].name; + cJSON_AddItemToObject(address, "scope", cJSON_CreateStringReference(scope_str)); + cJSON_AddItemToObject(address, "flags", flag_array); + + // Add address to JSON array + cJSON_AddItemToArray(addresses, address); + } + + fclose(file); + } + else + { + log_err("Cannot read /proc/net/if_inet6: %s", strerror(errno)); + return false; + } + return true; +} + +static bool read_proc_net_route(cJSON *routes) +{ + // Open /proc/net/route FILE *file; if((file = fopen("/proc/net/route", "r"))) { // Parse /proc/net/route - the kernel's IPv4 routing table + cJSON *ipv4 = cJSON_CreateArray(); char buf[1024] = { 0 }; while(fgets(buf, sizeof(buf), file)) { - 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; + char iface[IF_NAMESIZE] = { 0 }; + unsigned long dest = 0, gw = 0; + unsigned int flags = 0; + int metric = 0; - // Only analyze routes which are UP and whose - // destinations are a gateway - if(!(flags & RTF_UP) || !(flags & RTF_GATEWAY)) + // Parse route information + if(sscanf(buf, "%15s %lx %lx %x %*i %*i %i", iface, &dest, &gw, &flags, &metric) != 5) continue; - // Only analyze "catch all" routes (destination 0.0.0.0) - if(dest_r != 0) - continue; + cJSON *entry = cJSON_CreateObject(); + + // Format destination and gateway addresses + char dest_addr[INET_ADDRSTRLEN] = { 0 }; + char gw_addr[INET_ADDRSTRLEN] = { 0 }; + inet_ntop(AF_INET, &dest, dest_addr, sizeof(dest_addr)); + inet_ntop(AF_INET, &gw, gw_addr, sizeof(gw_addr)); + + // Format flags into human-readable array of strings + cJSON *flag_array = cJSON_CreateArray(); + for(size_t i = 0; i < sizeof(ripv4) / sizeof(ripv4[0]); i++) + if(flags & ripv4[i].flag) + cJSON_AddItemToArray(flag_array, cJSON_CreateStringReference(ripv4[i].name)); + + // Add route information to JSON object + cJSON_AddStringToObject(entry, "destination", dest_addr); + cJSON_AddStringToObject(entry, "gateway", gw_addr); + cJSON_AddNumberToObject(entry, "metric", metric); + cJSON_AddItemToObject(entry, "flags", flag_array); + cJSON_AddStringToObject(entry, "interface", iface); + + // Add route information to JSON array + cJSON_AddItemToArray(ipv4, entry); + } - // 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); + fclose(file); - 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); - } + // Add IPv4 routes to JSON object + cJSON_AddItemToObject(routes, "ipv4", ipv4); + } + return true; +} + +static bool read_proc_net_ipv6_route(cJSON *routes) +{ + // Open /proc/net/route + FILE *file; + + // Open /proc/net/ipv6_route + if((file = fopen("/proc/net/ipv6_route", "r"))) + { + // Parse /proc/net/ipv6_route - the kernel's IPv6 routing table + // 4.2. ipv6_route + // + // Type: One line per route containing multiple values + // + // Here all configured IPv6 routes are shown in a special + // format. The example displays for loopback interface only. The + // meaning is shown below (see ”net/ipv6/route.c” for more). + // + // # cat /proc/net/ipv6_route + // 00000000000000000000000000000000 00 00000000000000000000000000000000 00 00000000000000000000000000000000 ffffffff 00000001 00000001 00200200 lo + // +------------------------------+ ++ +------------------------------+ ++ +------------------------------+ +------+ +------+ +------+ +------+ ++ + // | | | | | | | | | | + // 1 2 3 4 5 6 7 8 9 10 + // + // 1. IPv6 destination network displayed in 32 hexadecimal chars without colons as separator + // 2. IPv6 destination prefix length in hexadecimal + // 3. IPv6 source network displayed in 32 hexadecimal chars without colons as separator + // 4. IPv6 source prefix length in hexadecimal + // 5. IPv6 next hop displayed in 32 hexadecimal chars without colons as separator + // 6. Metric in hexadecimal + // 7. Reference counter + // 8. Use counter + // 9. Flags + // 10. Device name + + cJSON *ipv6 = cJSON_CreateArray(); + + char buf[1024] = { 0 }; + while(fgets(buf, sizeof(buf), file)) + { + char iface[IF_NAMESIZE] = { 0 }; + char dest[33] = { 0 }; + char src[33] = { 0 }; + char gw[33] = { 0 }; + unsigned int prefix_dest = 0; + unsigned int prefix_src = 0; + unsigned int metric = 0; + unsigned int ref = 0; + unsigned int use = 0; + unsigned int flags = 0; + + // Parse route information + if(sscanf(buf, "%32s %x %32s %x %32s %x %x %x %x %15s", + dest, &prefix_dest, src, &prefix_src, gw, &metric, &ref, &use, &flags, iface) != 10) + continue; + + // Format flags into human-readable array of strings + cJSON *flag_array = cJSON_CreateArray(); + for(size_t i = 0; i < sizeof(ripv4) / sizeof(ripv4[0]); i++) + if(flags & ripv4[i].flag) + cJSON_AddItemToArray(flag_array, cJSON_CreateStringReference(ripv4[i].name)); + for(size_t i = 0; i < sizeof(ripv6) / sizeof(ripv6[0]); i++) + if(flags & ripv6[i].flag) + cJSON_AddItemToArray(flag_array, cJSON_CreateStringReference(ripv6[i].name)); + + // Format destination, source, and gateway addresses + char dest_addr[INET6_ADDRSTRLEN] = { 0 }; + const char *dest_addr_type = "UNSPEC"; + char src_addr[INET6_ADDRSTRLEN] = { 0 }; + const char *src_addr_type = "UNSPEC"; + char gw_addr[INET6_ADDRSTRLEN] = { 0 }; + const char *gw_addr_type = "UNSPEC"; + ipv6_hex_to_human(dest, dest_addr, &dest_addr_type); + ipv6_hex_to_human(src, src_addr, &src_addr_type); + ipv6_hex_to_human(gw, gw_addr, &gw_addr_type); + + // Create new route record + cJSON *entry = cJSON_CreateObject(); + + cJSON *destination = cJSON_CreateObject(); + cJSON_AddStringToObject(destination, "address", dest_addr); + cJSON_AddNumberToObject(destination, "prefix", prefix_dest); + cJSON_AddItemToObject(destination, "type", cJSON_CreateStringReference(dest_addr_type)); + cJSON_AddItemToObject(entry, "destination", destination); + + cJSON *source = cJSON_CreateObject(); + cJSON_AddStringToObject(source, "address", src_addr); + cJSON_AddNumberToObject(source, "prefix", prefix_src); + cJSON_AddItemToObject(source, "type", cJSON_CreateStringReference(src_addr_type)); + cJSON_AddItemToObject(entry, "source", source); + + cJSON *gateway = cJSON_CreateObject(); + cJSON_AddStringToObject(gateway, "address", gw_addr); + cJSON_AddItemToObject(gateway, "type", cJSON_CreateStringReference(gw_addr_type)); + cJSON_AddItemToObject(entry, "gateway", gateway); + + cJSON_AddNumberToObject(entry, "metric", metric); + cJSON_AddNumberToObject(entry, "ref", ref); + cJSON_AddNumberToObject(entry, "use", use); + cJSON_AddItemToObject(entry, "flags", flag_array); + cJSON_AddStringToObject(entry, "interface", iface); + + // Add route information to JSON array + cJSON_AddItemToArray(ipv6, entry); } + + // Add IPv6 routes to JSON object + cJSON_AddItemToObject(routes, "ipv6", ipv6); + fclose(file); } - else - log_err("Cannot read /proc/net/route: %s", strerror(errno)); - // Return success based on having found the default gateway's address - return gw != 0; + return true; } int api_network_gateway(struct ftl_conn *api) { - in_addr_t gw = 0; - char iface[IF_NAMESIZE] = { 0 }; + cJSON *json = JSON_NEW_OBJECT(); + + // Get JSON routes + cJSON *routes = cJSON_CreateObject(); + read_proc_net_route(routes); + read_proc_net_ipv6_route(routes); + + // Search for route with GATEWAY flag set + cJSON *ipv4 = cJSON_GetObjectItem(routes, "ipv4"); + cJSON *r_ipv4 = cJSON_CreateObject(); + cJSON *route = NULL; + cJSON_ArrayForEach(route, ipv4) + { + cJSON *flags = cJSON_GetObjectItem(route, "flags"); + if(cJSON_IsArray(flags)) + { + cJSON *flag = NULL; + cJSON_ArrayForEach(flag, flags) + { + if(strcmp(cJSON_GetStringValue(flag), "GATEWAY") == 0) + { + // Extract interface name + const char *iface_name = cJSON_GetStringValue(cJSON_GetObjectItem(route, "interface")); + JSON_COPY_STR_TO_OBJECT(r_ipv4, "interface", iface_name); + + // Extract gateway address + const char *gw_addr = cJSON_GetStringValue(cJSON_GetObjectItem(route, "gateway")); + JSON_COPY_STR_TO_OBJECT(r_ipv4, "address", gw_addr); + + break; + } + } + } + } + + // else: Search ipv6 routes + cJSON *ipv6 = cJSON_GetObjectItem(routes, "ipv6"); + cJSON *r_ipv6 = cJSON_CreateObject(); + cJSON_ArrayForEach(route, ipv6) + { + cJSON *flags = cJSON_GetObjectItem(route, "flags"); + if(cJSON_IsArray(flags)) + { + cJSON *flag = NULL; + cJSON_ArrayForEach(flag, flags) + { + if(strcmp(cJSON_GetStringValue(flag), "GATEWAY") == 0) + { + // Extract interface name + const char *iface_name = cJSON_GetStringValue(cJSON_GetObjectItem(route, "interface")); + JSON_COPY_STR_TO_OBJECT(r_ipv6, "interface", iface_name); + + // Extract gateway address + const char *gw_addr = cJSON_GetStringValue(cJSON_GetObjectItem(cJSON_GetObjectItem(route, "gateway"), "address")); + + JSON_COPY_STR_TO_OBJECT(r_ipv6, "address", gw_addr); + break; + } + } + } + } - // Get default interface - getDefaultInterface(iface, &gw); + // Add gateway information to JSON object + JSON_ADD_ITEM_TO_OBJECT(json, "ipv4", r_ipv4); + JSON_ADD_ITEM_TO_OBJECT(json, "ipv6", r_ipv6); - // Generate JSON response + cJSON_Delete(routes); + + JSON_SEND_OBJECT(json); +} + +int api_network_routes(struct ftl_conn *api) +{ + // Add routing information + cJSON *routes = JSON_NEW_OBJECT(); + read_proc_net_route(routes); + read_proc_net_ipv6_route(routes); 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); } @@ -93,11 +509,6 @@ 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; @@ -119,6 +530,10 @@ int api_network_interfaces(struct ftl_conn *api) if(getifaddrs(&ifap) == -1) log_err("API: Cannot get interface addresses: %s", strerror(errno)); + // Parse IPv6 address details + cJSON *ipv6a = JSON_NEW_ARRAY(); + read_proc_net_if_inet6(ipv6a); + cJSON *interfaces = JSON_NEW_ARRAY(); // Walk /sys/class/net directory while ((dp = readdir(dfd)) != NULL) @@ -134,10 +549,6 @@ int api_network_interfaces(struct ftl_conn *api) 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); @@ -230,30 +641,74 @@ int api_network_interfaces(struct ftl_conn *api) // 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) + if(family != AF_INET && family != AF_INET6) + continue; + // 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) { - // 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; - } + log_warn("API: getnameinfo(1) failed: %s\n", gai_strerror(s)); + continue; + } + // Get netmask + char netmask[NI_MAXHOST] = { 0 }; + const int s2 = getnameinfo(ifa->ifa_netmask, + (family == AF_INET) ? + sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6), + netmask, NI_MAXHOST, + NULL, 0, NI_NUMERICHOST); + if (s2 != 0) + { + log_warn("API: getnameinfo(2) failed: %s\n", gai_strerror(s2)); + continue; + } - if(family == AF_INET) - { - JSON_COPY_STR_TO_ARRAY(ipv4, host); - } - else if(family == AF_INET6) + cJSON *new_addr = JSON_NEW_OBJECT(); + JSON_COPY_STR_TO_OBJECT(new_addr, "address", host); + JSON_COPY_STR_TO_OBJECT(new_addr, "netmask", netmask); + + if(family == AF_INET) + { + // Add IPv4 address to array + JSON_ADD_ITEM_TO_ARRAY(ipv4, new_addr); + } + else if(family == AF_INET6) + { + // Search address in ipv6a array and add further details + cJSON *ipv6_entry = NULL; + cJSON_ArrayForEach(ipv6_entry, ipv6a) { - JSON_COPY_STR_TO_ARRAY(ipv6, host); + // Compare interface and address + const char *arr_name = cJSON_GetStringValue(cJSON_GetObjectItem(ipv6_entry, "interface")); + const char *arr_addr = cJSON_GetStringValue(cJSON_GetObjectItem(ipv6_entry, "address")); + // We compare only the first part of the address as the second part may be an interface specifier (%veth...) + if(strcmp(arr_name, iface_name) == 0 && strncmp(arr_addr, host, min(strlen(arr_addr), strlen(host))) == 0) + { + // Copy details from ipv6a array to new_addr (prefix, scope, flags) + JSON_ADD_ITEM_TO_OBJECT(new_addr, "type", cJSON_Duplicate(cJSON_GetObjectItem(ipv6_entry, "type"), true)); + JSON_ADD_NUMBER_TO_OBJECT(new_addr, "prefix", cJSON_GetNumberValue(cJSON_GetObjectItem(ipv6_entry, "prefix"))); + JSON_ADD_ITEM_TO_OBJECT(new_addr, "scope", cJSON_Duplicate(cJSON_GetObjectItem(ipv6_entry, "scope"), true)); + JSON_ADD_ITEM_TO_OBJECT(new_addr, "flags", cJSON_Duplicate(cJSON_GetObjectItem(ipv6_entry, "flags"), true)); + break; + } } + + // Add IPv6 address to array + JSON_ADD_ITEM_TO_ARRAY(ipv6, new_addr); } + + // Format flags into human-readable array of strings + cJSON *flag_array = cJSON_CreateArray(); + for(size_t i = 0; i < sizeof(iff_flags) / sizeof(iff_flags[0]); i++) + if(ifa->ifa_flags & iff_flags[i].flag) + cJSON_AddItemToArray(flag_array, cJSON_CreateStringReference(iff_flags[i].name)); + JSON_ADD_ITEM_TO_OBJECT(iface, "flags", flag_array); } JSON_ADD_ITEM_TO_OBJECT(iface, "ipv4", ipv4); JSON_ADD_ITEM_TO_OBJECT(iface, "ipv6", ipv6); @@ -271,6 +726,8 @@ int api_network_interfaces(struct ftl_conn *api) freeifaddrs(ifap); closedir(dfd); + cJSON_Delete(ipv6a); + ipv6a = NULL; cJSON *sum = JSON_NEW_OBJECT(); JSON_COPY_STR_TO_OBJECT(sum, "name", "sum"); From 6cf1a6774e83958b85186057541d6c51131cc9bf Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 9 Jul 2024 21:57:45 +0200 Subject: [PATCH 2/7] Add netlink implementation Signed-off-by: DL6ER --- src/api/docs/content/specs/network.yaml | 528 +++++++++------- src/api/network.c | 744 ++-------------------- src/dnsmasq/network.c | 43 ++ src/syscalls/CMakeLists.txt | 3 + src/syscalls/netlink.c | 796 ++++++++++++++++++++++++ src/syscalls/netlink.h | 53 ++ src/syscalls/netlink_consts.h | 598 ++++++++++++++++++ src/webserver/json_macros.h | 9 + 8 files changed, 1833 insertions(+), 941 deletions(-) create mode 100644 src/syscalls/netlink.c create mode 100644 src/syscalls/netlink.h create mode 100644 src/syscalls/netlink_consts.h diff --git a/src/api/docs/content/specs/network.yaml b/src/api/docs/content/specs/network.yaml index ddc7efcdb..60a68f226 100644 --- a/src/api/docs/content/specs/network.yaml +++ b/src/api/docs/content/specs/network.yaml @@ -33,8 +33,12 @@ components: 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. + 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 @@ -58,8 +62,12 @@ components: 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 @@ -144,158 +152,132 @@ components: gateway: type: object properties: - ipv4: - type: object - description: IPv4 gateway information - properties: - interface: - type: string - description: Interface - example: "eth0" - address: - type: string - description: Address of the gateway - example: "192.168.0.1" - ipv6: - type: object - description: IPv6 gateway information - properties: - interface: - type: string - description: Interface - example: "eth0" - address: - type: string - description: Address of the gateway - example: "fe80::3587:2fff:f11a:4321" + 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 + example: + - family: "inet" + interface: "eth0" + address: "192.168.0.2" + - family: "inet6" + interface: "eth0" + address: "fe80::3587:2fff:f11a:4321" routes: type: object properties: routes: - type: object - description: Routing table - properties: - ipv4: - type: array - description: Array of IPv4 routes - items: - type: object - properties: - destination: - type: string - description: Destination of the route - gateway: - type: string - description: Gateway of the route - metric: - type: integer - description: Metric of the route - flags: - type: array - description: Array of flags of the route - items: - type: string - interface: - type: string - description: Interface of the route - example: - - destination: "0.0.0.0" - gateway: "192.168.1.1" - metric: 0 - flags: [ "UP", "GATEWAY" ] - interface: "eth0" - - destination: "10.100.0.0" - gateway: "0.0.0.0" - metric: 0 - flags: [ "UP" ] - interface: "wg0" - ipv6: - type: array - description: Array of IPv6 routes - items: - type: object - properties: - destination: - type: object - description: Destination of the route - properties: - address: - type: string - description: IPv6 address - prefix: - type: integer - description: Prefix of the IPv6 address - type: - type: string - enum: [ "LL", "GUA", "ULA", "UNSPEC" ] - description: Type of the IPv6 address - source: - type: object - description: Source of the route - properties: - address: - type: string - description: IPv6 address - prefix: - type: integer - description: Prefix of the IPv6 address - type: - type: string - enum: [ "LL", "GUA", "ULA", "UNSPEC" ] - description: Type of the IPv6 address - gateway: - type: object - description: Gateway of the route - properties: - address: - type: string - description: IPv6 address - type: - type: string - enum: [ "LL", "GUA", "ULA", "UNSPEC" ] - description: Type of the IPv6 address - metric: - type: integer - description: Metric of the route - ref: - type: integer - description: Reference count of the route - use: - type: integer - description: Use count of the route - flags: - type: array - description: Array of flags of the route - items: - type: string - interface: - type: string - description: Interface of the route - example: - - destination: { "address": "2001:db8::", "prefix": 32, "type": "GUA" } - source: { "address": "2001:db8::1", "prefix": 128, "type": "GUA" } - gateway: { "address": "fe80::1", "type": "LL" } - metric: 0 - ref: 0 - use: 0 - flags: [ "UP", "GATEWAY" ] - interface: "eth0" - - destination: { "address": "::1", prefix: 128, "type": "UNSPEC" } - source: { "address": "::", prefix: 0, "type": "UNSPEC" } - gateway: { "address": "::", "type": "UNSPEC" } - metric: 256 - ref: 2 - use: 0 - flags: [ "UP" ] - interface: "lo" - - destination: { "address": "fd00:4711:0", prefix: 64, "type": "ULA" } - source: { "address": "::", prefix: 0, "type": "UNSPEC" } - gateway: { "address": "::", "type": "UNSPEC" } - metric: 256 - ref: 1 - use: 0 - flags: [ "UP" ] - interface: "wg0" + 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: main + description: Routing table ID (0 = unspecified, 253 = default, 254 = local, 255 = local) + 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: string + description: Route priority + + 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: "medium" + 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: "unknown" + oif: "wg0" + interfaces: type: object properties: @@ -307,116 +289,184 @@ components: properties: name: type: string - nullable: true description: Interface name - 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 + nullable: true + description: Speed of the interface in Mbit/s (`null` if not applicable) + type: + type: string + description: Type of the interface flags: type: array - description: Array of interface flags + description: Array of address flags items: type: string - ipv4: - type: array - nullable: true - description: Array of associated IPv4 addresses - items: - type: object - properties: - address: - type: string - description: IPv4 address - netmask: - type: string - description: Netmask of the IPv4 address - 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 + broadcase: + type: string + description: Interface broadcast address + perm_address: + type: string + description: Interface permanent hardware address + addresses: type: array nullable: true - description: Array of associated IPv6 addresses + description: Array of associated IPv addresses items: type: object properties: address: type: string - description: IPv6 address - netmask: + description: Interface address + local: type: string - description: Netmask of the IPv4 address - type: + description: Local address + family: type: string - enum: [ "LL", "GUA", "ULA", "UNSPEC" ] - description: Type of the IPv6 address - prefix: - type: integer - description: Prefix of the IPv6 address - scope: - type: string - enum: [ "LINK", "GLOBAL", "HOST", "SITE", "COMPATv4", "UNKNOWN" ] - description: Scope of the IPv6 address + enum: [ "inet", "inet6", "link", "mpls", "bridge", "???" ] + description: Address family flags: type: array - description: Array of flags of the IPv6 address + 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 (relative to the system uptime) + tstamp: + type: number + description: Updated timestamp of the address (relative to the system uptime) example: - - name: "eth0" + - 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" + addresses: + - address: "127.0.0.1" + local: "127.0.0.1" + family: "inet" + scope: "host" + flags: [ "permanent" ] + prefixlen: 8 + label: "lo" + prefered: 4294967295 + valid: 4294967295 + cstamp: 6.1 + tstamp: 6.1 + - address: "::1" + local: "::1" + family: "inet6" + scope: "host" + flags: [ "permanent" ] + prefixlen: 128 + label: "lo" + prefered: 4294967295 + valid: 4294967295 + cstamp: 6.1 + tstamp: 6.1 + - name: "eth0" speed: 1000 - tx: - num: 10.4 - unit: "MB" - rx: - num: 8.1 - unit: "MB" - flags: [ "UP", "BROADCAST", "RUNNING", "MULTICAST" ] - ipv4: [ { "address": "192.168.0.123", "netmask": "255.255.255.0" } ] - ipv6: [ { "address": "fe80::1234:5678:9abc:def0", "netmask": "ffff:ffff:ffff:ffff::", "type": "LL", "prefix": 64, "scope": "LINK", "flags": ["PERMANENT"] }, { "address": "2001:db8::1234:5678:9abc:def0", "netmask": "ffff:ffff:ffff:ffff::", "type": "GUA", "prefix": 64, "scope": "GLOBAL", "flags": [] } ] - - name: "wlan0" - carrier: false - speed: -1 - tx: - num: 0 - unit: "B" - rx: - num: 0 - unit: "B" - flags: [] - 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" + addresses: + - address: "192.168.0.123" + local: "192.168.0.123" + family: "inet" + scope: "universe" + flags: [ "permanent" ] + prefixlen: 24 + label: "eth0" + prefered: 4294967295 + valid: 4294967295 + cstamp: 11.23 + tstamp: 11.23 + - address: "2001:db8::1234:5678:9abc:def0" + 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" + family: "inet6" + scope: "universe" + flags: [] + prefixlen: 64 + label: "eth0" + prefered: 3461 + valid: 7061 + cstamp: 12.5 + tstamp: 2827298.75 + - address: "fe80::1234:5678:9abc:def0" + family: "inet6" + scope: "link" + flags: [ "permanent" ] + prefixlen: 64 + label: "eth0" + prefered: 4294967295 + valid: 4294967295 + cstamp: 11.23 + tstamp: 11.23 - name: "wg0" + 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" - flags: [ "UP", "POINTOPOINT", "RUNNING", "NOARP" ] - ipv4: [ { "address": "10.1.0.1", "netmask": "255.255.255.0" } ] - ipv6: [ { "address": "fd00:4711::1", "netmask": "ffff:ffff:ffff:ffff::", "type": "ULA", "prefix": 64, "scope": "GLOBAL", "flags": [ "PERMANENT" ] } ] + addresses: + - address: "10.1.0.1" + local: "10.1.0.1" + scope: "universe" + flags: [ "permanent" ] + prefixlen: 24 + label: "wg0" + prefered: 4294967295 + valid: 4294967295 + cstamp: 11.23 + tstamp: 11.23 + - address: "fd00:4711::1" + family: "inet6" + scope: "global" + flags: [ "permanent" ] + prefixlen: 64 + label: "wg0" + prefered: 4294967295 + valid: 4294967295 + cstamp: 11.23 + tstamp: 11.23 devices: type: object properties: @@ -505,3 +555,11 @@ components: type: integer required: true example: 1 + detailed: + in: query + description: (Optional) Detailed interface information + name: detailed + schema: + type: boolean + required: false + example: false diff --git a/src/api/network.c b/src/api/network.c index d3d7832e2..1deeeeaab 100644 --- a/src/api/network.c +++ b/src/api/network.c @@ -29,477 +29,58 @@ #include // IFA_LINK and friends #include - - -struct flag_names { - uint32_t flag; - const char *name; -}; - -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 ifaf_flags[] = { - { 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 ripv4[] = { - { RTF_UP, "UP" }, - { RTF_GATEWAY, "GATEWAY" }, - { RTF_HOST, "HOST" }, - { RTF_REINSTATE, "REINSTATE" }, - { RTF_DYNAMIC, "DYNAMIC" }, - { RTF_MODIFIED, "MODIFIED" }, - { RTF_MTU, "MTU" }, - { RTF_MSS, "MSS" }, - { RTF_WINDOW, "WINDOW" }, - { RTF_IRTT, "IRTT" }, - { RTF_REJECT, "REJECT" }, - { RTF_STATIC, "STATIC" }, - { RTF_XRESOLVE, "XRESOLVE" }, - { RTF_NOFORWARD, "NOFORWARD" }, - { RTF_THROW, "THROW" }, - { RTF_NOPMTUDISC, "NOPMTUDISC" }, -}; - -static struct flag_names ripv6[] = { - { RTF_DEFAULT, "DEFAULT" }, - { RTF_ALLONLINK, "ALLONLINK" }, - { RTF_ADDRCONF, "ADDRCONF" }, - { RTF_LINKRT, "LINKRT" }, - { RTF_NONEXTHOP, "NONEXTHOP" }, - { RTF_CACHE, "CACHE" }, - { RTF_FLOW, "FLOW" }, - { RTF_POLICY, "POLICY" }, - { RTF_LOCAL, "LOCAL" }, - { RTF_INTERFACE, "INTERFACE" }, - { RTF_MULTICAST, "MULTICAST" }, - { RTF_BROADCAST, "BROADCAST" }, - { RTF_NAT, "NAT" }, - { RTF_ADDRCLASSMASK, "ADDRCLASSMASK" }, -}; - -// 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 - - -static struct flag_names scopes[] = { - { IFA_GLOBAL, "GLOBAL" }, - { IFA_HOST, "HOST" }, - { IFA_LINK, "LINK" }, - { IFA_SITE, "SITE" }, - { IFA_COMPATv4, "COMPATv4" }, -}; - -static bool ipv6_hex_to_human(const char oct[33], char human[INET6_ADDRSTRLEN], const char **addr_type) -{ - strncpy(human, oct, 32); - // Insert ":" into address string - for(size_t i = 1; i < 8; i++) - { - const size_t m = 4*i + i - 1; - memmove(&human[m + 1], &human[m], INET6_ADDRSTRLEN - m); - human[m] = ':'; - } - // Add trailing null byte - human[INET6_ADDRSTRLEN - 1] = '\0'; - - // Format address into most-compact form, e.g. - // "fe80:0000:0000:0000:0042:3dff:feb1:d93d" -> "fe80::42:3dff:feb1:d93d" - // If conversion fails, return false and keep the non-compact form - struct in6_addr addr6 = { 0 }; - if(inet_pton(AF_INET6, human, &addr6)) - { - if(addr_type != NULL) - { - // Extract first byte - // We do not directly access the underlying union as - // MUSL defines it differently than GNU C - uint8_t bytes[2]; - memcpy(&bytes, &addr6, 2); - // Global Unicast Address (2000::/3, RFC 4291) - if((bytes[0] & 0x70) == 0x20) - *addr_type = "GUA"; - // Unique Local Address (fc00::/7, RFC 4193) - if((bytes[0] & 0xfe) == 0xfc) - *addr_type = "ULA"; - // Link Local Address (fe80::/10, RFC 4291) - if((bytes[0] & 0xff) == 0xfe && (bytes[1] & 0x30) == 0) - *addr_type = "LL"; - } - - return inet_ntop(AF_INET6, &addr6, human, INET6_ADDRSTRLEN); - } - - return false; -} - -static bool read_proc_net_if_inet6(cJSON *addresses) -{ - // 4.1. if_inet6 - // - // Type: One line per address containing multiple values - // - // Here all configured IPv6 addresses are shown in a special format. The - // example displays for loopback interface only. The meaning is shown - // below (see "net/ipv6/addrconf.c" for more). - // - // # cat /proc/net/if_inet6 - // 00000000000000000000000000000001 01 80 10 80 lo - // +------------------------------+ ++ ++ ++ ++ ++ - // | | | | | | - // 1 2 3 4 5 6 - // - // 1. IPv6 address displayed in 32 hexadecimal chars without colons as separator - // 2. Netlink device number (interface index) in hexadecimal (see "ip addr" , too) - // 3. Prefix length in hexadecimal - // 4. Scope value (see kernel source " include/net/ipv6.h" and "net/ipv6/addrconf.c" for more) - // 5. Interface flags (see "include/linux/rtnetlink.h" and "net/ipv6/addrconf.c" for more) - // 6. Device name - - // Open /proc/net/if_inet6 - FILE *file; - if((file = fopen("/proc/net/if_inet6", "r"))) - { - // Parse /proc/net/if_inet6 - the kernel's IPv6 address table - char buf[1024] = { 0 }; - while(fgets(buf, sizeof(buf), file)) - { - char oct[33] = { 0 }; - unsigned int ifaceid = 0; - char iface[IF_NAMESIZE] = { 0 }; - unsigned int prefix = 0; - unsigned int scope = 0; - unsigned int flags = 0; - - // Parse address information - if(sscanf(buf, "%32s %x %x %x %x %15s", oct, &ifaceid, &prefix, &scope, &flags, iface) != 6) - continue; - - char addr_str[INET6_ADDRSTRLEN] = { 0 }; - const char *addr_type = "UNKNOWN"; - ipv6_hex_to_human(oct, addr_str, &addr_type); - - // Format flags into human-readable array of strings - cJSON *flag_array = cJSON_CreateArray(); - for(size_t i = 0; i < sizeof(ifaf_flags) / sizeof(ifaf_flags[0]); i++) - if(flags & ifaf_flags[i].flag) - cJSON_AddItemToArray(flag_array, cJSON_CreateStringReference(ifaf_flags[i].name)); - - // Create new address record - cJSON *address = cJSON_CreateObject(); - cJSON_AddStringToObject(address, "address", addr_str); - cJSON_AddItemReferenceToObject(address, "type", cJSON_CreateStringReference(addr_type)); - cJSON_AddStringToObject(address, "interface", iface); - cJSON_AddNumberToObject(address, "prefix", prefix); - const char *scope_str = "UNSPEC"; - for(size_t i = 0; i < sizeof(scopes) / sizeof(scopes[0]); i++) - if(scope == scopes[i].flag) - scope_str = scopes[i].name; - cJSON_AddItemToObject(address, "scope", cJSON_CreateStringReference(scope_str)); - cJSON_AddItemToObject(address, "flags", flag_array); - - // Add address to JSON array - cJSON_AddItemToArray(addresses, address); - } - - fclose(file); - } - else - { - log_err("Cannot read /proc/net/if_inet6: %s", strerror(errno)); - return false; - } - - return true; -} - -static bool read_proc_net_route(cJSON *routes) -{ - // Open /proc/net/route - FILE *file; - if((file = fopen("/proc/net/route", "r"))) - { - // Parse /proc/net/route - the kernel's IPv4 routing table - cJSON *ipv4 = cJSON_CreateArray(); - char buf[1024] = { 0 }; - while(fgets(buf, sizeof(buf), file)) - { - char iface[IF_NAMESIZE] = { 0 }; - unsigned long dest = 0, gw = 0; - unsigned int flags = 0; - int metric = 0; - - // Parse route information - if(sscanf(buf, "%15s %lx %lx %x %*i %*i %i", iface, &dest, &gw, &flags, &metric) != 5) - continue; - - cJSON *entry = cJSON_CreateObject(); - - // Format destination and gateway addresses - char dest_addr[INET_ADDRSTRLEN] = { 0 }; - char gw_addr[INET_ADDRSTRLEN] = { 0 }; - inet_ntop(AF_INET, &dest, dest_addr, sizeof(dest_addr)); - inet_ntop(AF_INET, &gw, gw_addr, sizeof(gw_addr)); - - // Format flags into human-readable array of strings - cJSON *flag_array = cJSON_CreateArray(); - for(size_t i = 0; i < sizeof(ripv4) / sizeof(ripv4[0]); i++) - if(flags & ripv4[i].flag) - cJSON_AddItemToArray(flag_array, cJSON_CreateStringReference(ripv4[i].name)); - - // Add route information to JSON object - cJSON_AddStringToObject(entry, "destination", dest_addr); - cJSON_AddStringToObject(entry, "gateway", gw_addr); - cJSON_AddNumberToObject(entry, "metric", metric); - cJSON_AddItemToObject(entry, "flags", flag_array); - cJSON_AddStringToObject(entry, "interface", iface); - - // Add route information to JSON array - cJSON_AddItemToArray(ipv4, entry); - } - - fclose(file); - - // Add IPv4 routes to JSON object - cJSON_AddItemToObject(routes, "ipv4", ipv4); - } - return true; -} - -static bool read_proc_net_ipv6_route(cJSON *routes) -{ - // Open /proc/net/route - FILE *file; - - // Open /proc/net/ipv6_route - if((file = fopen("/proc/net/ipv6_route", "r"))) - { - // Parse /proc/net/ipv6_route - the kernel's IPv6 routing table - // 4.2. ipv6_route - // - // Type: One line per route containing multiple values - // - // Here all configured IPv6 routes are shown in a special - // format. The example displays for loopback interface only. The - // meaning is shown below (see ”net/ipv6/route.c” for more). - // - // # cat /proc/net/ipv6_route - // 00000000000000000000000000000000 00 00000000000000000000000000000000 00 00000000000000000000000000000000 ffffffff 00000001 00000001 00200200 lo - // +------------------------------+ ++ +------------------------------+ ++ +------------------------------+ +------+ +------+ +------+ +------+ ++ - // | | | | | | | | | | - // 1 2 3 4 5 6 7 8 9 10 - // - // 1. IPv6 destination network displayed in 32 hexadecimal chars without colons as separator - // 2. IPv6 destination prefix length in hexadecimal - // 3. IPv6 source network displayed in 32 hexadecimal chars without colons as separator - // 4. IPv6 source prefix length in hexadecimal - // 5. IPv6 next hop displayed in 32 hexadecimal chars without colons as separator - // 6. Metric in hexadecimal - // 7. Reference counter - // 8. Use counter - // 9. Flags - // 10. Device name - - cJSON *ipv6 = cJSON_CreateArray(); - - char buf[1024] = { 0 }; - while(fgets(buf, sizeof(buf), file)) - { - char iface[IF_NAMESIZE] = { 0 }; - char dest[33] = { 0 }; - char src[33] = { 0 }; - char gw[33] = { 0 }; - unsigned int prefix_dest = 0; - unsigned int prefix_src = 0; - unsigned int metric = 0; - unsigned int ref = 0; - unsigned int use = 0; - unsigned int flags = 0; - - // Parse route information - if(sscanf(buf, "%32s %x %32s %x %32s %x %x %x %x %15s", - dest, &prefix_dest, src, &prefix_src, gw, &metric, &ref, &use, &flags, iface) != 10) - continue; - - // Format flags into human-readable array of strings - cJSON *flag_array = cJSON_CreateArray(); - for(size_t i = 0; i < sizeof(ripv4) / sizeof(ripv4[0]); i++) - if(flags & ripv4[i].flag) - cJSON_AddItemToArray(flag_array, cJSON_CreateStringReference(ripv4[i].name)); - for(size_t i = 0; i < sizeof(ripv6) / sizeof(ripv6[0]); i++) - if(flags & ripv6[i].flag) - cJSON_AddItemToArray(flag_array, cJSON_CreateStringReference(ripv6[i].name)); - - // Format destination, source, and gateway addresses - char dest_addr[INET6_ADDRSTRLEN] = { 0 }; - const char *dest_addr_type = "UNSPEC"; - char src_addr[INET6_ADDRSTRLEN] = { 0 }; - const char *src_addr_type = "UNSPEC"; - char gw_addr[INET6_ADDRSTRLEN] = { 0 }; - const char *gw_addr_type = "UNSPEC"; - ipv6_hex_to_human(dest, dest_addr, &dest_addr_type); - ipv6_hex_to_human(src, src_addr, &src_addr_type); - ipv6_hex_to_human(gw, gw_addr, &gw_addr_type); - - // Create new route record - cJSON *entry = cJSON_CreateObject(); - - cJSON *destination = cJSON_CreateObject(); - cJSON_AddStringToObject(destination, "address", dest_addr); - cJSON_AddNumberToObject(destination, "prefix", prefix_dest); - cJSON_AddItemToObject(destination, "type", cJSON_CreateStringReference(dest_addr_type)); - cJSON_AddItemToObject(entry, "destination", destination); - - cJSON *source = cJSON_CreateObject(); - cJSON_AddStringToObject(source, "address", src_addr); - cJSON_AddNumberToObject(source, "prefix", prefix_src); - cJSON_AddItemToObject(source, "type", cJSON_CreateStringReference(src_addr_type)); - cJSON_AddItemToObject(entry, "source", source); - - cJSON *gateway = cJSON_CreateObject(); - cJSON_AddStringToObject(gateway, "address", gw_addr); - cJSON_AddItemToObject(gateway, "type", cJSON_CreateStringReference(gw_addr_type)); - cJSON_AddItemToObject(entry, "gateway", gateway); - - cJSON_AddNumberToObject(entry, "metric", metric); - cJSON_AddNumberToObject(entry, "ref", ref); - cJSON_AddNumberToObject(entry, "use", use); - cJSON_AddItemToObject(entry, "flags", flag_array); - cJSON_AddStringToObject(entry, "interface", iface); - - // Add route information to JSON array - cJSON_AddItemToArray(ipv6, entry); - } - - // Add IPv6 routes to JSON object - cJSON_AddItemToObject(routes, "ipv6", ipv6); - - fclose(file); - } - - return true; -} +// nlroutes(), nladdrs(), nllinks() +#include "syscalls/netlink.h" int api_network_gateway(struct ftl_conn *api) { - cJSON *json = JSON_NEW_OBJECT(); - // Get JSON routes - cJSON *routes = cJSON_CreateObject(); - read_proc_net_route(routes); - read_proc_net_ipv6_route(routes); + // Add routing information + cJSON *routes = JSON_NEW_ARRAY(); + nlroutes(routes, false); + cJSON *gateway = JSON_NEW_ARRAY(); - // Search for route with GATEWAY flag set - cJSON *ipv4 = cJSON_GetObjectItem(routes, "ipv4"); - cJSON *r_ipv4 = cJSON_CreateObject(); + // Search through routes for the default gateway + // They are the ones with "dst" == "default" cJSON *route = NULL; - cJSON_ArrayForEach(route, ipv4) + cJSON_ArrayForEach(route, routes) { - cJSON *flags = cJSON_GetObjectItem(route, "flags"); - if(cJSON_IsArray(flags)) + cJSON *dst = cJSON_GetObjectItem(route, "dst"); + if(dst != NULL && cJSON_IsString(dst) && strcmp(cJSON_GetStringValue(dst), "default") == 0) { - cJSON *flag = NULL; - cJSON_ArrayForEach(flag, flags) - { - if(strcmp(cJSON_GetStringValue(flag), "GATEWAY") == 0) - { - // Extract interface name - const char *iface_name = cJSON_GetStringValue(cJSON_GetObjectItem(route, "interface")); - JSON_COPY_STR_TO_OBJECT(r_ipv4, "interface", iface_name); + cJSON *gwobj = JSON_NEW_OBJECT(); - // Extract gateway address - const char *gw_addr = cJSON_GetStringValue(cJSON_GetObjectItem(route, "gateway")); - JSON_COPY_STR_TO_OBJECT(r_ipv4, "address", gw_addr); + // Extract and add family + const int family = cJSON_GetNumberValue(cJSON_GetObjectItem(route, "family")); + JSON_ADD_NUMBER_TO_OBJECT(gwobj, "family", family); - break; - } - } - } - } + // Extract and add interface name + const char *iface_name = cJSON_GetStringValue(cJSON_GetObjectItem(route, "oif")); + JSON_COPY_STR_TO_OBJECT(gwobj, "interface", iface_name); - // else: Search ipv6 routes - cJSON *ipv6 = cJSON_GetObjectItem(routes, "ipv6"); - cJSON *r_ipv6 = cJSON_CreateObject(); - cJSON_ArrayForEach(route, ipv6) - { - cJSON *flags = cJSON_GetObjectItem(route, "flags"); - if(cJSON_IsArray(flags)) - { - cJSON *flag = NULL; - cJSON_ArrayForEach(flag, flags) - { - if(strcmp(cJSON_GetStringValue(flag), "GATEWAY") == 0) - { - // Extract interface name - const char *iface_name = cJSON_GetStringValue(cJSON_GetObjectItem(route, "interface")); - JSON_COPY_STR_TO_OBJECT(r_ipv6, "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 gateway address - const char *gw_addr = cJSON_GetStringValue(cJSON_GetObjectItem(cJSON_GetObjectItem(route, "gateway"), "address")); - - JSON_COPY_STR_TO_OBJECT(r_ipv6, "address", gw_addr); - break; - } - } + cJSON_AddItemToArray(gateway, gwobj); } } - - // Add gateway information to JSON object - JSON_ADD_ITEM_TO_OBJECT(json, "ipv4", r_ipv4); - JSON_ADD_ITEM_TO_OBJECT(json, "ipv6", r_ipv6); - cJSON_Delete(routes); + cJSON *json = JSON_NEW_OBJECT(); + JSON_ADD_ITEM_TO_OBJECT(json, "gateway", gateway); JSON_SEND_OBJECT(json); } int api_network_routes(struct ftl_conn *api) { + // Get ?detailed parameter + bool detailed = false; + get_bool_var(api->request->query_string, "detailed", &detailed); + // Add routing information - cJSON *routes = JSON_NEW_OBJECT(); - read_proc_net_route(routes); - read_proc_net_ipv6_route(routes); + cJSON *routes = JSON_NEW_ARRAY(); + nlroutes(routes, detailed); cJSON *json = JSON_NEW_OBJECT(); JSON_ADD_ITEM_TO_OBJECT(json, "routes", routes); JSON_SEND_OBJECT(json); @@ -507,266 +88,17 @@ int api_network_routes(struct ftl_conn *api) int api_network_interfaces(struct ftl_conn *api) { - cJSON *json = JSON_NEW_OBJECT(); - - // 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)); - - // Parse IPv6 address details - cJSON *ipv6a = JSON_NEW_ARRAY(); - read_proc_net_if_inet6(ipv6a); + // 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; - - // 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); - - // 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); + // Get links ... + nllinks(interfaces, detailed); + // ... and enrich them with addresses + nladdrs(interfaces, detailed); - // 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) - continue; - // 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(1) failed: %s\n", gai_strerror(s)); - continue; - } - // Get netmask - char netmask[NI_MAXHOST] = { 0 }; - const int s2 = getnameinfo(ifa->ifa_netmask, - (family == AF_INET) ? - sizeof(struct sockaddr_in) : - sizeof(struct sockaddr_in6), - netmask, NI_MAXHOST, - NULL, 0, NI_NUMERICHOST); - if (s2 != 0) - { - log_warn("API: getnameinfo(2) failed: %s\n", gai_strerror(s2)); - continue; - } - - cJSON *new_addr = JSON_NEW_OBJECT(); - JSON_COPY_STR_TO_OBJECT(new_addr, "address", host); - JSON_COPY_STR_TO_OBJECT(new_addr, "netmask", netmask); - - if(family == AF_INET) - { - // Add IPv4 address to array - JSON_ADD_ITEM_TO_ARRAY(ipv4, new_addr); - } - else if(family == AF_INET6) - { - // Search address in ipv6a array and add further details - cJSON *ipv6_entry = NULL; - cJSON_ArrayForEach(ipv6_entry, ipv6a) - { - // Compare interface and address - const char *arr_name = cJSON_GetStringValue(cJSON_GetObjectItem(ipv6_entry, "interface")); - const char *arr_addr = cJSON_GetStringValue(cJSON_GetObjectItem(ipv6_entry, "address")); - // We compare only the first part of the address as the second part may be an interface specifier (%veth...) - if(strcmp(arr_name, iface_name) == 0 && strncmp(arr_addr, host, min(strlen(arr_addr), strlen(host))) == 0) - { - // Copy details from ipv6a array to new_addr (prefix, scope, flags) - JSON_ADD_ITEM_TO_OBJECT(new_addr, "type", cJSON_Duplicate(cJSON_GetObjectItem(ipv6_entry, "type"), true)); - JSON_ADD_NUMBER_TO_OBJECT(new_addr, "prefix", cJSON_GetNumberValue(cJSON_GetObjectItem(ipv6_entry, "prefix"))); - JSON_ADD_ITEM_TO_OBJECT(new_addr, "scope", cJSON_Duplicate(cJSON_GetObjectItem(ipv6_entry, "scope"), true)); - JSON_ADD_ITEM_TO_OBJECT(new_addr, "flags", cJSON_Duplicate(cJSON_GetObjectItem(ipv6_entry, "flags"), true)); - break; - } - } - - // Add IPv6 address to array - JSON_ADD_ITEM_TO_ARRAY(ipv6, new_addr); - } - - // Format flags into human-readable array of strings - cJSON *flag_array = cJSON_CreateArray(); - for(size_t i = 0; i < sizeof(iff_flags) / sizeof(iff_flags[0]); i++) - if(ifa->ifa_flags & iff_flags[i].flag) - cJSON_AddItemToArray(flag_array, cJSON_CreateStringReference(iff_flags[i].name)); - JSON_ADD_ITEM_TO_OBJECT(iface, "flags", flag_array); - } - 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_Delete(ipv6a); - ipv6a = NULL; - - 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/syscalls/CMakeLists.txt b/src/syscalls/CMakeLists.txt index 7ba43aa4a..951b1bbe5 100644 --- a/src/syscalls/CMakeLists.txt +++ b/src/syscalls/CMakeLists.txt @@ -13,6 +13,9 @@ set(sources asprintf.c calloc.c ftlallocate.c + netlink_consts.h + netlink.c + netlink.h fopen.c fprintf.c free.c diff --git a/src/syscalls/netlink.c b/src/syscalls/netlink.c new file mode 100644 index 000000000..e5597a1ac --- /dev/null +++ b/src/syscalls/netlink.c @@ -0,0 +1,796 @@ +/* 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 + +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 = { sa, sizeof(*sa), &iov, 1, NULL, 0, 0 }; + + // 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, "family", rt->rtm_family); + cJSON_AddNumberToObject(route, "table", rt->rtm_table); + + // 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_TABLE: // routing table id + 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_PRIORITY: // royute priority + const uint32_t prio = *(uint32_t*)RTA_DATA(rta); + cJSON_AddNumberToObject(route, rtaTypeToString(rta->rta_type), prio); + break; + + case RTA_PREF: // route preference + const uint32_t pref = *(uint32_t*)RTA_DATA(rta); + cJSON_AddNumberToObject(route, rtaTypeToString(rta->rta_type), pref); + 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); + cJSON_AddNumberToObject(route, "cstamp", ci->rta_clntref); + cJSON_AddNumberToObject(route, "tstamp", 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); + 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); + cJSON_AddNumberToObject(addr, "cstamp", 0.01*ci->cstamp); // created timestamp + cJSON_AddNumberToObject(addr, "tstamp", 0.01*ci->tstamp); // updated timestamp + break; + + case IFA_FLAGS: + cJSON *iflags = 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(iflags, ifaf_flags[i].name); + cJSON_AddItemToObject(addr, "flags", iflags); + 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; + 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: + // Only add it != ifname + if(strcmp(ifname, (char*)RTA_DATA(rta)) != 0) + cJSON_AddStringToObject(link, iflaTypeToString(rta->rta_type), (char*)RTA_DATA(rta)); + break; + 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; + cJSON_AddStringToObject(link, iflaTypeToString(rta->rta_type), (char*)RTA_DATA(rta)); + 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: + if(!detailed) + break; + const uint32_t number = *(uint32_t*)RTA_DATA(rta); + cJSON_AddNumberToObject(link, iflaTypeToString(rta->rta_type), number); + break; + + case IFLA_STATS: + if(!detailed) + break; + struct rtnl_link_stats *stats = (struct rtnl_link_stats*)RTA_DATA(rta); + cJSON_AddNumberToObject(link, "rx_packets", stats->rx_packets); + cJSON_AddNumberToObject(link, "tx_packets", stats->tx_packets); + cJSON_AddNumberToObject(link, "rx_bytes", stats->rx_bytes); + cJSON_AddNumberToObject(link, "tx_bytes", stats->tx_bytes); + cJSON_AddNumberToObject(link, "rx_errors", stats->rx_errors); + cJSON_AddNumberToObject(link, "tx_errors", stats->tx_errors); + cJSON_AddNumberToObject(link, "rx_dropped", stats->rx_dropped); + cJSON_AddNumberToObject(link, "tx_dropped", stats->tx_dropped); + cJSON_AddNumberToObject(link, "multicast", stats->multicast); + cJSON_AddNumberToObject(link, "collisions", stats->collisions); + cJSON_AddNumberToObject(link, "rx_length_errors", stats->rx_length_errors); + cJSON_AddNumberToObject(link, "rx_over_errors", stats->rx_over_errors); + cJSON_AddNumberToObject(link, "rx_crc_errors", stats->rx_crc_errors); + cJSON_AddNumberToObject(link, "rx_frame_errors", stats->rx_frame_errors); + cJSON_AddNumberToObject(link, "rx_fifo_errors", stats->rx_fifo_errors); + cJSON_AddNumberToObject(link, "rx_missed_errors", stats->rx_missed_errors); + cJSON_AddNumberToObject(link, "tx_aborted_errors", stats->tx_aborted_errors); + cJSON_AddNumberToObject(link, "tx_carrier_errors", stats->tx_carrier_errors); + cJSON_AddNumberToObject(link, "tx_fifo_errors", stats->tx_fifo_errors); + cJSON_AddNumberToObject(link, "tx_heartbeat_errors", stats->tx_heartbeat_errors); + cJSON_AddNumberToObject(link, "tx_window_errors", stats->tx_window_errors); + cJSON_AddNumberToObject(link, "rx_compressed", stats->rx_compressed); + cJSON_AddNumberToObject(link, "tx_compressed", stats->tx_compressed); + break; + + case IFLA_STATS64: + if(!detailed) + break; + struct rtnl_link_stats64 *stats64 = (struct rtnl_link_stats64*)RTA_DATA(rta); + cJSON_AddNumberToObject(link, "rx_packets", stats64->rx_packets); + cJSON_AddNumberToObject(link, "tx_packets", stats64->tx_packets); + cJSON_AddNumberToObject(link, "rx_bytes", stats64->rx_bytes); + cJSON_AddNumberToObject(link, "tx_bytes", stats64->tx_bytes); + cJSON_AddNumberToObject(link, "rx_errors", stats64->rx_errors); + cJSON_AddNumberToObject(link, "tx_errors", stats64->tx_errors); + cJSON_AddNumberToObject(link, "rx_dropped", stats64->rx_dropped); + cJSON_AddNumberToObject(link, "tx_dropped", stats64->tx_dropped); + cJSON_AddNumberToObject(link, "multicast", stats64->multicast); + cJSON_AddNumberToObject(link, "collisions", stats64->collisions); + cJSON_AddNumberToObject(link, "rx_length_errors", stats64->rx_length_errors); + cJSON_AddNumberToObject(link, "rx_over_errors", stats64->rx_over_errors); + cJSON_AddNumberToObject(link, "rx_crc_errors", stats64->rx_crc_errors); + cJSON_AddNumberToObject(link, "rx_frame_errors", stats64->rx_frame_errors); + cJSON_AddNumberToObject(link, "rx_fifo_errors", stats64->rx_fifo_errors); + cJSON_AddNumberToObject(link, "rx_missed_errors", stats64->rx_missed_errors); + cJSON_AddNumberToObject(link, "tx_aborted_errors", stats64->tx_aborted_errors); + cJSON_AddNumberToObject(link, "tx_carrier_errors", stats64->tx_carrier_errors); + cJSON_AddNumberToObject(link, "tx_fifo_errors", stats64->tx_fifo_errors); + cJSON_AddNumberToObject(link, "tx_heartbeat_errors", stats64->tx_heartbeat_errors); + cJSON_AddNumberToObject(link, "tx_window_errors", stats64->tx_window_errors); + cJSON_AddNumberToObject(link, "rx_compressed", stats64->rx_compressed); + cJSON_AddNumberToObject(link, "tx_compressed", stats64->tx_compressed); + 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; + 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]) + { + 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]) + { + 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; + + 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 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/syscalls/netlink.h b/src/syscalls/netlink.h new file mode 100644 index 000000000..1d94f2410 --- /dev/null +++ b/src/syscalls/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/syscalls/netlink_consts.h b/src/syscalls/netlink_consts.h new file mode 100644 index 000000000..867e32908 --- /dev/null +++ b/src/syscalls/netlink_consts.h @@ -0,0 +1,598 @@ +/* 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"; + case PF_MCTP: + return "mctp"; + 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 1ad9b09dd..2644e075c 100644 --- a/src/webserver/json_macros.h +++ b/src/webserver/json_macros.h @@ -260,3 +260,12 @@ cJSON *elem = cJSON_GetObjectItemCaseSensitive(obj, key); \ elem != NULL ? cJSON_IsTrue(elem) : false; \ }) + +#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)) From 35166fd00c9018d3d2d9a31c2bf28a5b4bf59083 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 13 Jul 2024 08:23:43 +0200 Subject: [PATCH 3/7] Fix a small bug in the API response verifier and ensure we always favor using 64 bit interface statistics if available. The reason is that the legacy statistics use 32 bit conters which overflow every 4 GB of interface traffic Signed-off-by: DL6ER --- .devcontainer/devcontainer.json | 4 +- .github/.codespellignore | 2 + src/api/docs/content/specs/network.yaml | 68 ++++- src/api/network.c | 13 +- src/api/stats.c | 21 +- src/syscalls/netlink.c | 379 ++++++++++++++++++++---- src/syscalls/netlink_consts.h | 3 + test/api/libs/responseVerifyer.py | 1 - 8 files changed, 398 insertions(+), 93 deletions(-) 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/docs/content/specs/network.yaml b/src/api/docs/content/specs/network.yaml index 60a68f226..904fb0639 100644 --- a/src/api/docs/content/specs/network.yaml +++ b/src/api/docs/content/specs/network.yaml @@ -190,8 +190,8 @@ components: enum: [ "inet", "inet6", "link", "mpls", "bridge", "???" ] description: Address family table: - type: main - description: Routing table ID (0 = unspecified, 253 = default, 254 = local, 255 = local) + type: integer + description: Routing table ID (0 = unspecified, 253 = default, 254 = local, 255 = local, other = user-defined) protocol: type: string description: Routing protocol @@ -222,8 +222,11 @@ components: type: string description: Preferred source address priority: - type: string + type: integer description: Route priority + pref: + type: integer + description: Route preference example: - family: "inet" @@ -258,7 +261,7 @@ components: type: "local" flags: [] dst: "::1" - priority: "medium" + priority: 0 oif: "eth0" - family: "inet6" table: 254 @@ -275,7 +278,7 @@ components: type: "multicast" flags: [] dst: "fd00:4711::" - priority: "unknown" + priority: 5 oif: "wg0" interfaces: @@ -294,6 +297,9 @@ components: type: integer nullable: true 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 @@ -311,12 +317,35 @@ components: address: type: string description: Interface hardware address - broadcase: + 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 @@ -327,9 +356,15 @@ components: address: type: string description: Interface address + broadcast: + type: string + description: Interface broadcast address local: type: string description: Local address + label: + type: string + description: Interface label family: type: string enum: [ "inet", "inet6", "link", "mpls", "bridge", "???" ] @@ -366,6 +401,13 @@ components: 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" local: "127.0.0.1" @@ -398,6 +440,13 @@ components: 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" local: "192.168.0.123" @@ -446,6 +495,13 @@ components: flags: [ "up", "pointopoint", "running", "noarp", "lower_up" ] state: "unknown" carrier: true + stats: + rx_bytes: + value: 458.44598 + unit: "MB" + tx_bytes: + value: 5.5895 + unit: "MB" addresses: - address: "10.1.0.1" local: "10.1.0.1" diff --git a/src/api/network.c b/src/api/network.c index 1deeeeaab..dce732929 100644 --- a/src/api/network.c +++ b/src/api/network.c @@ -38,21 +38,23 @@ int api_network_gateway(struct ftl_conn *api) // Add routing information cJSON *routes = JSON_NEW_ARRAY(); nlroutes(routes, false); - cJSON *gateway = JSON_NEW_ARRAY(); + 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) { cJSON *dst = cJSON_GetObjectItem(route, "dst"); - if(dst != NULL && cJSON_IsString(dst) && strcmp(cJSON_GetStringValue(dst), "default") == 0) + if(dst != NULL && + cJSON_IsString(dst) && + strcmp(cJSON_GetStringValue(dst), "default") == 0) { cJSON *gwobj = JSON_NEW_OBJECT(); // Extract and add family - const int family = cJSON_GetNumberValue(cJSON_GetObjectItem(route, "family")); - JSON_ADD_NUMBER_TO_OBJECT(gwobj, "family", 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")); @@ -65,8 +67,11 @@ int api_network_gateway(struct ftl_conn *api) cJSON_AddItemToArray(gateway, gwobj); } } + + // Free routes array cJSON_Delete(routes); + // Send gateway information cJSON *json = JSON_NEW_OBJECT(); JSON_ADD_ITEM_TO_OBJECT(json, "gateway", gateway); JSON_SEND_OBJECT(json); diff --git a/src/api/stats.c b/src/api/stats.c index b272e768d..1569fb6bf 100644 --- a/src/api/stats.c +++ b/src/api/stats.c @@ -196,23 +196,6 @@ int api_stats_top_domains(struct ftl_conn *api) // Sort temporary array qsort(temparray, added_domains, sizeof(int[2]), cmpdesc); - // Get filter - const char* log_show = read_setupVarsconf("API_QUERY_LOG_SHOW"); - bool showpermitted = true, showblocked = true; - if(log_show != NULL) - { - if((strcmp(log_show, "permittedonly")) == 0) - showblocked = false; - else if((strcmp(log_show, "blockedonly")) == 0) - showpermitted = false; - else if((strcmp(log_show, "nothing")) == 0) - { - showpermitted = false; - showblocked = false; - } - } - clearSetupVarsArray(); - // Get domains which the user doesn't want to see regex_t *regex_domains = NULL; unsigned int N_regex_domains = 0; @@ -259,12 +242,12 @@ int api_stats_top_domains(struct ftl_conn *api) continue; int domain_count = -1; - if(blocked && showblocked && domain->blockedcount > 0) + if(blocked && domain->blockedcount > 0) { domain_count = domain->blockedcount; n++; } - else if(!blocked && showpermitted && (domain->count - domain->blockedcount) > 0) + else if(!blocked && (domain->count - domain->blockedcount) > 0) { domain_count = domain->count - domain->blockedcount; n++; diff --git a/src/syscalls/netlink.c b/src/syscalls/netlink.c index e5597a1ac..d8e91c382 100644 --- a/src/syscalls/netlink.c +++ b/src/syscalls/netlink.c @@ -56,7 +56,11 @@ static bool nlrequest(int fd, struct sockaddr_nl *sa, int nlmsg_type) // Prepare struct msghdr for sending struct iovec iov = { nl, nl->nlmsg_len }; - struct msghdr msg = { sa, sizeof(*sa), &iov, 1, NULL, 0, 0 }; + 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; @@ -82,8 +86,8 @@ static int nlparsemsg_route(struct rtmsg *rt, void *buf, size_t len, cJSON *rout { char ifname[IF_NAMESIZE]; cJSON *route = cJSON_CreateObject(); - cJSON_AddNumberToObject(route, "family", rt->rtm_family); 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++) @@ -149,10 +153,12 @@ static int nlparsemsg_route(struct rtmsg *rt, void *buf, size_t len, cJSON *rout 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 @@ -165,23 +171,24 @@ static int nlparsemsg_route(struct rtmsg *rt, void *buf, size_t len, cJSON *rout 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_PRIORITY: // royute priority - const uint32_t prio = *(uint32_t*)RTA_DATA(rta); - cJSON_AddNumberToObject(route, rtaTypeToString(rta->rta_type), prio); - break; - + case RTA_PRIORITY: // route priority case RTA_PREF: // route preference - const uint32_t pref = *(uint32_t*)RTA_DATA(rta); - cJSON_AddNumberToObject(route, rtaTypeToString(rta->rta_type), pref); + { + 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); @@ -202,14 +209,18 @@ static int nlparsemsg_route(struct rtmsg *rt, void *buf, size_t len, cJSON *rout 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); @@ -217,8 +228,10 @@ static int nlparsemsg_route(struct rtmsg *rt, void *buf, size_t len, cJSON *rout 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); @@ -228,8 +241,10 @@ static int nlparsemsg_route(struct rtmsg *rt, void *buf, size_t len, cJSON *rout 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 @@ -244,6 +259,7 @@ static int nlparsemsg_route(struct rtmsg *rt, void *buf, size_t len, cJSON *rout } cJSON_AddNumberToArray(unknown, rta->rta_type); break; + } } } @@ -297,10 +313,12 @@ static int nlparsemsg_address(struct ifaddrmsg *ifa, void *buf, size_t len, cJSO 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); break; + } case IFA_LABEL: strncpy(ifname, (char*)RTA_DATA(rta), IF_NAMESIZE); @@ -308,36 +326,45 @@ static int nlparsemsg_address(struct ifaddrmsg *ifa, void *buf, size_t len, cJSO 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); cJSON_AddNumberToObject(addr, "cstamp", 0.01*ci->cstamp); // created timestamp cJSON_AddNumberToObject(addr, "tstamp", 0.01*ci->tstamp); // updated timestamp break; + } case IFA_FLAGS: + { cJSON *iflags = 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(iflags, ifaf_flags[i].name); cJSON_AddItemToObject(addr, "flags", iflags); 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 @@ -352,6 +379,7 @@ static int nlparsemsg_address(struct ifaddrmsg *ifa, void *buf, size_t len, cJSO } cJSON_AddNumberToArray(unknown, rta->rta_type); break; + } } } @@ -429,12 +457,14 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * // 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", @@ -443,27 +473,29 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * // Addresses may be empty, so only add them if they are not cJSON_AddStringToObject(link, iflaTypeToString(rta->rta_type), mac); break; + } case IFLA_IFNAME: - // Only add it != ifname - if(strcmp(ifname, (char*)RTA_DATA(rta)) != 0) - cJSON_AddStringToObject(link, iflaTypeToString(rta->rta_type), (char*)RTA_DATA(rta)); - break; 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; - cJSON_AddStringToObject(link, iflaTypeToString(rta->rta_type), (char*)RTA_DATA(rta)); + 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++) @@ -500,71 +532,267 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * case IFLA_NEW_NETNSID: case IFLA_MIN_MTU: case IFLA_MAX_MTU: + { 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; - struct rtnl_link_stats *stats = (struct rtnl_link_stats*)RTA_DATA(rta); - cJSON_AddNumberToObject(link, "rx_packets", stats->rx_packets); - cJSON_AddNumberToObject(link, "tx_packets", stats->tx_packets); - cJSON_AddNumberToObject(link, "rx_bytes", stats->rx_bytes); - cJSON_AddNumberToObject(link, "tx_bytes", stats->tx_bytes); - cJSON_AddNumberToObject(link, "rx_errors", stats->rx_errors); - cJSON_AddNumberToObject(link, "tx_errors", stats->tx_errors); - cJSON_AddNumberToObject(link, "rx_dropped", stats->rx_dropped); - cJSON_AddNumberToObject(link, "tx_dropped", stats->tx_dropped); - cJSON_AddNumberToObject(link, "multicast", stats->multicast); - cJSON_AddNumberToObject(link, "collisions", stats->collisions); - cJSON_AddNumberToObject(link, "rx_length_errors", stats->rx_length_errors); - cJSON_AddNumberToObject(link, "rx_over_errors", stats->rx_over_errors); - cJSON_AddNumberToObject(link, "rx_crc_errors", stats->rx_crc_errors); - cJSON_AddNumberToObject(link, "rx_frame_errors", stats->rx_frame_errors); - cJSON_AddNumberToObject(link, "rx_fifo_errors", stats->rx_fifo_errors); - cJSON_AddNumberToObject(link, "rx_missed_errors", stats->rx_missed_errors); - cJSON_AddNumberToObject(link, "tx_aborted_errors", stats->tx_aborted_errors); - cJSON_AddNumberToObject(link, "tx_carrier_errors", stats->tx_carrier_errors); - cJSON_AddNumberToObject(link, "tx_fifo_errors", stats->tx_fifo_errors); - cJSON_AddNumberToObject(link, "tx_heartbeat_errors", stats->tx_heartbeat_errors); - cJSON_AddNumberToObject(link, "tx_window_errors", stats->tx_window_errors); - cJSON_AddNumberToObject(link, "rx_compressed", stats->rx_compressed); - cJSON_AddNumberToObject(link, "tx_compressed", stats->tx_compressed); + 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; - struct rtnl_link_stats64 *stats64 = (struct rtnl_link_stats64*)RTA_DATA(rta); - cJSON_AddNumberToObject(link, "rx_packets", stats64->rx_packets); - cJSON_AddNumberToObject(link, "tx_packets", stats64->tx_packets); - cJSON_AddNumberToObject(link, "rx_bytes", stats64->rx_bytes); - cJSON_AddNumberToObject(link, "tx_bytes", stats64->tx_bytes); - cJSON_AddNumberToObject(link, "rx_errors", stats64->rx_errors); - cJSON_AddNumberToObject(link, "tx_errors", stats64->tx_errors); - cJSON_AddNumberToObject(link, "rx_dropped", stats64->rx_dropped); - cJSON_AddNumberToObject(link, "tx_dropped", stats64->tx_dropped); - cJSON_AddNumberToObject(link, "multicast", stats64->multicast); - cJSON_AddNumberToObject(link, "collisions", stats64->collisions); - cJSON_AddNumberToObject(link, "rx_length_errors", stats64->rx_length_errors); - cJSON_AddNumberToObject(link, "rx_over_errors", stats64->rx_over_errors); - cJSON_AddNumberToObject(link, "rx_crc_errors", stats64->rx_crc_errors); - cJSON_AddNumberToObject(link, "rx_frame_errors", stats64->rx_frame_errors); - cJSON_AddNumberToObject(link, "rx_fifo_errors", stats64->rx_fifo_errors); - cJSON_AddNumberToObject(link, "rx_missed_errors", stats64->rx_missed_errors); - cJSON_AddNumberToObject(link, "tx_aborted_errors", stats64->tx_aborted_errors); - cJSON_AddNumberToObject(link, "tx_carrier_errors", stats64->tx_carrier_errors); - cJSON_AddNumberToObject(link, "tx_fifo_errors", stats64->tx_fifo_errors); - cJSON_AddNumberToObject(link, "tx_heartbeat_errors", stats64->tx_heartbeat_errors); - cJSON_AddNumberToObject(link, "tx_window_errors", stats64->tx_window_errors); - cJSON_AddNumberToObject(link, "rx_compressed", stats64->rx_compressed); - cJSON_AddNumberToObject(link, "tx_compressed", stats64->tx_compressed); + // @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; @@ -577,6 +805,7 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * cJSON_AddStringToObject(link, "link_kind", (char*)RTA_DATA(nlinkinfo)); break; default: + { // Unknown rta_type cJSON *unknown = cJSON_GetObjectItem(link, "linkinfo_unknown"); if(unknown == NULL) @@ -586,11 +815,14 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * } cJSON_AddNumberToArray(unknown, nlinkinfo->rta_type); break; + } } } break; + } case IFLA_VFINFO_LIST: + { if(!detailed) break; struct rtattr *vfinfo = RTA_DATA(rta); @@ -610,6 +842,7 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * 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], @@ -618,6 +851,7 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * } 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]); @@ -634,8 +868,10 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * } break; + } case IFLA_EVENT: + { if(!detailed) break; const uint32_t event = *(uint32_t*)RTA_DATA(rta); @@ -648,8 +884,10 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * 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); @@ -683,8 +921,10 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * } cJSON_AddItemToObject(link, "af_specs", af_specs); break; + } default: + { // Unknown rta_type // Add the rta_type as a number to an array of // unknown types if in detailed mode @@ -699,8 +939,23 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * } 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); diff --git a/src/syscalls/netlink_consts.h b/src/syscalls/netlink_consts.h index 867e32908..f6a5e95f2 100644 --- a/src/syscalls/netlink_consts.h +++ b/src/syscalls/netlink_consts.h @@ -558,8 +558,11 @@ static const char *__attribute__ ((const)) family_name(int family) 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"; } 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]] From c284d6447e9288c5ad2327758f1634e1dd88ac82 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 14 Jul 2024 15:51:33 +0200 Subject: [PATCH 4/7] Move netlink under tools/ Signed-off-by: DL6ER --- src/api/network.c | 2 +- src/syscalls/CMakeLists.txt | 3 --- src/tools/CMakeLists.txt | 3 +++ src/{syscalls => tools}/netlink.c | 4 ++-- src/{syscalls => tools}/netlink.h | 0 src/{syscalls => tools}/netlink_consts.h | 0 6 files changed, 6 insertions(+), 6 deletions(-) rename src/{syscalls => tools}/netlink.c (99%) rename src/{syscalls => tools}/netlink.h (100%) rename src/{syscalls => tools}/netlink_consts.h (100%) diff --git a/src/api/network.c b/src/api/network.c index dce732929..31df4584d 100644 --- a/src/api/network.c +++ b/src/api/network.c @@ -30,7 +30,7 @@ // IFA_LINK and friends #include // nlroutes(), nladdrs(), nllinks() -#include "syscalls/netlink.h" +#include "tools/netlink.h" int api_network_gateway(struct ftl_conn *api) { diff --git a/src/syscalls/CMakeLists.txt b/src/syscalls/CMakeLists.txt index 951b1bbe5..7ba43aa4a 100644 --- a/src/syscalls/CMakeLists.txt +++ b/src/syscalls/CMakeLists.txt @@ -13,9 +13,6 @@ set(sources asprintf.c calloc.c ftlallocate.c - netlink_consts.h - netlink.c - netlink.h fopen.c fprintf.c free.c 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/syscalls/netlink.c b/src/tools/netlink.c similarity index 99% rename from src/syscalls/netlink.c rename to src/tools/netlink.c index d8e91c382..02d2cafec 100644 --- a/src/syscalls/netlink.c +++ b/src/tools/netlink.c @@ -685,8 +685,8 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * // 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`. + // 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 diff --git a/src/syscalls/netlink.h b/src/tools/netlink.h similarity index 100% rename from src/syscalls/netlink.h rename to src/tools/netlink.h diff --git a/src/syscalls/netlink_consts.h b/src/tools/netlink_consts.h similarity index 100% rename from src/syscalls/netlink_consts.h rename to src/tools/netlink_consts.h From c342c60c71d89d179975f4fd63e30aa52c5cc15f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 14 Jul 2024 15:54:19 +0200 Subject: [PATCH 5/7] fail*.dnssec.works changed its configuration from being BOGUS to ABANDONNED and cannot be used any longer for BOGUS testing Signed-off-by: DL6ER --- test/test_suite.bats | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/test_suite.bats b/test/test_suite.bats index d747e8dd6..ed73f5576 100644 --- a/test/test_suite.bats +++ b/test/test_suite.bats @@ -401,12 +401,6 @@ [[ ${lines[@]} == *"status: NOERROR"* ]] } -@test "DNSSEC: BOGUS domain is rejected" { - run bash -c "dig A fail01.dnssec.works @127.0.0.1" - printf "%s\n" "${lines[@]}" - [[ ${lines[@]} == *"status: SERVFAIL"* ]] -} - @test "Special domain: NXDOMAIN is returned" { run bash -c "dig A mask.icloud.com @127.0.0.1" printf "%s\n" "${lines[@]}" From 7c42a785cc6b99f89dc5545c391b98469419f413 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 14 Jul 2024 22:44:25 +0200 Subject: [PATCH 6/7] Report cstamp/tstamp in wall_clock time Signed-off-by: DL6ER --- src/api/docs/content/specs/network.yaml | 31 ++++++++++---- src/api/network.c | 57 ++++++++++++++++++++++--- src/tools/netlink.c | 20 +++++++-- 3 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/api/docs/content/specs/network.yaml b/src/api/docs/content/specs/network.yaml index 904fb0639..62f0b66fc 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 @@ -166,13 +170,22 @@ components: 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.2" + address: "192.168.0.1" + local: + - "192.168.0.22" - family: "inet6" interface: "eth0" - address: "fe80::3587:2fff:f11a:4321" + address: "fe80::3587:2fff:f11a:1" + local: + - "fe80::3587:2fff:f11a:4321" routes: type: object properties: @@ -388,10 +401,10 @@ components: description: Valid lifetime of the address (`4294967295` = forever) cstamp: type: number - description: Creation timestamp of the address (relative to the system uptime) + description: Creation timestamp of the address tstamp: type: number - description: Updated timestamp of the address (relative to the system uptime) + description: Updated timestamp of the address example: - name: "lo" speed: null @@ -418,8 +431,8 @@ components: label: "lo" prefered: 4294967295 valid: 4294967295 - cstamp: 6.1 - tstamp: 6.1 + cstamp: 1720989931 + tstamp: 1720989931 - address: "::1" local: "::1" family: "inet6" @@ -429,8 +442,8 @@ components: label: "lo" prefered: 4294967295 valid: 4294967295 - cstamp: 6.1 - tstamp: 6.1 + cstamp: 1720989931.1 + tstamp: 1720989931.1 - name: "eth0" speed: 1000 type: "ether" @@ -613,7 +626,7 @@ components: example: 1 detailed: in: query - description: (Optional) Detailed interface information + description: (Optional) Detailed interface/routing information name: detailed schema: type: boolean diff --git a/src/api/network.c b/src/api/network.c index 31df4584d..6ff36a62e 100644 --- a/src/api/network.c +++ b/src/api/network.c @@ -34,10 +34,19 @@ int api_network_gateway(struct ftl_conn *api) { + // Get ?detailed parameter + bool detailed = false; + get_bool_var(api->request->query_string, "detailed", &detailed); - // Add routing information + // Get routing information cJSON *routes = JSON_NEW_ARRAY(); - nlroutes(routes, false); + nlroutes(routes, detailed); + + // 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 @@ -64,16 +73,54 @@ int api_network_gateway(struct ftl_conn *api) 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); } } - // Free routes array - cJSON_Delete(routes); - // 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 + { + // Free arrays + cJSON_Delete(routes); + cJSON_Delete(interfaces); + } + JSON_SEND_OBJECT(json); } diff --git a/src/tools/netlink.c b/src/tools/netlink.c index 02d2cafec..578139845 100644 --- a/src/tools/netlink.c +++ b/src/tools/netlink.c @@ -235,8 +235,14 @@ static int nlparsemsg_route(struct rtmsg *rt, void *buf, size_t len, cJSON *rout if(!detailed) break; struct rta_cacheinfo *ci = (struct rta_cacheinfo*)RTA_DATA(rta); - cJSON_AddNumberToObject(route, "cstamp", ci->rta_clntref); - cJSON_AddNumberToObject(route, "tstamp", ci->rta_lastuse); + // 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); @@ -330,8 +336,14 @@ static int nlparsemsg_address(struct ifaddrmsg *ifa, void *buf, size_t len, cJSO struct ifa_cacheinfo *ci = (struct ifa_cacheinfo*)RTA_DATA(rta); cJSON_AddNumberToObject(addr, "prefered", ci->ifa_prefered); cJSON_AddNumberToObject(addr, "valid", ci->ifa_valid); - cJSON_AddNumberToObject(addr, "cstamp", 0.01*ci->cstamp); // created timestamp - cJSON_AddNumberToObject(addr, "tstamp", 0.01*ci->tstamp); // updated timestamp + // 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; } From 66b7fdf1cc729ef2e0090a1bdcbab7fd37824729 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Mon, 15 Jul 2024 07:23:57 +0200 Subject: [PATCH 7/7] Add link-netnsid and address types Signed-off-by: DL6ER --- src/api/docs/content/specs/network.yaml | 42 +++++++--- src/dnsmasq_interface.c | 2 +- src/tools/netlink.c | 105 ++++++++++++++++++++++-- 3 files changed, 130 insertions(+), 19 deletions(-) diff --git a/src/api/docs/content/specs/network.yaml b/src/api/docs/content/specs/network.yaml index 62f0b66fc..079e8238c 100644 --- a/src/api/docs/content/specs/network.yaml +++ b/src/api/docs/content/specs/network.yaml @@ -369,12 +369,21 @@ components: 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 @@ -423,7 +432,9 @@ components: 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" ] @@ -434,7 +445,9 @@ components: cstamp: 1720989931 tstamp: 1720989931 - address: "::1" + address_type: "loopback" local: "::1" + local_type: "loopback" family: "inet6" scope: "host" flags: [ "permanent" ] @@ -462,7 +475,9 @@ components: 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" ] @@ -470,9 +485,10 @@ components: label: "eth0" prefered: 4294967295 valid: 4294967295 - cstamp: 11.23 - tstamp: 11.23 + cstamp: 1720989931.1 + tstamp: 1720989931.1 - address: "2001:db8::1234:5678:9abc:def0" + address_type: "global (GUA)" family: "inet6" scope: "universe" flags: [] @@ -483,6 +499,7 @@ components: cstamp: 2789057.25 tstamp: 2789057.25 - address: "fd29:db8::1234:5678:9abc:def0" + address_type: "site-local (ULA)" family: "inet6" scope: "universe" flags: [] @@ -490,9 +507,10 @@ components: label: "eth0" prefered: 3461 valid: 7061 - cstamp: 12.5 - tstamp: 2827298.75 + cstamp: 1720989931.1 + tstamp: 1720989931.1 - address: "fe80::1234:5678:9abc:def0" + address_type: "link-local (LL)" family: "inet6" scope: "link" flags: [ "permanent" ] @@ -500,8 +518,8 @@ components: label: "eth0" prefered: 4294967295 valid: 4294967295 - cstamp: 11.23 - tstamp: 11.23 + cstamp: 1720989931.1 + tstamp: 1720989931.1 - name: "wg0" speed: null type: "none" @@ -517,16 +535,20 @@ components: 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: 11.23 - tstamp: 11.23 + cstamp: 1720989931.1 + tstamp: 1720989931.1 - address: "fd00:4711::1" + address_type: "site-local (ULA)" family: "inet6" scope: "global" flags: [ "permanent" ] @@ -534,8 +556,8 @@ components: label: "wg0" prefered: 4294967295 valid: 4294967295 - cstamp: 11.23 - tstamp: 11.23 + cstamp: 1720989931.1 + tstamp: 1720989931.1 devices: type: object properties: diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 72bb41cee..200e37e93 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/netlink.c b/src/tools/netlink.c index 578139845..1de4d67ef 100644 --- a/src/tools/netlink.c +++ b/src/tools/netlink.c @@ -17,6 +17,9 @@ #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 }; @@ -162,7 +165,6 @@ static int nlparsemsg_route(struct rtmsg *rt, void *buf, size_t len, cJSON *rout case RTA_FLOW: // route realm case RTA_METRICS: // route metric - case RTA_TABLE: // routing table id case RTA_MARK: // route mark case RTA_EXPIRES: // route expires (in seconds) case RTA_UID: // user id @@ -179,6 +181,10 @@ static int nlparsemsg_route(struct rtmsg *rt, void *buf, size_t len, cJSON *rout break; } + case RTA_TABLE: // routing table id + // Already added above + break; + case RTA_PRIORITY: // route priority case RTA_PREF: // route preference { @@ -323,6 +329,79 @@ static int nlparsemsg_address(struct ifaddrmsg *ifa, void *buf, size_t len, cJSO 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; } @@ -348,14 +427,8 @@ static int nlparsemsg_address(struct ifaddrmsg *ifa, void *buf, size_t len, cJSO } case IFA_FLAGS: - { - cJSON *iflags = 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(iflags, ifaf_flags[i].name); - cJSON_AddItemToObject(addr, "flags", iflags); + // Already added above, ignore this duplicate break; - } case IFA_RT_PRIORITY: { @@ -544,6 +617,7 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * case IFLA_NEW_NETNSID: case IFLA_MIN_MTU: case IFLA_MAX_MTU: + case IFLA_LINK_NETNSID: { if(!detailed) break; @@ -816,6 +890,15 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * 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 @@ -935,6 +1018,12 @@ static int nlparsemsg_link(struct ifinfomsg *ifi, void *buf, size_t len, cJSON * break; } + case IFLA_XDP: + // Parsing XDP needs a full BPF program + // disassembler which is clearly out of scope + // here + break; + default: { // Unknown rta_type