162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include "netlink.h" 762306a36Sopenharmony_ci#include "device.h" 862306a36Sopenharmony_ci#include "peer.h" 962306a36Sopenharmony_ci#include "socket.h" 1062306a36Sopenharmony_ci#include "queueing.h" 1162306a36Sopenharmony_ci#include "messages.h" 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <uapi/linux/wireguard.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/if.h> 1662306a36Sopenharmony_ci#include <net/genetlink.h> 1762306a36Sopenharmony_ci#include <net/sock.h> 1862306a36Sopenharmony_ci#include <crypto/algapi.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic struct genl_family genl_family; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic const struct nla_policy device_policy[WGDEVICE_A_MAX + 1] = { 2362306a36Sopenharmony_ci [WGDEVICE_A_IFINDEX] = { .type = NLA_U32 }, 2462306a36Sopenharmony_ci [WGDEVICE_A_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ - 1 }, 2562306a36Sopenharmony_ci [WGDEVICE_A_PRIVATE_KEY] = NLA_POLICY_EXACT_LEN(NOISE_PUBLIC_KEY_LEN), 2662306a36Sopenharmony_ci [WGDEVICE_A_PUBLIC_KEY] = NLA_POLICY_EXACT_LEN(NOISE_PUBLIC_KEY_LEN), 2762306a36Sopenharmony_ci [WGDEVICE_A_FLAGS] = { .type = NLA_U32 }, 2862306a36Sopenharmony_ci [WGDEVICE_A_LISTEN_PORT] = { .type = NLA_U16 }, 2962306a36Sopenharmony_ci [WGDEVICE_A_FWMARK] = { .type = NLA_U32 }, 3062306a36Sopenharmony_ci [WGDEVICE_A_PEERS] = { .type = NLA_NESTED } 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic const struct nla_policy peer_policy[WGPEER_A_MAX + 1] = { 3462306a36Sopenharmony_ci [WGPEER_A_PUBLIC_KEY] = NLA_POLICY_EXACT_LEN(NOISE_PUBLIC_KEY_LEN), 3562306a36Sopenharmony_ci [WGPEER_A_PRESHARED_KEY] = NLA_POLICY_EXACT_LEN(NOISE_SYMMETRIC_KEY_LEN), 3662306a36Sopenharmony_ci [WGPEER_A_FLAGS] = { .type = NLA_U32 }, 3762306a36Sopenharmony_ci [WGPEER_A_ENDPOINT] = NLA_POLICY_MIN_LEN(sizeof(struct sockaddr)), 3862306a36Sopenharmony_ci [WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL] = { .type = NLA_U16 }, 3962306a36Sopenharmony_ci [WGPEER_A_LAST_HANDSHAKE_TIME] = NLA_POLICY_EXACT_LEN(sizeof(struct __kernel_timespec)), 4062306a36Sopenharmony_ci [WGPEER_A_RX_BYTES] = { .type = NLA_U64 }, 4162306a36Sopenharmony_ci [WGPEER_A_TX_BYTES] = { .type = NLA_U64 }, 4262306a36Sopenharmony_ci [WGPEER_A_ALLOWEDIPS] = { .type = NLA_NESTED }, 4362306a36Sopenharmony_ci [WGPEER_A_PROTOCOL_VERSION] = { .type = NLA_U32 } 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic const struct nla_policy allowedip_policy[WGALLOWEDIP_A_MAX + 1] = { 4762306a36Sopenharmony_ci [WGALLOWEDIP_A_FAMILY] = { .type = NLA_U16 }, 4862306a36Sopenharmony_ci [WGALLOWEDIP_A_IPADDR] = NLA_POLICY_MIN_LEN(sizeof(struct in_addr)), 4962306a36Sopenharmony_ci [WGALLOWEDIP_A_CIDR_MASK] = { .type = NLA_U8 } 5062306a36Sopenharmony_ci}; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic struct wg_device *lookup_interface(struct nlattr **attrs, 5362306a36Sopenharmony_ci struct sk_buff *skb) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci struct net_device *dev = NULL; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci if (!attrs[WGDEVICE_A_IFINDEX] == !attrs[WGDEVICE_A_IFNAME]) 5862306a36Sopenharmony_ci return ERR_PTR(-EBADR); 5962306a36Sopenharmony_ci if (attrs[WGDEVICE_A_IFINDEX]) 6062306a36Sopenharmony_ci dev = dev_get_by_index(sock_net(skb->sk), 6162306a36Sopenharmony_ci nla_get_u32(attrs[WGDEVICE_A_IFINDEX])); 6262306a36Sopenharmony_ci else if (attrs[WGDEVICE_A_IFNAME]) 6362306a36Sopenharmony_ci dev = dev_get_by_name(sock_net(skb->sk), 6462306a36Sopenharmony_ci nla_data(attrs[WGDEVICE_A_IFNAME])); 6562306a36Sopenharmony_ci if (!dev) 6662306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 6762306a36Sopenharmony_ci if (!dev->rtnl_link_ops || !dev->rtnl_link_ops->kind || 6862306a36Sopenharmony_ci strcmp(dev->rtnl_link_ops->kind, KBUILD_MODNAME)) { 6962306a36Sopenharmony_ci dev_put(dev); 7062306a36Sopenharmony_ci return ERR_PTR(-EOPNOTSUPP); 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci return netdev_priv(dev); 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic int get_allowedips(struct sk_buff *skb, const u8 *ip, u8 cidr, 7662306a36Sopenharmony_ci int family) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci struct nlattr *allowedip_nest; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci allowedip_nest = nla_nest_start(skb, 0); 8162306a36Sopenharmony_ci if (!allowedip_nest) 8262306a36Sopenharmony_ci return -EMSGSIZE; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci if (nla_put_u8(skb, WGALLOWEDIP_A_CIDR_MASK, cidr) || 8562306a36Sopenharmony_ci nla_put_u16(skb, WGALLOWEDIP_A_FAMILY, family) || 8662306a36Sopenharmony_ci nla_put(skb, WGALLOWEDIP_A_IPADDR, family == AF_INET6 ? 8762306a36Sopenharmony_ci sizeof(struct in6_addr) : sizeof(struct in_addr), ip)) { 8862306a36Sopenharmony_ci nla_nest_cancel(skb, allowedip_nest); 8962306a36Sopenharmony_ci return -EMSGSIZE; 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci nla_nest_end(skb, allowedip_nest); 9362306a36Sopenharmony_ci return 0; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistruct dump_ctx { 9762306a36Sopenharmony_ci struct wg_device *wg; 9862306a36Sopenharmony_ci struct wg_peer *next_peer; 9962306a36Sopenharmony_ci u64 allowedips_seq; 10062306a36Sopenharmony_ci struct allowedips_node *next_allowedip; 10162306a36Sopenharmony_ci}; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci#define DUMP_CTX(cb) ((struct dump_ctx *)(cb)->args) 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic int 10662306a36Sopenharmony_ciget_peer(struct wg_peer *peer, struct sk_buff *skb, struct dump_ctx *ctx) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci struct nlattr *allowedips_nest, *peer_nest = nla_nest_start(skb, 0); 11062306a36Sopenharmony_ci struct allowedips_node *allowedips_node = ctx->next_allowedip; 11162306a36Sopenharmony_ci bool fail; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci if (!peer_nest) 11462306a36Sopenharmony_ci return -EMSGSIZE; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci down_read(&peer->handshake.lock); 11762306a36Sopenharmony_ci fail = nla_put(skb, WGPEER_A_PUBLIC_KEY, NOISE_PUBLIC_KEY_LEN, 11862306a36Sopenharmony_ci peer->handshake.remote_static); 11962306a36Sopenharmony_ci up_read(&peer->handshake.lock); 12062306a36Sopenharmony_ci if (fail) 12162306a36Sopenharmony_ci goto err; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (!allowedips_node) { 12462306a36Sopenharmony_ci const struct __kernel_timespec last_handshake = { 12562306a36Sopenharmony_ci .tv_sec = peer->walltime_last_handshake.tv_sec, 12662306a36Sopenharmony_ci .tv_nsec = peer->walltime_last_handshake.tv_nsec 12762306a36Sopenharmony_ci }; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci down_read(&peer->handshake.lock); 13062306a36Sopenharmony_ci fail = nla_put(skb, WGPEER_A_PRESHARED_KEY, 13162306a36Sopenharmony_ci NOISE_SYMMETRIC_KEY_LEN, 13262306a36Sopenharmony_ci peer->handshake.preshared_key); 13362306a36Sopenharmony_ci up_read(&peer->handshake.lock); 13462306a36Sopenharmony_ci if (fail) 13562306a36Sopenharmony_ci goto err; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci if (nla_put(skb, WGPEER_A_LAST_HANDSHAKE_TIME, 13862306a36Sopenharmony_ci sizeof(last_handshake), &last_handshake) || 13962306a36Sopenharmony_ci nla_put_u16(skb, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, 14062306a36Sopenharmony_ci peer->persistent_keepalive_interval) || 14162306a36Sopenharmony_ci nla_put_u64_64bit(skb, WGPEER_A_TX_BYTES, peer->tx_bytes, 14262306a36Sopenharmony_ci WGPEER_A_UNSPEC) || 14362306a36Sopenharmony_ci nla_put_u64_64bit(skb, WGPEER_A_RX_BYTES, peer->rx_bytes, 14462306a36Sopenharmony_ci WGPEER_A_UNSPEC) || 14562306a36Sopenharmony_ci nla_put_u32(skb, WGPEER_A_PROTOCOL_VERSION, 1)) 14662306a36Sopenharmony_ci goto err; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci read_lock_bh(&peer->endpoint_lock); 14962306a36Sopenharmony_ci if (peer->endpoint.addr.sa_family == AF_INET) 15062306a36Sopenharmony_ci fail = nla_put(skb, WGPEER_A_ENDPOINT, 15162306a36Sopenharmony_ci sizeof(peer->endpoint.addr4), 15262306a36Sopenharmony_ci &peer->endpoint.addr4); 15362306a36Sopenharmony_ci else if (peer->endpoint.addr.sa_family == AF_INET6) 15462306a36Sopenharmony_ci fail = nla_put(skb, WGPEER_A_ENDPOINT, 15562306a36Sopenharmony_ci sizeof(peer->endpoint.addr6), 15662306a36Sopenharmony_ci &peer->endpoint.addr6); 15762306a36Sopenharmony_ci read_unlock_bh(&peer->endpoint_lock); 15862306a36Sopenharmony_ci if (fail) 15962306a36Sopenharmony_ci goto err; 16062306a36Sopenharmony_ci allowedips_node = 16162306a36Sopenharmony_ci list_first_entry_or_null(&peer->allowedips_list, 16262306a36Sopenharmony_ci struct allowedips_node, peer_list); 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci if (!allowedips_node) 16562306a36Sopenharmony_ci goto no_allowedips; 16662306a36Sopenharmony_ci if (!ctx->allowedips_seq) 16762306a36Sopenharmony_ci ctx->allowedips_seq = peer->device->peer_allowedips.seq; 16862306a36Sopenharmony_ci else if (ctx->allowedips_seq != peer->device->peer_allowedips.seq) 16962306a36Sopenharmony_ci goto no_allowedips; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci allowedips_nest = nla_nest_start(skb, WGPEER_A_ALLOWEDIPS); 17262306a36Sopenharmony_ci if (!allowedips_nest) 17362306a36Sopenharmony_ci goto err; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci list_for_each_entry_from(allowedips_node, &peer->allowedips_list, 17662306a36Sopenharmony_ci peer_list) { 17762306a36Sopenharmony_ci u8 cidr, ip[16] __aligned(__alignof(u64)); 17862306a36Sopenharmony_ci int family; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci family = wg_allowedips_read_node(allowedips_node, ip, &cidr); 18162306a36Sopenharmony_ci if (get_allowedips(skb, ip, cidr, family)) { 18262306a36Sopenharmony_ci nla_nest_end(skb, allowedips_nest); 18362306a36Sopenharmony_ci nla_nest_end(skb, peer_nest); 18462306a36Sopenharmony_ci ctx->next_allowedip = allowedips_node; 18562306a36Sopenharmony_ci return -EMSGSIZE; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci nla_nest_end(skb, allowedips_nest); 18962306a36Sopenharmony_cino_allowedips: 19062306a36Sopenharmony_ci nla_nest_end(skb, peer_nest); 19162306a36Sopenharmony_ci ctx->next_allowedip = NULL; 19262306a36Sopenharmony_ci ctx->allowedips_seq = 0; 19362306a36Sopenharmony_ci return 0; 19462306a36Sopenharmony_cierr: 19562306a36Sopenharmony_ci nla_nest_cancel(skb, peer_nest); 19662306a36Sopenharmony_ci return -EMSGSIZE; 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic int wg_get_device_start(struct netlink_callback *cb) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci struct wg_device *wg; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci wg = lookup_interface(genl_info_dump(cb)->attrs, cb->skb); 20462306a36Sopenharmony_ci if (IS_ERR(wg)) 20562306a36Sopenharmony_ci return PTR_ERR(wg); 20662306a36Sopenharmony_ci DUMP_CTX(cb)->wg = wg; 20762306a36Sopenharmony_ci return 0; 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_cistatic int wg_get_device_dump(struct sk_buff *skb, struct netlink_callback *cb) 21162306a36Sopenharmony_ci{ 21262306a36Sopenharmony_ci struct wg_peer *peer, *next_peer_cursor; 21362306a36Sopenharmony_ci struct dump_ctx *ctx = DUMP_CTX(cb); 21462306a36Sopenharmony_ci struct wg_device *wg = ctx->wg; 21562306a36Sopenharmony_ci struct nlattr *peers_nest; 21662306a36Sopenharmony_ci int ret = -EMSGSIZE; 21762306a36Sopenharmony_ci bool done = true; 21862306a36Sopenharmony_ci void *hdr; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci rtnl_lock(); 22162306a36Sopenharmony_ci mutex_lock(&wg->device_update_lock); 22262306a36Sopenharmony_ci cb->seq = wg->device_update_gen; 22362306a36Sopenharmony_ci next_peer_cursor = ctx->next_peer; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, 22662306a36Sopenharmony_ci &genl_family, NLM_F_MULTI, WG_CMD_GET_DEVICE); 22762306a36Sopenharmony_ci if (!hdr) 22862306a36Sopenharmony_ci goto out; 22962306a36Sopenharmony_ci genl_dump_check_consistent(cb, hdr); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci if (!ctx->next_peer) { 23262306a36Sopenharmony_ci if (nla_put_u16(skb, WGDEVICE_A_LISTEN_PORT, 23362306a36Sopenharmony_ci wg->incoming_port) || 23462306a36Sopenharmony_ci nla_put_u32(skb, WGDEVICE_A_FWMARK, wg->fwmark) || 23562306a36Sopenharmony_ci nla_put_u32(skb, WGDEVICE_A_IFINDEX, wg->dev->ifindex) || 23662306a36Sopenharmony_ci nla_put_string(skb, WGDEVICE_A_IFNAME, wg->dev->name)) 23762306a36Sopenharmony_ci goto out; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci down_read(&wg->static_identity.lock); 24062306a36Sopenharmony_ci if (wg->static_identity.has_identity) { 24162306a36Sopenharmony_ci if (nla_put(skb, WGDEVICE_A_PRIVATE_KEY, 24262306a36Sopenharmony_ci NOISE_PUBLIC_KEY_LEN, 24362306a36Sopenharmony_ci wg->static_identity.static_private) || 24462306a36Sopenharmony_ci nla_put(skb, WGDEVICE_A_PUBLIC_KEY, 24562306a36Sopenharmony_ci NOISE_PUBLIC_KEY_LEN, 24662306a36Sopenharmony_ci wg->static_identity.static_public)) { 24762306a36Sopenharmony_ci up_read(&wg->static_identity.lock); 24862306a36Sopenharmony_ci goto out; 24962306a36Sopenharmony_ci } 25062306a36Sopenharmony_ci } 25162306a36Sopenharmony_ci up_read(&wg->static_identity.lock); 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci peers_nest = nla_nest_start(skb, WGDEVICE_A_PEERS); 25562306a36Sopenharmony_ci if (!peers_nest) 25662306a36Sopenharmony_ci goto out; 25762306a36Sopenharmony_ci ret = 0; 25862306a36Sopenharmony_ci /* If the last cursor was removed via list_del_init in peer_remove, then 25962306a36Sopenharmony_ci * we just treat this the same as there being no more peers left. The 26062306a36Sopenharmony_ci * reason is that seq_nr should indicate to userspace that this isn't a 26162306a36Sopenharmony_ci * coherent dump anyway, so they'll try again. 26262306a36Sopenharmony_ci */ 26362306a36Sopenharmony_ci if (list_empty(&wg->peer_list) || 26462306a36Sopenharmony_ci (ctx->next_peer && list_empty(&ctx->next_peer->peer_list))) { 26562306a36Sopenharmony_ci nla_nest_cancel(skb, peers_nest); 26662306a36Sopenharmony_ci goto out; 26762306a36Sopenharmony_ci } 26862306a36Sopenharmony_ci lockdep_assert_held(&wg->device_update_lock); 26962306a36Sopenharmony_ci peer = list_prepare_entry(ctx->next_peer, &wg->peer_list, peer_list); 27062306a36Sopenharmony_ci list_for_each_entry_continue(peer, &wg->peer_list, peer_list) { 27162306a36Sopenharmony_ci if (get_peer(peer, skb, ctx)) { 27262306a36Sopenharmony_ci done = false; 27362306a36Sopenharmony_ci break; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci next_peer_cursor = peer; 27662306a36Sopenharmony_ci } 27762306a36Sopenharmony_ci nla_nest_end(skb, peers_nest); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ciout: 28062306a36Sopenharmony_ci if (!ret && !done && next_peer_cursor) 28162306a36Sopenharmony_ci wg_peer_get(next_peer_cursor); 28262306a36Sopenharmony_ci wg_peer_put(ctx->next_peer); 28362306a36Sopenharmony_ci mutex_unlock(&wg->device_update_lock); 28462306a36Sopenharmony_ci rtnl_unlock(); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (ret) { 28762306a36Sopenharmony_ci genlmsg_cancel(skb, hdr); 28862306a36Sopenharmony_ci return ret; 28962306a36Sopenharmony_ci } 29062306a36Sopenharmony_ci genlmsg_end(skb, hdr); 29162306a36Sopenharmony_ci if (done) { 29262306a36Sopenharmony_ci ctx->next_peer = NULL; 29362306a36Sopenharmony_ci return 0; 29462306a36Sopenharmony_ci } 29562306a36Sopenharmony_ci ctx->next_peer = next_peer_cursor; 29662306a36Sopenharmony_ci return skb->len; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci /* At this point, we can't really deal ourselves with safely zeroing out 29962306a36Sopenharmony_ci * the private key material after usage. This will need an additional API 30062306a36Sopenharmony_ci * in the kernel for marking skbs as zero_on_free. 30162306a36Sopenharmony_ci */ 30262306a36Sopenharmony_ci} 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_cistatic int wg_get_device_done(struct netlink_callback *cb) 30562306a36Sopenharmony_ci{ 30662306a36Sopenharmony_ci struct dump_ctx *ctx = DUMP_CTX(cb); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci if (ctx->wg) 30962306a36Sopenharmony_ci dev_put(ctx->wg->dev); 31062306a36Sopenharmony_ci wg_peer_put(ctx->next_peer); 31162306a36Sopenharmony_ci return 0; 31262306a36Sopenharmony_ci} 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_cistatic int set_port(struct wg_device *wg, u16 port) 31562306a36Sopenharmony_ci{ 31662306a36Sopenharmony_ci struct wg_peer *peer; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci if (wg->incoming_port == port) 31962306a36Sopenharmony_ci return 0; 32062306a36Sopenharmony_ci list_for_each_entry(peer, &wg->peer_list, peer_list) 32162306a36Sopenharmony_ci wg_socket_clear_peer_endpoint_src(peer); 32262306a36Sopenharmony_ci if (!netif_running(wg->dev)) { 32362306a36Sopenharmony_ci wg->incoming_port = port; 32462306a36Sopenharmony_ci return 0; 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci return wg_socket_init(wg, port); 32762306a36Sopenharmony_ci} 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_cistatic int set_allowedip(struct wg_peer *peer, struct nlattr **attrs) 33062306a36Sopenharmony_ci{ 33162306a36Sopenharmony_ci int ret = -EINVAL; 33262306a36Sopenharmony_ci u16 family; 33362306a36Sopenharmony_ci u8 cidr; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci if (!attrs[WGALLOWEDIP_A_FAMILY] || !attrs[WGALLOWEDIP_A_IPADDR] || 33662306a36Sopenharmony_ci !attrs[WGALLOWEDIP_A_CIDR_MASK]) 33762306a36Sopenharmony_ci return ret; 33862306a36Sopenharmony_ci family = nla_get_u16(attrs[WGALLOWEDIP_A_FAMILY]); 33962306a36Sopenharmony_ci cidr = nla_get_u8(attrs[WGALLOWEDIP_A_CIDR_MASK]); 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci if (family == AF_INET && cidr <= 32 && 34262306a36Sopenharmony_ci nla_len(attrs[WGALLOWEDIP_A_IPADDR]) == sizeof(struct in_addr)) 34362306a36Sopenharmony_ci ret = wg_allowedips_insert_v4( 34462306a36Sopenharmony_ci &peer->device->peer_allowedips, 34562306a36Sopenharmony_ci nla_data(attrs[WGALLOWEDIP_A_IPADDR]), cidr, peer, 34662306a36Sopenharmony_ci &peer->device->device_update_lock); 34762306a36Sopenharmony_ci else if (family == AF_INET6 && cidr <= 128 && 34862306a36Sopenharmony_ci nla_len(attrs[WGALLOWEDIP_A_IPADDR]) == sizeof(struct in6_addr)) 34962306a36Sopenharmony_ci ret = wg_allowedips_insert_v6( 35062306a36Sopenharmony_ci &peer->device->peer_allowedips, 35162306a36Sopenharmony_ci nla_data(attrs[WGALLOWEDIP_A_IPADDR]), cidr, peer, 35262306a36Sopenharmony_ci &peer->device->device_update_lock); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci return ret; 35562306a36Sopenharmony_ci} 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_cistatic int set_peer(struct wg_device *wg, struct nlattr **attrs) 35862306a36Sopenharmony_ci{ 35962306a36Sopenharmony_ci u8 *public_key = NULL, *preshared_key = NULL; 36062306a36Sopenharmony_ci struct wg_peer *peer = NULL; 36162306a36Sopenharmony_ci u32 flags = 0; 36262306a36Sopenharmony_ci int ret; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci ret = -EINVAL; 36562306a36Sopenharmony_ci if (attrs[WGPEER_A_PUBLIC_KEY] && 36662306a36Sopenharmony_ci nla_len(attrs[WGPEER_A_PUBLIC_KEY]) == NOISE_PUBLIC_KEY_LEN) 36762306a36Sopenharmony_ci public_key = nla_data(attrs[WGPEER_A_PUBLIC_KEY]); 36862306a36Sopenharmony_ci else 36962306a36Sopenharmony_ci goto out; 37062306a36Sopenharmony_ci if (attrs[WGPEER_A_PRESHARED_KEY] && 37162306a36Sopenharmony_ci nla_len(attrs[WGPEER_A_PRESHARED_KEY]) == NOISE_SYMMETRIC_KEY_LEN) 37262306a36Sopenharmony_ci preshared_key = nla_data(attrs[WGPEER_A_PRESHARED_KEY]); 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci if (attrs[WGPEER_A_FLAGS]) 37562306a36Sopenharmony_ci flags = nla_get_u32(attrs[WGPEER_A_FLAGS]); 37662306a36Sopenharmony_ci ret = -EOPNOTSUPP; 37762306a36Sopenharmony_ci if (flags & ~__WGPEER_F_ALL) 37862306a36Sopenharmony_ci goto out; 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci ret = -EPFNOSUPPORT; 38162306a36Sopenharmony_ci if (attrs[WGPEER_A_PROTOCOL_VERSION]) { 38262306a36Sopenharmony_ci if (nla_get_u32(attrs[WGPEER_A_PROTOCOL_VERSION]) != 1) 38362306a36Sopenharmony_ci goto out; 38462306a36Sopenharmony_ci } 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci peer = wg_pubkey_hashtable_lookup(wg->peer_hashtable, 38762306a36Sopenharmony_ci nla_data(attrs[WGPEER_A_PUBLIC_KEY])); 38862306a36Sopenharmony_ci ret = 0; 38962306a36Sopenharmony_ci if (!peer) { /* Peer doesn't exist yet. Add a new one. */ 39062306a36Sopenharmony_ci if (flags & (WGPEER_F_REMOVE_ME | WGPEER_F_UPDATE_ONLY)) 39162306a36Sopenharmony_ci goto out; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci /* The peer is new, so there aren't allowed IPs to remove. */ 39462306a36Sopenharmony_ci flags &= ~WGPEER_F_REPLACE_ALLOWEDIPS; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci down_read(&wg->static_identity.lock); 39762306a36Sopenharmony_ci if (wg->static_identity.has_identity && 39862306a36Sopenharmony_ci !memcmp(nla_data(attrs[WGPEER_A_PUBLIC_KEY]), 39962306a36Sopenharmony_ci wg->static_identity.static_public, 40062306a36Sopenharmony_ci NOISE_PUBLIC_KEY_LEN)) { 40162306a36Sopenharmony_ci /* We silently ignore peers that have the same public 40262306a36Sopenharmony_ci * key as the device. The reason we do it silently is 40362306a36Sopenharmony_ci * that we'd like for people to be able to reuse the 40462306a36Sopenharmony_ci * same set of API calls across peers. 40562306a36Sopenharmony_ci */ 40662306a36Sopenharmony_ci up_read(&wg->static_identity.lock); 40762306a36Sopenharmony_ci ret = 0; 40862306a36Sopenharmony_ci goto out; 40962306a36Sopenharmony_ci } 41062306a36Sopenharmony_ci up_read(&wg->static_identity.lock); 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci peer = wg_peer_create(wg, public_key, preshared_key); 41362306a36Sopenharmony_ci if (IS_ERR(peer)) { 41462306a36Sopenharmony_ci ret = PTR_ERR(peer); 41562306a36Sopenharmony_ci peer = NULL; 41662306a36Sopenharmony_ci goto out; 41762306a36Sopenharmony_ci } 41862306a36Sopenharmony_ci /* Take additional reference, as though we've just been 41962306a36Sopenharmony_ci * looked up. 42062306a36Sopenharmony_ci */ 42162306a36Sopenharmony_ci wg_peer_get(peer); 42262306a36Sopenharmony_ci } 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci if (flags & WGPEER_F_REMOVE_ME) { 42562306a36Sopenharmony_ci wg_peer_remove(peer); 42662306a36Sopenharmony_ci goto out; 42762306a36Sopenharmony_ci } 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci if (preshared_key) { 43062306a36Sopenharmony_ci down_write(&peer->handshake.lock); 43162306a36Sopenharmony_ci memcpy(&peer->handshake.preshared_key, preshared_key, 43262306a36Sopenharmony_ci NOISE_SYMMETRIC_KEY_LEN); 43362306a36Sopenharmony_ci up_write(&peer->handshake.lock); 43462306a36Sopenharmony_ci } 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci if (attrs[WGPEER_A_ENDPOINT]) { 43762306a36Sopenharmony_ci struct sockaddr *addr = nla_data(attrs[WGPEER_A_ENDPOINT]); 43862306a36Sopenharmony_ci size_t len = nla_len(attrs[WGPEER_A_ENDPOINT]); 43962306a36Sopenharmony_ci struct endpoint endpoint = { { { 0 } } }; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci if (len == sizeof(struct sockaddr_in) && addr->sa_family == AF_INET) { 44262306a36Sopenharmony_ci endpoint.addr4 = *(struct sockaddr_in *)addr; 44362306a36Sopenharmony_ci wg_socket_set_peer_endpoint(peer, &endpoint); 44462306a36Sopenharmony_ci } else if (len == sizeof(struct sockaddr_in6) && addr->sa_family == AF_INET6) { 44562306a36Sopenharmony_ci endpoint.addr6 = *(struct sockaddr_in6 *)addr; 44662306a36Sopenharmony_ci wg_socket_set_peer_endpoint(peer, &endpoint); 44762306a36Sopenharmony_ci } 44862306a36Sopenharmony_ci } 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_ci if (flags & WGPEER_F_REPLACE_ALLOWEDIPS) 45162306a36Sopenharmony_ci wg_allowedips_remove_by_peer(&wg->peer_allowedips, peer, 45262306a36Sopenharmony_ci &wg->device_update_lock); 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci if (attrs[WGPEER_A_ALLOWEDIPS]) { 45562306a36Sopenharmony_ci struct nlattr *attr, *allowedip[WGALLOWEDIP_A_MAX + 1]; 45662306a36Sopenharmony_ci int rem; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci nla_for_each_nested(attr, attrs[WGPEER_A_ALLOWEDIPS], rem) { 45962306a36Sopenharmony_ci ret = nla_parse_nested(allowedip, WGALLOWEDIP_A_MAX, 46062306a36Sopenharmony_ci attr, allowedip_policy, NULL); 46162306a36Sopenharmony_ci if (ret < 0) 46262306a36Sopenharmony_ci goto out; 46362306a36Sopenharmony_ci ret = set_allowedip(peer, allowedip); 46462306a36Sopenharmony_ci if (ret < 0) 46562306a36Sopenharmony_ci goto out; 46662306a36Sopenharmony_ci } 46762306a36Sopenharmony_ci } 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci if (attrs[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]) { 47062306a36Sopenharmony_ci const u16 persistent_keepalive_interval = nla_get_u16( 47162306a36Sopenharmony_ci attrs[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]); 47262306a36Sopenharmony_ci const bool send_keepalive = 47362306a36Sopenharmony_ci !peer->persistent_keepalive_interval && 47462306a36Sopenharmony_ci persistent_keepalive_interval && 47562306a36Sopenharmony_ci netif_running(wg->dev); 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci peer->persistent_keepalive_interval = persistent_keepalive_interval; 47862306a36Sopenharmony_ci if (send_keepalive) 47962306a36Sopenharmony_ci wg_packet_send_keepalive(peer); 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci if (netif_running(wg->dev)) 48362306a36Sopenharmony_ci wg_packet_send_staged_packets(peer); 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ciout: 48662306a36Sopenharmony_ci wg_peer_put(peer); 48762306a36Sopenharmony_ci if (attrs[WGPEER_A_PRESHARED_KEY]) 48862306a36Sopenharmony_ci memzero_explicit(nla_data(attrs[WGPEER_A_PRESHARED_KEY]), 48962306a36Sopenharmony_ci nla_len(attrs[WGPEER_A_PRESHARED_KEY])); 49062306a36Sopenharmony_ci return ret; 49162306a36Sopenharmony_ci} 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_cistatic int wg_set_device(struct sk_buff *skb, struct genl_info *info) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci struct wg_device *wg = lookup_interface(info->attrs, skb); 49662306a36Sopenharmony_ci u32 flags = 0; 49762306a36Sopenharmony_ci int ret; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci if (IS_ERR(wg)) { 50062306a36Sopenharmony_ci ret = PTR_ERR(wg); 50162306a36Sopenharmony_ci goto out_nodev; 50262306a36Sopenharmony_ci } 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci rtnl_lock(); 50562306a36Sopenharmony_ci mutex_lock(&wg->device_update_lock); 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci if (info->attrs[WGDEVICE_A_FLAGS]) 50862306a36Sopenharmony_ci flags = nla_get_u32(info->attrs[WGDEVICE_A_FLAGS]); 50962306a36Sopenharmony_ci ret = -EOPNOTSUPP; 51062306a36Sopenharmony_ci if (flags & ~__WGDEVICE_F_ALL) 51162306a36Sopenharmony_ci goto out; 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci if (info->attrs[WGDEVICE_A_LISTEN_PORT] || info->attrs[WGDEVICE_A_FWMARK]) { 51462306a36Sopenharmony_ci struct net *net; 51562306a36Sopenharmony_ci rcu_read_lock(); 51662306a36Sopenharmony_ci net = rcu_dereference(wg->creating_net); 51762306a36Sopenharmony_ci ret = !net || !ns_capable(net->user_ns, CAP_NET_ADMIN) ? -EPERM : 0; 51862306a36Sopenharmony_ci rcu_read_unlock(); 51962306a36Sopenharmony_ci if (ret) 52062306a36Sopenharmony_ci goto out; 52162306a36Sopenharmony_ci } 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci ++wg->device_update_gen; 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci if (info->attrs[WGDEVICE_A_FWMARK]) { 52662306a36Sopenharmony_ci struct wg_peer *peer; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci wg->fwmark = nla_get_u32(info->attrs[WGDEVICE_A_FWMARK]); 52962306a36Sopenharmony_ci list_for_each_entry(peer, &wg->peer_list, peer_list) 53062306a36Sopenharmony_ci wg_socket_clear_peer_endpoint_src(peer); 53162306a36Sopenharmony_ci } 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci if (info->attrs[WGDEVICE_A_LISTEN_PORT]) { 53462306a36Sopenharmony_ci ret = set_port(wg, 53562306a36Sopenharmony_ci nla_get_u16(info->attrs[WGDEVICE_A_LISTEN_PORT])); 53662306a36Sopenharmony_ci if (ret) 53762306a36Sopenharmony_ci goto out; 53862306a36Sopenharmony_ci } 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci if (flags & WGDEVICE_F_REPLACE_PEERS) 54162306a36Sopenharmony_ci wg_peer_remove_all(wg); 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci if (info->attrs[WGDEVICE_A_PRIVATE_KEY] && 54462306a36Sopenharmony_ci nla_len(info->attrs[WGDEVICE_A_PRIVATE_KEY]) == 54562306a36Sopenharmony_ci NOISE_PUBLIC_KEY_LEN) { 54662306a36Sopenharmony_ci u8 *private_key = nla_data(info->attrs[WGDEVICE_A_PRIVATE_KEY]); 54762306a36Sopenharmony_ci u8 public_key[NOISE_PUBLIC_KEY_LEN]; 54862306a36Sopenharmony_ci struct wg_peer *peer, *temp; 54962306a36Sopenharmony_ci bool send_staged_packets; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci if (!crypto_memneq(wg->static_identity.static_private, 55262306a36Sopenharmony_ci private_key, NOISE_PUBLIC_KEY_LEN)) 55362306a36Sopenharmony_ci goto skip_set_private_key; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci /* We remove before setting, to prevent race, which means doing 55662306a36Sopenharmony_ci * two 25519-genpub ops. 55762306a36Sopenharmony_ci */ 55862306a36Sopenharmony_ci if (curve25519_generate_public(public_key, private_key)) { 55962306a36Sopenharmony_ci peer = wg_pubkey_hashtable_lookup(wg->peer_hashtable, 56062306a36Sopenharmony_ci public_key); 56162306a36Sopenharmony_ci if (peer) { 56262306a36Sopenharmony_ci wg_peer_put(peer); 56362306a36Sopenharmony_ci wg_peer_remove(peer); 56462306a36Sopenharmony_ci } 56562306a36Sopenharmony_ci } 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci down_write(&wg->static_identity.lock); 56862306a36Sopenharmony_ci send_staged_packets = !wg->static_identity.has_identity && netif_running(wg->dev); 56962306a36Sopenharmony_ci wg_noise_set_static_identity_private_key(&wg->static_identity, private_key); 57062306a36Sopenharmony_ci send_staged_packets = send_staged_packets && wg->static_identity.has_identity; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci wg_cookie_checker_precompute_device_keys(&wg->cookie_checker); 57362306a36Sopenharmony_ci list_for_each_entry_safe(peer, temp, &wg->peer_list, peer_list) { 57462306a36Sopenharmony_ci wg_noise_precompute_static_static(peer); 57562306a36Sopenharmony_ci wg_noise_expire_current_peer_keypairs(peer); 57662306a36Sopenharmony_ci if (send_staged_packets) 57762306a36Sopenharmony_ci wg_packet_send_staged_packets(peer); 57862306a36Sopenharmony_ci } 57962306a36Sopenharmony_ci up_write(&wg->static_identity.lock); 58062306a36Sopenharmony_ci } 58162306a36Sopenharmony_ciskip_set_private_key: 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci if (info->attrs[WGDEVICE_A_PEERS]) { 58462306a36Sopenharmony_ci struct nlattr *attr, *peer[WGPEER_A_MAX + 1]; 58562306a36Sopenharmony_ci int rem; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci nla_for_each_nested(attr, info->attrs[WGDEVICE_A_PEERS], rem) { 58862306a36Sopenharmony_ci ret = nla_parse_nested(peer, WGPEER_A_MAX, attr, 58962306a36Sopenharmony_ci peer_policy, NULL); 59062306a36Sopenharmony_ci if (ret < 0) 59162306a36Sopenharmony_ci goto out; 59262306a36Sopenharmony_ci ret = set_peer(wg, peer); 59362306a36Sopenharmony_ci if (ret < 0) 59462306a36Sopenharmony_ci goto out; 59562306a36Sopenharmony_ci } 59662306a36Sopenharmony_ci } 59762306a36Sopenharmony_ci ret = 0; 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ciout: 60062306a36Sopenharmony_ci mutex_unlock(&wg->device_update_lock); 60162306a36Sopenharmony_ci rtnl_unlock(); 60262306a36Sopenharmony_ci dev_put(wg->dev); 60362306a36Sopenharmony_ciout_nodev: 60462306a36Sopenharmony_ci if (info->attrs[WGDEVICE_A_PRIVATE_KEY]) 60562306a36Sopenharmony_ci memzero_explicit(nla_data(info->attrs[WGDEVICE_A_PRIVATE_KEY]), 60662306a36Sopenharmony_ci nla_len(info->attrs[WGDEVICE_A_PRIVATE_KEY])); 60762306a36Sopenharmony_ci return ret; 60862306a36Sopenharmony_ci} 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_cistatic const struct genl_ops genl_ops[] = { 61162306a36Sopenharmony_ci { 61262306a36Sopenharmony_ci .cmd = WG_CMD_GET_DEVICE, 61362306a36Sopenharmony_ci .start = wg_get_device_start, 61462306a36Sopenharmony_ci .dumpit = wg_get_device_dump, 61562306a36Sopenharmony_ci .done = wg_get_device_done, 61662306a36Sopenharmony_ci .flags = GENL_UNS_ADMIN_PERM 61762306a36Sopenharmony_ci }, { 61862306a36Sopenharmony_ci .cmd = WG_CMD_SET_DEVICE, 61962306a36Sopenharmony_ci .doit = wg_set_device, 62062306a36Sopenharmony_ci .flags = GENL_UNS_ADMIN_PERM 62162306a36Sopenharmony_ci } 62262306a36Sopenharmony_ci}; 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_cistatic struct genl_family genl_family __ro_after_init = { 62562306a36Sopenharmony_ci .ops = genl_ops, 62662306a36Sopenharmony_ci .n_ops = ARRAY_SIZE(genl_ops), 62762306a36Sopenharmony_ci .resv_start_op = WG_CMD_SET_DEVICE + 1, 62862306a36Sopenharmony_ci .name = WG_GENL_NAME, 62962306a36Sopenharmony_ci .version = WG_GENL_VERSION, 63062306a36Sopenharmony_ci .maxattr = WGDEVICE_A_MAX, 63162306a36Sopenharmony_ci .module = THIS_MODULE, 63262306a36Sopenharmony_ci .policy = device_policy, 63362306a36Sopenharmony_ci .netnsok = true 63462306a36Sopenharmony_ci}; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ciint __init wg_genetlink_init(void) 63762306a36Sopenharmony_ci{ 63862306a36Sopenharmony_ci return genl_register_family(&genl_family); 63962306a36Sopenharmony_ci} 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_civoid __exit wg_genetlink_uninit(void) 64262306a36Sopenharmony_ci{ 64362306a36Sopenharmony_ci genl_unregister_family(&genl_family); 64462306a36Sopenharmony_ci} 645