mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-08-25 07:44:53 +00:00

The current netlink API for WireGuard does not directly support removal of allowed ips from a peer. A user can remove an allowed ip from a peer in one of two ways: 1. By using the WGPEER_F_REPLACE_ALLOWEDIPS flag and providing a new list of allowed ips which omits the allowed ip that is to be removed. 2. By reassigning an allowed ip to a "dummy" peer then removing that peer with WGPEER_F_REMOVE_ME. With the first approach, the driver completely rebuilds the allowed ip list for a peer. If my current configuration is such that a peer has allowed ips 192.168.0.2 and 192.168.0.3 and I want to remove 192.168.0.2 the actual transition looks like this. [192.168.0.2, 192.168.0.3] <-- Initial state [] <-- Step 1: Allowed ips removed for peer [192.168.0.3] <-- Step 2: Allowed ips added back for peer This is true even if the allowed ip list is small and the update does not need to be batched into multiple WG_CMD_SET_DEVICE requests, as the removal and subsequent addition of ips is non-atomic within a single request. Consequently, wg_allowedips_lookup_dst and wg_allowedips_lookup_src may return NULL while reconfiguring a peer even for packets bound for ips a user did not intend to remove leading to unintended interruptions in connectivity. This presents in userspace as failed calls to sendto and sendmsg for UDP sockets. In my case, I ran netperf while repeatedly reconfiguring the allowed ips for a peer with wg. /usr/local/bin/netperf -H 10.102.73.72 -l 10m -t UDP_STREAM -- -R 1 -m 1024 send_data: data send error: No route to host (errno 113) netperf: send_omni: send_data failed: No route to host While this may not be of particular concern for environments where peers and allowed ips are mostly static, systems like Cilium manage peers and allowed ips in a dynamic environment where peers (i.e. Kubernetes nodes) and allowed ips (i.e. pods running on those nodes) can frequently change making WGPEER_F_REPLACE_ALLOWEDIPS problematic. The second approach avoids any possible connectivity interruptions but is hacky and less direct, requiring the creation of a temporary peer just to dispose of an allowed ip. Introduce a new flag called WGALLOWEDIP_F_REMOVE_ME which in the same way that WGPEER_F_REMOVE_ME allows a user to remove a single peer from a WireGuard device's configuration allows a user to remove an ip from a peer's set of allowed ips. This enables incremental updates to a device's configuration without any connectivity blips or messy workarounds. A corresponding patch for wg extends the existing `wg set` interface to leverage this feature. $ wg set wg0 peer <PUBKEY> allowed-ips +192.168.88.0/24,-192.168.0.1/32 When '+' or '-' is prepended to any ip in the list, wg clears WGPEER_F_REPLACE_ALLOWEDIPS and sets the WGALLOWEDIP_F_REMOVE_ME flag on any ip prefixed with '-'. Signed-off-by: Jordan Rife <jordan@jrife.io> [Jason: minor style nits, fixes to selftest, bump of wireguard-tools version] Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> Link: https://patch.msgid.link/20250521212707.1767879-5-Jason@zx2c4.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
64 lines
2.1 KiB
C
64 lines
2.1 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
|
*/
|
|
|
|
#ifndef _WG_ALLOWEDIPS_H
|
|
#define _WG_ALLOWEDIPS_H
|
|
|
|
#include <linux/mutex.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/ipv6.h>
|
|
|
|
struct wg_peer;
|
|
|
|
struct allowedips_node {
|
|
struct wg_peer __rcu *peer;
|
|
struct allowedips_node __rcu *bit[2];
|
|
u8 cidr, bit_at_a, bit_at_b, bitlen;
|
|
u8 bits[16] __aligned(__alignof(u64));
|
|
|
|
/* Keep rarely used members at bottom to be beyond cache line. */
|
|
unsigned long parent_bit_packed;
|
|
union {
|
|
struct list_head peer_list;
|
|
struct rcu_head rcu;
|
|
};
|
|
};
|
|
|
|
struct allowedips {
|
|
struct allowedips_node __rcu *root4;
|
|
struct allowedips_node __rcu *root6;
|
|
u64 seq;
|
|
} __aligned(4); /* We pack the lower 2 bits of &root, but m68k only gives 16-bit alignment. */
|
|
|
|
void wg_allowedips_init(struct allowedips *table);
|
|
void wg_allowedips_free(struct allowedips *table, struct mutex *mutex);
|
|
int wg_allowedips_insert_v4(struct allowedips *table, const struct in_addr *ip,
|
|
u8 cidr, struct wg_peer *peer, struct mutex *lock);
|
|
int wg_allowedips_insert_v6(struct allowedips *table, const struct in6_addr *ip,
|
|
u8 cidr, struct wg_peer *peer, struct mutex *lock);
|
|
int wg_allowedips_remove_v4(struct allowedips *table, const struct in_addr *ip,
|
|
u8 cidr, struct wg_peer *peer, struct mutex *lock);
|
|
int wg_allowedips_remove_v6(struct allowedips *table, const struct in6_addr *ip,
|
|
u8 cidr, struct wg_peer *peer, struct mutex *lock);
|
|
void wg_allowedips_remove_by_peer(struct allowedips *table,
|
|
struct wg_peer *peer, struct mutex *lock);
|
|
/* The ip input pointer should be __aligned(__alignof(u64))) */
|
|
int wg_allowedips_read_node(struct allowedips_node *node, u8 ip[16], u8 *cidr);
|
|
|
|
/* These return a strong reference to a peer: */
|
|
struct wg_peer *wg_allowedips_lookup_dst(struct allowedips *table,
|
|
struct sk_buff *skb);
|
|
struct wg_peer *wg_allowedips_lookup_src(struct allowedips *table,
|
|
struct sk_buff *skb);
|
|
|
|
#ifdef DEBUG
|
|
bool wg_allowedips_selftest(void);
|
|
#endif
|
|
|
|
int wg_allowedips_slab_init(void);
|
|
void wg_allowedips_slab_uninit(void);
|
|
|
|
#endif /* _WG_ALLOWEDIPS_H */
|