--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "ansi-color.h"
+#include "arphrd-util.h"
+#include "dhcp-message-dump.h"
+#include "dhcp-protocol.h"
+#include "dhcp-route.h"
+#include "dns-resolver-internal.h"
+#include "escape.h"
+#include "ether-addr-util.h"
+#include "format-table.h"
+#include "hashmap.h"
+#include "hexdecoct.h"
+#include "iovec-util.h"
+#include "iovec-wrapper.h"
+#include "parse-util.h"
+#include "stdio-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+
+static int iovec_to_hex(const struct iovec *iov, char **ret) {
+ assert(iov);
+ assert(ret);
+
+ _cleanup_free_ char *str = new(char, iov->iov_len * 3);
+ if (!str)
+ return log_oom();
+
+ char *p = str;
+ FOREACH_ARRAY(v, ((uint8_t*) iov->iov_base), iov->iov_len) {
+ if (p != str)
+ *p++ = ':';
+ *p++ = hexchar(*v >> 4);
+ *p++ = hexchar(*v & 0x0f);
+ }
+ *p = '\0';
+
+ *ret = TAKE_PTR(str);
+ return 0;
+}
+
+static int iovw_to_strv(const struct iovec_wrapper *iovw, char ***ret) {
+ int r;
+
+ assert(iovw);
+ assert(ret);
+
+ _cleanup_strv_free_ char **strv = NULL;
+ size_t n_strv = 0;
+ FOREACH_ARRAY(iov, iovw->iovec, iovw->count) {
+ _cleanup_free_ char *escaped = cescape_length(iov->iov_base, iov->iov_len);
+ if (!escaped)
+ return log_oom();
+
+ r = strv_consume_with_size(&strv, &n_strv, TAKE_PTR(escaped));
+ if (r < 0)
+ return log_oom();
+ }
+
+ *ret = TAKE_PTR(strv);
+ return 0;
+}
+
+static void table_apply_flags(Table *table, DumpDHCPMessageFlag flags) {
+ assert(table);
+
+ table_set_header(table, FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND));
+ if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_FULL))
+ table_set_width(table, 0);
+}
+
+static int dump_dhcp_option_vendor_specific_information(sd_dhcp_message *m, DumpDHCPMessageFlag flags) {
+ int r;
+
+ assert(m);
+
+ _cleanup_(table_unrefp) Table *table = table_new("code", "data");
+ if (!table)
+ return log_oom();
+
+ table_apply_flags(table, flags);
+
+ (void) table_set_sort(table, (size_t) 0);
+
+ TableCell *cell = table_get_cell(table, 0, 0);
+ if (!cell)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell.");
+
+ (void) table_set_align_percent(table, cell, 100);
+ (void) table_set_ellipsize_percent(table, cell, 100);
+
+ _cleanup_(tlv_unrefp) TLV *tlv = NULL;
+ r = dhcp_message_get_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, TLV_DHCP4_SUBOPTION, &tlv);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read DHCP option %i: %m", SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION);
+
+ void *tagp;
+ struct iovec_wrapper *iovw;
+ HASHMAP_FOREACH_KEY(iovw, tagp, tlv->entries)
+ FOREACH_ARRAY(iov, iovw->iovec, iovw->count) {
+ _cleanup_free_ char *str = NULL;
+ r = iovec_to_hex(iov, &str);
+ if (r < 0)
+ return r;
+
+ r = table_add_many(
+ table,
+ TABLE_UINT32, PTR_TO_UINT32(tagp),
+ TABLE_STRING, str);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ putchar('\n');
+ if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND))
+ printf("%s%sVendor-Specific Information:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal());
+
+ return table_print_or_warn(table);
+}
+
+static int dump_dhcp_option_vendor_identifying_vendor_class(sd_dhcp_message *m, DumpDHCPMessageFlag flags) {
+ int r;
+
+ assert(m);
+
+ _cleanup_(table_unrefp) Table *table = table_new("enterprise-number", "data");
+ if (!table)
+ return log_oom();
+
+ table_apply_flags(table, flags);
+
+ (void) table_set_sort(table, (size_t) 0);
+
+ TableCell *cell = table_get_cell(table, 0, 0);
+ if (!cell)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell.");
+
+ (void) table_set_align_percent(table, cell, 100);
+ (void) table_set_ellipsize_percent(table, cell, 100);
+
+ _cleanup_(tlv_unrefp) TLV *tlv = NULL;
+ r = dhcp_message_get_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS, TLV_DHCP4_VENDOR_IDENTIFYING_OPTION, &tlv);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read DHCP option %i: %m", SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS);
+
+ void *key, *value;
+ HASHMAP_FOREACH_KEY(value, key, tlv->entries) {
+ uint32_t enterprise_number = PTR_TO_UINT32(key);
+
+ _cleanup_(iovec_done) struct iovec iov = {};
+ r = tlv_get_alloc(tlv, enterprise_number, &iov);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read vendor class of enterprise number %"PRIu32": %m", enterprise_number);
+
+ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
+ r = iovec_split(&iov, /* length_size= */ 1, &iovw);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse vendor class of enterprise number %"PRIu32": %m", enterprise_number);
+
+ _cleanup_strv_free_ char **strv = NULL;
+ r = iovw_to_strv(&iovw, &strv);
+ if (r < 0)
+ return r;
+
+ r = table_add_many(
+ table,
+ TABLE_UINT32, enterprise_number,
+ TABLE_STRV, strv);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ putchar('\n');
+ if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND))
+ printf("%s%sVendor-Identifying Vendor Class:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal());
+
+ return table_print_or_warn(table);
+}
+
+static int dump_dhcp_option_vendor_identifying_vendor_specific_information(sd_dhcp_message *m, DumpDHCPMessageFlag flags) {
+ int r;
+
+ assert(m);
+
+ _cleanup_(table_unrefp) Table *table = table_new("enterprise-number", "code", "data");
+ if (!table)
+ return log_oom();
+
+ table_apply_flags(table, flags);
+
+ (void) table_set_sort(table, (size_t) 0, (size_t) 1);
+
+ for (unsigned i = 0; i <= 1; i++) {
+ TableCell *cell = table_get_cell(table, 0, i);
+ if (!cell)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell.");
+
+ (void) table_set_align_percent(table, cell, 100);
+ (void) table_set_ellipsize_percent(table, cell, 100);
+ }
+
+ _cleanup_(tlv_unrefp) TLV *tlv = NULL;
+ r = dhcp_message_get_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION, TLV_DHCP4_VENDOR_IDENTIFYING_OPTION, &tlv);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read DHCP option %i: %m", SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION);
+
+ void *key, *value;
+ HASHMAP_FOREACH_KEY(value, key, tlv->entries) {
+ uint32_t enterprise_number = PTR_TO_UINT32(key);
+
+ _cleanup_(iovec_done) struct iovec iov = {};
+ r = tlv_get_alloc(tlv, enterprise_number, &iov);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read vendor specific information of enterprise number %"PRIu32": %m", enterprise_number);
+
+ _cleanup_(tlv_done) TLV sub_tlv = TLV_INIT(TLV_DHCP4_SUBOPTION);
+ r = tlv_parse(&sub_tlv, &iov);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse vendor specific information of enterprise number %"PRIu32": %m", enterprise_number);
+
+ void *tagp;
+ struct iovec_wrapper *iovw;
+ HASHMAP_FOREACH_KEY(iovw, tagp, sub_tlv.entries) {
+ uint32_t code = PTR_TO_UINT32(tagp);
+
+ FOREACH_ARRAY(i, iovw->iovec, iovw->count) {
+ _cleanup_free_ char *str = NULL;
+ r = iovec_to_hex(i, &str);
+ if (r < 0)
+ return r;
+
+ r = table_add_many(
+ table,
+ TABLE_UINT32, enterprise_number,
+ TABLE_UINT32, code,
+ TABLE_STRING, str);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+ }
+ }
+
+ putchar('\n');
+ if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND))
+ printf("%s%sVendor-Identifying Vendor-Specific Information:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal());
+
+ return table_print_or_warn(table);
+}
+
+typedef enum DHCPOptionType {
+ DHCP_OPTION_TYPE_AUTO,
+ DHCP_OPTION_TYPE_HEX,
+ DHCP_OPTION_TYPE_FLAG,
+ DHCP_OPTION_TYPE_BOOL,
+ DHCP_OPTION_TYPE_UINT8,
+ DHCP_OPTION_TYPE_UINT16,
+ DHCP_OPTION_TYPE_TIME,
+ DHCP_OPTION_TYPE_STRING,
+ DHCP_OPTION_TYPE_ADDRESS,
+ _DHCP_OPTION_TYPE_AUTO_MAX,
+ DHCP_OPTION_TYPE_SIP = _DHCP_OPTION_TYPE_AUTO_MAX,
+ DHCP_OPTION_TYPE_FQDN,
+ DHCP_OPTION_TYPE_ROUTE,
+ DHCP_OPTION_TYPE_LENGTH_PREFIXED_DATA,
+ DHCP_OPTION_TYPE_SEARCH_DOMAINS,
+ DHCP_OPTION_TYPE_DNR,
+ DHCP_OPTION_TYPE_6RD,
+ DHCP_OPTION_TYPE_TBD,
+ _DHCP_OPTION_TYPE_MAX,
+ _DHCP_OPTION_TYPE_INVALID = -EINVAL,
+} DHCPOptionType;
+
+static const char * const dhcp_option_type_table[_DHCP_OPTION_TYPE_AUTO_MAX] = {
+ [DHCP_OPTION_TYPE_AUTO] = "auto",
+ [DHCP_OPTION_TYPE_HEX] = "hex",
+ [DHCP_OPTION_TYPE_FLAG] = "flag",
+ [DHCP_OPTION_TYPE_BOOL] = "bool",
+ [DHCP_OPTION_TYPE_UINT8] = "uint8",
+ [DHCP_OPTION_TYPE_UINT16] = "uint16",
+ [DHCP_OPTION_TYPE_TIME] = "time",
+ [DHCP_OPTION_TYPE_STRING] = "string",
+ [DHCP_OPTION_TYPE_ADDRESS] = "address",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_option_type, DHCPOptionType);
+
+static DHCPOptionType dhcp_option_type_from_code(uint8_t code) {
+ switch (code) {
+ case SD_DHCP_OPTION_PAD:
+ return -EINVAL;
+ case SD_DHCP_OPTION_SUBNET_MASK:
+ case SD_DHCP_OPTION_ROUTER:
+ case SD_DHCP_OPTION_TIME_SERVER:
+ case SD_DHCP_OPTION_NAME_SERVER:
+ case SD_DHCP_OPTION_DOMAIN_NAME_SERVER:
+ case SD_DHCP_OPTION_LOG_SERVER:
+ case SD_DHCP_OPTION_QUOTES_SERVER:
+ case SD_DHCP_OPTION_LPR_SERVER:
+ case SD_DHCP_OPTION_IMPRESS_SERVER:
+ case SD_DHCP_OPTION_RLP_SERVER:
+ return DHCP_OPTION_TYPE_ADDRESS;
+ case SD_DHCP_OPTION_HOST_NAME:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_BOOT_FILE_SIZE:
+ return DHCP_OPTION_TYPE_UINT16;
+ case SD_DHCP_OPTION_MERIT_DUMP_FILE:
+ case SD_DHCP_OPTION_DOMAIN_NAME:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_SWAP_SERVER:
+ return DHCP_OPTION_TYPE_ADDRESS;
+ case SD_DHCP_OPTION_ROOT_PATH:
+ case SD_DHCP_OPTION_EXTENSION_FILE:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_FORWARD:
+ case SD_DHCP_OPTION_SOURCE_ROUTE:
+ return DHCP_OPTION_TYPE_BOOL;
+ case SD_DHCP_OPTION_MAX_DATAGRAM_ASSEMBLY:
+ return DHCP_OPTION_TYPE_UINT16;
+ case SD_DHCP_OPTION_DEFAULT_IP_TTL:
+ return DHCP_OPTION_TYPE_UINT8;
+ case SD_DHCP_OPTION_MTU_TIMEOUT:
+ return DHCP_OPTION_TYPE_TIME;
+ case SD_DHCP_OPTION_MTU_INTERFACE:
+ return DHCP_OPTION_TYPE_UINT16;
+ case SD_DHCP_OPTION_MTU_SUBNET:
+ return DHCP_OPTION_TYPE_BOOL;
+ case SD_DHCP_OPTION_BROADCAST:
+ return DHCP_OPTION_TYPE_ADDRESS;
+ case SD_DHCP_OPTION_MASK_DISCOVERY:
+ case SD_DHCP_OPTION_MASK_SUPPLIER:
+ case SD_DHCP_OPTION_ROUTER_DISCOVERY:
+ return DHCP_OPTION_TYPE_BOOL;
+ case SD_DHCP_OPTION_ROUTER_REQUEST:
+ return DHCP_OPTION_TYPE_ADDRESS;
+ case SD_DHCP_OPTION_STATIC_ROUTE:
+ return DHCP_OPTION_TYPE_ROUTE;
+ case SD_DHCP_OPTION_TRAILERS:
+ return DHCP_OPTION_TYPE_BOOL;
+ case SD_DHCP_OPTION_ARP_TIMEOUT:
+ return DHCP_OPTION_TYPE_TIME;
+ case SD_DHCP_OPTION_ETHERNET:
+ return DHCP_OPTION_TYPE_BOOL;
+ case SD_DHCP_OPTION_DEFAULT_TCP_TTL:
+ return DHCP_OPTION_TYPE_UINT8;
+ case SD_DHCP_OPTION_KEEPALIVE_TIME:
+ return DHCP_OPTION_TYPE_TIME;
+ case SD_DHCP_OPTION_KEEPALIVE_DATA:
+ return DHCP_OPTION_TYPE_BOOL;
+ case SD_DHCP_OPTION_NIS_DOMAIN:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_NIS_SERVER:
+ case SD_DHCP_OPTION_NTP_SERVER:
+ return DHCP_OPTION_TYPE_ADDRESS;
+ case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION:
+ return DHCP_OPTION_TYPE_TBD;
+ case SD_DHCP_OPTION_NETBIOS_NAME_SERVER:
+ case SD_DHCP_OPTION_NETBIOS_DIST_SERVER:
+ return DHCP_OPTION_TYPE_ADDRESS;
+ case SD_DHCP_OPTION_NETBIOS_NODE_TYPE:
+ return DHCP_OPTION_TYPE_UINT8;
+ case SD_DHCP_OPTION_NETBIOS_SCOPE:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_X_WINDOW_FONT:
+ case SD_DHCP_OPTION_X_WINDOW_MANAGER:
+ return DHCP_OPTION_TYPE_ADDRESS;
+ case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS:
+ return DHCP_OPTION_TYPE_ADDRESS;
+ case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
+ return DHCP_OPTION_TYPE_TIME;
+ case SD_DHCP_OPTION_OVERLOAD:
+ case SD_DHCP_OPTION_MESSAGE_TYPE:
+ return DHCP_OPTION_TYPE_UINT8;
+ case SD_DHCP_OPTION_SERVER_IDENTIFIER:
+ return DHCP_OPTION_TYPE_ADDRESS;
+ case SD_DHCP_OPTION_ERROR_MESSAGE:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
+ return DHCP_OPTION_TYPE_UINT16;
+ case SD_DHCP_OPTION_RENEWAL_TIME:
+ case SD_DHCP_OPTION_REBINDING_TIME:
+ return DHCP_OPTION_TYPE_TIME;
+ case SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_NETWARE_IP_DOMAIN:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_NIS_DOMAIN_NAME:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_NIS_SERVER_ADDR:
+ return DHCP_OPTION_TYPE_ADDRESS;
+ case SD_DHCP_OPTION_BOOT_SERVER_NAME:
+ case SD_DHCP_OPTION_BOOT_FILENAME:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_HOME_AGENT_ADDRESS:
+ case SD_DHCP_OPTION_SMTP_SERVER:
+ case SD_DHCP_OPTION_POP3_SERVER:
+ case SD_DHCP_OPTION_NNTP_SERVER:
+ case SD_DHCP_OPTION_WWW_SERVER:
+ case SD_DHCP_OPTION_FINGER_SERVER:
+ case SD_DHCP_OPTION_IRC_SERVER:
+ case SD_DHCP_OPTION_STREETTALK_SERVER:
+ case SD_DHCP_OPTION_STDA_SERVER:
+ return DHCP_OPTION_TYPE_ADDRESS;
+ case SD_DHCP_OPTION_USER_CLASS:
+ return DHCP_OPTION_TYPE_LENGTH_PREFIXED_DATA;
+ case SD_DHCP_OPTION_RAPID_COMMIT:
+ return DHCP_OPTION_TYPE_FLAG;
+ case SD_DHCP_OPTION_FQDN:
+ return DHCP_OPTION_TYPE_FQDN;
+ case SD_DHCP_OPTION_POSIX_TIMEZONE:
+ case SD_DHCP_OPTION_TZDB_TIMEZONE:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_IPV6_ONLY_PREFERRED:
+ return DHCP_OPTION_TYPE_TIME;
+ case SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_DOMAIN_SEARCH:
+ return DHCP_OPTION_TYPE_SEARCH_DOMAINS;
+ case SD_DHCP_OPTION_SIP_SERVER:
+ return DHCP_OPTION_TYPE_SIP;
+ case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
+ return DHCP_OPTION_TYPE_ROUTE;
+ case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS:
+ case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION:
+ return DHCP_OPTION_TYPE_TBD;
+ case SD_DHCP_OPTION_MUD_URL:
+ return DHCP_OPTION_TYPE_STRING;
+ case SD_DHCP_OPTION_V4_DNR:
+ return DHCP_OPTION_TYPE_DNR;
+ case SD_DHCP_OPTION_6RD:
+ return DHCP_OPTION_TYPE_6RD;
+ case SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE:
+ return DHCP_OPTION_TYPE_ROUTE;
+ case SD_DHCP_OPTION_END:
+ return -EINVAL;
+ default:
+ return DHCP_OPTION_TYPE_HEX;
+ }
+}
+
+static int dump_dhcp_option_hex(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+
+ _cleanup_(iovec_done) struct iovec iov = {};
+ r = dhcp_message_get_option_alloc(message, code, &iov);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read DHCP option %u: %m", code);
+
+ _cleanup_free_ char *str = NULL;
+ r = iovec_to_hex(&iov, &str);
+ if (r < 0)
+ return r;
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_STRING, str);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_flag(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+
+ r = dhcp_message_get_option_flag(message, code);
+ if (r < 0) {
+ if (fallback)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+ return log_error_errno(r, "Failed to dump DHCP option %u as flag: %m", code);
+ }
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_BOOLEAN, true);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_bool(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+
+ uint8_t u;
+ r = dhcp_message_get_option_u8(message, code, &u);
+ if (r < 0) {
+ if (fallback)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+ return log_error_errno(r, "Failed to read option %u as boolean: %m", code);
+ }
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_BOOLEAN, !!u);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_uint8(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+
+ uint8_t u;
+ r = dhcp_message_get_option_u8(message, code, &u);
+ if (r < 0) {
+ if (fallback)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+ return log_error_errno(r, "Failed to read option %u as uint8: %m", code);
+ }
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_UINT8, u);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_uint16(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+
+ uint16_t u;
+ r = dhcp_message_get_option_u16(message, code, &u);
+ if (r < 0) {
+ if (fallback)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+ return log_error_errno(r, "Failed to read option %u as uint16: %m", code);
+ }
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_UINT16, u);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_time(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+
+ usec_t usec;
+ r = dhcp_message_get_option_sec(message, code, /* max_as_infinity= */ true, &usec);
+ if (r < 0) {
+ if (fallback)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+ return log_error_errno(r, "Failed to read option %u as time: %m", code);
+ }
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_TIMESPAN, usec);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_string(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+
+ _cleanup_free_ char *str = NULL;
+ r = dhcp_message_get_option_string(message, code, &str);
+ if (r < 0) {
+ if (fallback)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+ return log_error_errno(r, "Failed to read option %u as string: %m", code);
+ }
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_STRING, str);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_address(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+
+ _cleanup_free_ struct in_addr *addrs = NULL;
+ size_t n_addrs;
+ r = dhcp_message_get_option_addresses(message, code, &n_addrs, &addrs);
+ if (r < 0) {
+ if (fallback)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+ return log_error_errno(r, "Failed to read option %u as address: %m", code);
+ }
+
+ _cleanup_strv_free_ char **strv = NULL;
+ size_t n_strv = 0;
+ FOREACH_ARRAY(a, addrs, n_addrs) {
+ r = strv_extend_with_size(&strv, &n_strv, IN4_ADDR_TO_STRING(a));
+ if (r < 0)
+ return log_oom();
+ }
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_STRV, strv);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_sip(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+ assert(code == SD_DHCP_OPTION_SIP_SERVER);
+
+ _cleanup_strv_free_ char **strv = NULL;
+ if (dhcp_message_get_option_domains(message, code, &strv) < 0)
+ return dump_dhcp_option_address(table, message, code, /* fallback= */ true);
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_STRV, strv);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_fqdn(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+ assert(code == SD_DHCP_OPTION_FQDN);
+
+ _cleanup_free_ char *fqdn = NULL;
+ uint8_t flags;
+ if (dhcp_message_get_option_fqdn(message, &flags, &fqdn) < 0)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code));
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_cell_stringf(table, /* ret_cell= */ NULL, "flags: 0x%x, fqdn: %s", flags, fqdn);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_route(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+
+ _cleanup_free_ sd_dhcp_route *routes = NULL;
+ size_t n_routes;
+ if (dhcp_message_get_option_routes(message, code, &n_routes, &routes) < 0)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+
+ _cleanup_strv_free_ char **strv = NULL;
+ size_t n_strv = 0;
+ FOREACH_ARRAY(route, routes, n_routes) {
+ r = strv_extendf_with_size(&strv, &n_strv, "%s via %s",
+ IN4_ADDR_PREFIX_TO_STRING(&route->dst_addr, route->dst_prefixlen),
+ IN4_ADDR_TO_STRING(&route->gw_addr));
+ if (r < 0)
+ return log_oom();
+ }
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_STRV, strv);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_length_prefixed_data(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+
+ _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
+ if (dhcp_message_get_option_length_prefixed_data(message, code, /* length_size= */ 1, &iovw) < 0)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+
+ _cleanup_strv_free_ char **strv = NULL;
+ r = iovw_to_strv(&iovw, &strv);
+ if (r < 0)
+ return r;
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_STRV, strv);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_tbd(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+
+ if (!dhcp_message_has_option(message, code))
+ return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "The DHCP message does not have option %u.", code);
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_STRING, "See below.");
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_search_domains(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+ assert(code == SD_DHCP_OPTION_DOMAIN_SEARCH);
+
+ _cleanup_strv_free_ char **strv = NULL;
+ if (dhcp_message_get_option_domains(message, code, &strv) < 0)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_STRV, strv);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_dnr(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+ assert(code == SD_DHCP_OPTION_V4_DNR);
+
+ sd_dns_resolver *resolvers = NULL;
+ size_t n_resolvers = 0;
+ CLEANUP_ARRAY(resolvers, n_resolvers, dns_resolver_free_array);
+
+ if (dhcp_message_get_option_dnr(message, &n_resolvers, &resolvers) < 0)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+
+ _cleanup_strv_free_ char **strv = NULL;
+ r = dns_resolvers_to_dot_strv(resolvers, n_resolvers, &strv);
+ if (r < 0)
+ return log_oom();
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code),
+ TABLE_STRV, strv);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_6rd(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) {
+ int r;
+
+ assert(table);
+ assert(message);
+ assert(code == SD_DHCP_OPTION_6RD);
+
+ uint8_t ipv4masklen, prefixlen;
+ struct in6_addr prefix;
+ size_t n_br_addresses;
+ _cleanup_free_ struct in_addr *br_addresses = NULL;
+ if (dhcp_message_get_option_6rd(message, &ipv4masklen, &prefixlen, &prefix, &n_br_addresses, &br_addresses) < 0)
+ return dump_dhcp_option_hex(table, message, code, /* fallback= */ false);
+
+ _cleanup_free_ char *str = asprintf_safe("ipv4masklen: %u, prefix: %s, br_addresses: ",
+ ipv4masklen, IN6_ADDR_PREFIX_TO_STRING(&prefix, prefixlen));
+ if (!str)
+ return log_oom();
+
+ assert(n_br_addresses > 0);
+ _cleanup_free_ char *br_addresses_str = NULL;
+ FOREACH_ARRAY(a, br_addresses, n_br_addresses)
+ if (!strextend_with_separator(&br_addresses_str, ", ", IN4_ADDR_TO_STRING(a)))
+ return log_oom();
+
+ r = table_add_many(
+ table,
+ TABLE_UINT8, code,
+ TABLE_STRING, dhcp_option_code_to_string(code));
+ if (r < 0)
+ return table_log_add_error(r);
+
+ r = table_add_cell_stringf(table, /* ret_cell= */ NULL, "%s%s", str, br_addresses_str);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_option_one(Table *table, sd_dhcp_message *message, uint8_t code, DHCPOptionType type) {
+ assert(table);
+ assert(message);
+ assert(!IN_SET(code, SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END));
+
+ typedef int (*dump_dhcp_option_t)(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback);
+
+ static const dump_dhcp_option_t functions[_DHCP_OPTION_TYPE_MAX] = {
+ [DHCP_OPTION_TYPE_HEX] = dump_dhcp_option_hex,
+ [DHCP_OPTION_TYPE_FLAG] = dump_dhcp_option_flag,
+ [DHCP_OPTION_TYPE_BOOL] = dump_dhcp_option_bool,
+ [DHCP_OPTION_TYPE_UINT8] = dump_dhcp_option_uint8,
+ [DHCP_OPTION_TYPE_UINT16] = dump_dhcp_option_uint16,
+ [DHCP_OPTION_TYPE_TIME] = dump_dhcp_option_time,
+ [DHCP_OPTION_TYPE_STRING] = dump_dhcp_option_string,
+ [DHCP_OPTION_TYPE_ADDRESS] = dump_dhcp_option_address,
+ [DHCP_OPTION_TYPE_SIP] = dump_dhcp_option_sip,
+ [DHCP_OPTION_TYPE_FQDN] = dump_dhcp_option_fqdn,
+ [DHCP_OPTION_TYPE_ROUTE] = dump_dhcp_option_route,
+ [DHCP_OPTION_TYPE_LENGTH_PREFIXED_DATA] = dump_dhcp_option_length_prefixed_data,
+ [DHCP_OPTION_TYPE_SEARCH_DOMAINS] = dump_dhcp_option_search_domains,
+ [DHCP_OPTION_TYPE_DNR] = dump_dhcp_option_dnr,
+ [DHCP_OPTION_TYPE_6RD] = dump_dhcp_option_6rd,
+ [DHCP_OPTION_TYPE_TBD] = dump_dhcp_option_tbd,
+ };
+
+ bool fallback = false;
+ if (type == DHCP_OPTION_TYPE_AUTO) {
+ type = dhcp_option_type_from_code(code);
+ fallback = true;
+ }
+
+ assert(functions[type]);
+ return functions[type](table, message, code, fallback);
+}
+
+static int parse_arg(const char *arg, uint8_t *ret_code, DHCPOptionType *ret_type) {
+ _cleanup_free_ char *buf = NULL;
+ const char *code_str, *type_str;
+ int r;
+
+ assert(arg);
+
+ const char *colon = strchr(arg, ':');
+ if (colon) {
+ buf = strndup(arg, colon - arg);
+ if (!buf)
+ return log_oom();
+
+ code_str = buf;
+ type_str = colon + 1;
+ } else {
+ code_str = arg;
+ type_str = NULL;
+ }
+
+ uint8_t code;
+ r = safe_atou8(code_str, &code);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse option code number '%s': %m", code_str);
+
+ if (IN_SET(code, SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Invalid option code number: %u", code);
+
+ DHCPOptionType type = DHCP_OPTION_TYPE_AUTO;
+ if (type_str) {
+ type = dhcp_option_type_from_string(type_str);
+ if (type < 0)
+ return log_error_errno(type, "Failed to parse option type '%s': %m", type_str);
+ }
+
+ if (ret_code)
+ *ret_code = code;
+ if (ret_type)
+ *ret_type = type;
+ return 0;
+}
+
+static int dump_dhcp_options(sd_dhcp_message *message, char * const *args, DumpDHCPMessageFlag flags) {
+ int r;
+
+ assert(message);
+
+ _cleanup_(table_unrefp) Table *table = table_new("code", "name", "data");
+ if (!table)
+ return log_oom();
+
+ table_apply_flags(table, flags);
+
+ (void) table_set_sort(table, (size_t) 0);
+
+ TableCell *cell = table_get_cell(table, 0, 0);
+ if (!cell)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell.");
+
+ (void) table_set_align_percent(table, cell, 100);
+ (void) table_set_ellipsize_percent(table, cell, 100);
+
+ bool
+ has_vendor_specific_information = false,
+ has_vendor_identifying_vendor_class = false,
+ has_vendor_identifying_vendor_specific_information = false;
+
+ if (strv_isempty(args)) {
+ void *tagp;
+ struct iovec_wrapper *iovw;
+ HASHMAP_FOREACH_KEY(iovw, tagp, message->options.entries) {
+ uint32_t tag = PTR_TO_UINT32(tagp);
+ assert(tag > 0);
+ assert(tag < UINT8_MAX);
+
+ r = dump_dhcp_option_one(table, message, tag, DHCP_OPTION_TYPE_AUTO);
+ if (r < 0)
+ return r;
+
+ switch (tag) {
+ case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION:
+ has_vendor_specific_information = true;
+ break;
+ case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS:
+ has_vendor_identifying_vendor_class = true;
+ break;
+ case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION:
+ has_vendor_identifying_vendor_specific_information = true;
+ break;
+ }
+ }
+ } else
+ STRV_FOREACH(arg, args) {
+ uint8_t code = SD_DHCP_OPTION_PAD; /* avoid false maybe-uninitialized warning */
+ DHCPOptionType type = DHCP_OPTION_TYPE_AUTO; /* avoid false maybe-uninitialized warning */
+ r = parse_arg(*arg, &code, &type);
+ if (r < 0)
+ return r;
+ assert(!IN_SET(code, SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END));
+
+ r = dump_dhcp_option_one(table, message, code, type);
+ if (r < 0)
+ return r;
+
+ if (type == DHCP_OPTION_TYPE_AUTO)
+ switch (code) {
+ case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION:
+ has_vendor_specific_information = true;
+ break;
+ case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS:
+ has_vendor_identifying_vendor_class = true;
+ break;
+ case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION:
+ has_vendor_identifying_vendor_specific_information = true;
+ break;
+ }
+ }
+
+ if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND))
+ printf("%s%sOptions:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal());
+
+ r = table_print_or_warn(table);
+ if (r < 0)
+ return r;
+
+ if (has_vendor_specific_information) {
+ r = dump_dhcp_option_vendor_specific_information(message, flags);
+ if (r < 0)
+ return r;
+ }
+
+ if (has_vendor_identifying_vendor_class) {
+ r = dump_dhcp_option_vendor_identifying_vendor_class(message, flags);
+ if (r < 0)
+ return r;
+ }
+
+ if (has_vendor_identifying_vendor_specific_information) {
+ r = dump_dhcp_option_vendor_identifying_vendor_specific_information(message, flags);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int dump_buffer(Table *table, const char *field, uint8_t *buf, size_t len) {
+ int r;
+
+ assert(table);
+ assert(field);
+ assert(buf);
+ assert(len > 0);
+
+ uint8_t *nul = memchr(buf, 0, len);
+ if (nul)
+ len = nul - buf;
+
+ if (len == 0)
+ return 0;
+
+ _cleanup_free_ char *str = NULL;
+ r = make_cstring(buf, len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &str);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse buffer for field '%s': %m", field);
+
+ if (isempty(str))
+ return 0;
+
+ r = table_add_many(
+ table,
+ TABLE_FIELD, field,
+ TABLE_STRING, str);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ return 0;
+}
+
+static int dump_dhcp_header(sd_dhcp_message *message, DumpDHCPMessageFlag flags) {
+ int r;
+
+ assert(message);
+
+ _cleanup_(table_unrefp) Table *table = table_new_vertical();
+ if (!table)
+ return log_oom();
+
+ table_apply_flags(table, flags);
+
+ TableCell *cell = table_get_cell(table, 0, 0);
+ if (!cell)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell.");
+
+ (void) table_set_align_percent(table, cell, 100);
+ (void) table_set_ellipsize_percent(table, cell, 100);
+
+ struct hw_addr_data hw_addr;
+ r = dhcp_message_get_hw_addr(message, &hw_addr);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get hardware address from DHCP message: %m");
+
+ struct in_addr yiaddr = { .s_addr = message->header.yiaddr };
+
+ r = table_add_many(
+ table,
+ TABLE_FIELD, "Hardware Type",
+ TABLE_STRING, arphrd_to_name(message->header.htype),
+ TABLE_FIELD, "Hardware Address",
+ TABLE_STRING, HW_ADDR_TO_STR(&hw_addr),
+ TABLE_FIELD, "Client Address",
+ TABLE_IN_ADDR, &yiaddr);
+ if (r < 0)
+ return table_log_add_error(r);
+
+ if (message->header.siaddr != INADDR_ANY) {
+ struct in_addr siaddr = { .s_addr = message->header.siaddr };
+
+ r = table_add_many(
+ table,
+ TABLE_FIELD, "Server Address",
+ TABLE_IN_ADDR, &siaddr);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ if (message->header.giaddr != INADDR_ANY) {
+ struct in_addr giaddr = { .s_addr = message->header.giaddr };
+
+ r = table_add_many(
+ table,
+ TABLE_FIELD, "Relay Agent Address",
+ TABLE_IN_ADDR, &giaddr);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ uint8_t overload = DHCP_OVERLOAD_NONE;
+ (void) dhcp_message_get_option_u8(message, SD_DHCP_OPTION_OVERLOAD, &overload);
+
+ if (!FLAGS_SET(overload, DHCP_OVERLOAD_SNAME)) {
+ r = dump_buffer(table, "Server Host Name", message->header.sname, sizeof(message->header.sname));
+ if (r < 0)
+ return r;
+ }
+
+ if (!FLAGS_SET(overload, DHCP_OVERLOAD_FILE)) {
+ r = dump_buffer(table, "Boot File Name", message->header.file, sizeof(message->header.file));
+ if (r < 0)
+ return r;
+ }
+
+ if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND))
+ printf("%s%sHeader:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal());
+
+ return table_print_or_warn(table);
+}
+
+int dump_dhcp_message(sd_dhcp_message *m, char * const *args, DumpDHCPMessageFlag flags) {
+ int r;
+
+ assert(m);
+
+ if (strv_isempty(args)) {
+ r = dump_dhcp_header(m, flags);
+ if (r < 0)
+ return r;
+
+ putchar('\n');
+ }
+
+ return dump_dhcp_options(m, args, flags);
+}