diff --git a/src/include/linux/netlink.h b/src/include/linux/netlink.h index 777a1b7da9..b2da318b64 100644 --- a/src/include/linux/netlink.h +++ b/src/include/linux/netlink.h @@ -67,6 +67,14 @@ struct nlmsghdr { #define NLM_F_CREATE 0x400 /* Create, if it does not exist */ #define NLM_F_APPEND 0x800 /* Add to end of list */ +/* Modifiers to DELETE request */ +#define NLM_F_NONREC 0x100 /* Do not delete recursively */ +#define NLM_F_BULK 0x200 /* Delete multiple objects */ + +/* Flags for ACK message */ +#define NLM_F_CAPPED 0x100 /* request was capped */ +#define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */ + /* 4.4BSD ADD NLM_F_CREATE|NLM_F_EXCL 4.4BSD CHANGE NLM_F_REPLACE @@ -99,6 +107,45 @@ struct nlmsghdr { struct nlmsgerr { int error; struct nlmsghdr msg; + /* + * followed by the message contents unless NETLINK_CAP_ACK was set + * or the ACK indicates success (error == 0) + * message length is aligned with NLMSG_ALIGN() + */ + /* + * followed by TLVs defined in enum nlmsgerr_attrs + * if NETLINK_EXT_ACK was set + */ +}; + +/** + * enum nlmsgerr_attrs - nlmsgerr attributes + * @NLMSGERR_ATTR_UNUSED: unused + * @NLMSGERR_ATTR_MSG: error message string (string) + * @NLMSGERR_ATTR_OFFS: offset of the invalid attribute in the original + * message, counting from the beginning of the header (u32) + * @NLMSGERR_ATTR_COOKIE: arbitrary subsystem specific cookie to + * be used - in the success case - to identify a created + * object or operation or similar (binary) + * @NLMSGERR_ATTR_POLICY: policy for a rejected attribute + * @NLMSGERR_ATTR_MISS_TYPE: type of a missing required attribute, + * %NLMSGERR_ATTR_MISS_NEST will not be present if the attribute was + * missing at the message level + * @NLMSGERR_ATTR_MISS_NEST: offset of the nest where attribute was missing + * @__NLMSGERR_ATTR_MAX: number of attributes + * @NLMSGERR_ATTR_MAX: highest attribute number + */ +enum nlmsgerr_attrs { + NLMSGERR_ATTR_UNUSED, + NLMSGERR_ATTR_MSG, + NLMSGERR_ATTR_OFFS, + NLMSGERR_ATTR_COOKIE, + NLMSGERR_ATTR_POLICY, + NLMSGERR_ATTR_MISS_TYPE, + NLMSGERR_ATTR_MISS_NEST, + + __NLMSGERR_ATTR_MAX, + NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1 }; #define NETLINK_ADD_MEMBERSHIP 1 @@ -110,6 +157,8 @@ struct nlmsgerr { #define NETLINK_TX_RING 7 #define NETLINK_LISTEN_ALL_NSID 8 #define NETLINK_LIST_MEMBERSHIPS 9 +#define NETLINK_CAP_ACK 10 +#define NETLINK_EXT_ACK 11 struct nl_pktinfo { __u32 group; diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 6f7b50fb7a..38670c41d7 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -1217,9 +1217,7 @@ static status_t get_spi_internal(private_kernel_netlink_ipsec_t *this, } case NLMSG_ERROR: { - struct nlmsgerr *err = NLMSG_DATA(hdr); - DBG1(DBG_KNL, "allocating SPI failed: %s (%d)", - strerror(-err->error), -err->error); + netlink_log_error(hdr, "allocating SPI failed"); break; } default: @@ -2099,9 +2097,8 @@ static void get_replay_state(private_kernel_netlink_ipsec_t *this, } case NLMSG_ERROR: { - struct nlmsgerr *err = NLMSG_DATA(hdr); - DBG1(DBG_KNL, "querying replay state from SAD entry " - "failed: %s (%d)", strerror(-err->error), -err->error); + netlink_log_error(hdr, "querying replay state from SAD " + "entry failed"); break; } default: @@ -2201,11 +2198,7 @@ METHOD(kernel_ipsec_t, query_sa, status_t, } case NLMSG_ERROR: { - struct nlmsgerr *err = NLMSG_DATA(hdr); - - DBG1(DBG_KNL, "querying SAD entry with SPI %.8x%s failed: " - "%s (%d)", ntohl(id->spi), markstr, - strerror(-err->error), -err->error); + netlink_log_error(hdr, "querying SAD entry failed"); break; } default: @@ -2391,9 +2384,7 @@ METHOD(kernel_ipsec_t, update_sa, status_t, } case NLMSG_ERROR: { - struct nlmsgerr *err = NLMSG_DATA(hdr); - DBG1(DBG_KNL, "querying SAD entry failed: %s (%d)", - strerror(-err->error), -err->error); + netlink_log_error(hdr, "querying SAD entry failed"); break; } default: @@ -3086,9 +3077,7 @@ METHOD(kernel_ipsec_t, query_policy, status_t, } case NLMSG_ERROR: { - struct nlmsgerr *err = NLMSG_DATA(hdr); - DBG1(DBG_KNL, "querying policy failed: %s (%d)", - strerror(-err->error), -err->error); + netlink_log_error(hdr, "querying policy failed"); break; } default: @@ -3609,9 +3598,7 @@ static bool get_spd_hash_thresh(private_kernel_netlink_ipsec_t *this, } case NLMSG_ERROR: { - struct nlmsgerr *err = NLMSG_DATA(hdr); - DBG1(DBG_KNL, "getting SPD hash threshold failed: %s (%d)", - strerror(-err->error), -err->error); + netlink_log_error(hdr, "getting SPD hash threshold failed"); break; } default: diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c index 4baf457939..95cb00f4c2 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c @@ -1,6 +1,6 @@ /* + * Copyright (C) 2008-2022 Tobias Brunner * Copyright (C) 2014 Martin Willi - * Copyright (C) 2008-2020 Tobias Brunner * * Copyright (C) secunet Security Networks AG * @@ -535,7 +535,7 @@ METHOD(netlink_socket_t, netlink_send_ack, status_t, { case NLMSG_ERROR: { - struct nlmsgerr* err = NLMSG_DATA(hdr); + struct nlmsgerr *err = NLMSG_DATA(hdr); if (err->error) { @@ -549,11 +549,11 @@ METHOD(netlink_socket_t, netlink_send_ack, status_t, free(out); return NOT_FOUND; } - DBG1(DBG_KNL, "received netlink error: %s (%d)", - strerror(-err->error), -err->error); + netlink_log_error(hdr, NULL); free(out); return FAILED; } + netlink_log_error(hdr, NULL); free(out); return SUCCESS; } @@ -620,7 +620,7 @@ netlink_socket_t *netlink_socket_create(int protocol, enum_name_t *names, .nl_family = AF_NETLINK, }; bool force_buf = FALSE; - int rcvbuf_size = 0; + int on = 1, rcvbuf_size = 0; INIT(this, .public = { @@ -659,6 +659,13 @@ netlink_socket_t *netlink_socket_create(int protocol, enum_name_t *names, destroy(this); return NULL; } + + /* don't echo back the request payload in error messages, might not be + * supported by older kernels, so don't check the result */ + setsockopt(this->socket, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on)); + /* enable extended ACK attributes, might not be supported by older kernels */ + setsockopt(this->socket, SOL_NETLINK, NETLINK_EXT_ACK, &on, sizeof(on)); + rcvbuf_size = lib->settings->get_int(lib->settings, "%s.plugins.kernel-netlink.receive_buffer_size", rcvbuf_size, lib->ns); @@ -766,6 +773,69 @@ void *netlink_reserve(struct nlmsghdr *hdr, int buflen, int type, int len) return RTA_DATA(rta); } +/* + * Described in header + */ +void netlink_log_error(struct nlmsghdr *hdr, const char *prefix) +{ + struct nlmsgerr *err = NLMSG_DATA(hdr); + struct rtattr *rta; + size_t offset, rtasize; + const char *msg = NULL; + bool is_error = err->error != 0; + + if (!prefix) + { + prefix = is_error ? "received netlink error" + : "received netlink warning"; + } + + if (hdr->nlmsg_flags & NLM_F_ACK_TLVS) + { + /* skip the headers, and the request payload for older kernels that + * don't support omitting it */ + offset = sizeof(*err); + if (!(hdr->nlmsg_flags & NLM_F_CAPPED)) + { + offset += err->msg.nlmsg_len - NLMSG_HDRLEN; + } + + rta = (struct rtattr*)(NLMSG_DATA(hdr) + NLMSG_ALIGN(offset)); + rtasize = NLMSG_PAYLOAD(hdr, offset); + while (RTA_OK(rta, rtasize)) + { + if (rta->rta_type == NLMSGERR_ATTR_MSG) + { + msg = RTA_DATA(rta); + /* sanity check, strings from the kernel should be terminated */ + if (!RTA_PAYLOAD(rta) || msg[RTA_PAYLOAD(rta)-1] != '\0') + { + msg = NULL; + } + break; + } + rta = RTA_NEXT(rta, rtasize); + } + } + + if (msg && *msg) + { + if (is_error) + { + DBG1(DBG_KNL, "%s: %s (%d)", prefix, msg, -err->error); + } + else + { + DBG2(DBG_KNL, "%s: %s", prefix, msg); + } + } + else if (is_error) + { + DBG1(DBG_KNL, "%s: %s (%d)", prefix, strerror(-err->error), + -err->error); + } +} + /* * Described in header */ diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h index 816b53de73..cec3e5e5b9 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2020 Tobias Brunner + * Copyright (C) 2008-2022 Tobias Brunner * * Copyright (C) secunet Security Networks AG * @@ -122,7 +122,17 @@ void netlink_nested_end(struct nlmsghdr *hdr, void *attr); * @param len length of RTA data * @return buffer to len bytes of attribute data, NULL on error */ -void* netlink_reserve(struct nlmsghdr *hdr, int buflen, int type, int len); +void *netlink_reserve(struct nlmsghdr *hdr, int buflen, int type, int len); + +/** + * Log extended ACK error/warning message in a NLMSG_ERROR message. In error + * messages (i.e. error != 0), the generic error message is logged if no + * extended ACK message is available. + * + * @param hdr netlink message + * @param prefix optional prefix to add before error message + */ +void netlink_log_error(struct nlmsghdr *hdr, const char *prefix); /** * Determine buffer size for received messages (e.g. events).