diff options
author | David Gwynne <dlg@cvs.openbsd.org> | 2020-06-21 12:20:07 +0000 |
---|---|---|
committer | David Gwynne <dlg@cvs.openbsd.org> | 2020-06-21 12:20:07 +0000 |
commit | e1fcb3744000c7900541b0fe6cd687b13a7fb659 (patch) | |
tree | 23a3d2f3b8da6ed8b0649afcd0f996773b197572 /sbin/ifconfig | |
parent | f49e326d56e513622ca708f5487f1660236fbd8c (diff) |
teach ifconfig about wireguard.
note that this links ifconfig with libcrypto to get at base64
encoding and decoding routines. im looking at an alternative way
to do that, so hopefully this is temporary.
secondly, note that all the wireguard stuff is under ifndef SMALL,
so the special build of ifconfig for install media does include
wireguard support, and also does not need libcrypto.
from Matt Dunwoodie and Jason A. Donenfeld
ok deraadt@
Diffstat (limited to 'sbin/ifconfig')
-rw-r--r-- | sbin/ifconfig/Makefile | 4 | ||||
-rw-r--r-- | sbin/ifconfig/ifconfig.8 | 130 | ||||
-rw-r--r-- | sbin/ifconfig/ifconfig.c | 324 |
3 files changed, 452 insertions, 6 deletions
diff --git a/sbin/ifconfig/Makefile b/sbin/ifconfig/Makefile index b49efcda9c8..195f8cbc3f4 100644 --- a/sbin/ifconfig/Makefile +++ b/sbin/ifconfig/Makefile @@ -1,10 +1,10 @@ -# $OpenBSD: Makefile,v 1.15 2019/04/10 10:14:37 dlg Exp $ +# $OpenBSD: Makefile,v 1.16 2020/06/21 12:20:06 dlg Exp $ PROG= ifconfig SRCS= ifconfig.c brconfig.c sff.c MAN= ifconfig.8 -LDADD= -lutil -lm +LDADD= -lutil -lm -lcrypto DPADD= ${LIBUTIL} .include <bsd.prog.mk> diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8 index 8a5025f8bdf..5c11f6a698f 100644 --- a/sbin/ifconfig/ifconfig.8 +++ b/sbin/ifconfig/ifconfig.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ifconfig.8,v 1.346 2020/04/29 13:13:29 stsp Exp $ +.\" $OpenBSD: ifconfig.8,v 1.347 2020/06/21 12:20:06 dlg Exp $ .\" $NetBSD: ifconfig.8,v 1.11 1996/01/04 21:27:29 pk Exp $ .\" $FreeBSD: ifconfig.8,v 1.16 1998/02/01 07:03:29 steve Exp $ .\" @@ -31,7 +31,7 @@ .\" .\" @(#)ifconfig.8 8.4 (Berkeley) 6/1/94 .\" -.Dd $Mdocdate: April 29 2020 $ +.Dd $Mdocdate: June 21 2020 $ .Dt IFCONFIG 8 .Os .Sh NAME @@ -207,7 +207,8 @@ At least the following devices can be created on demand: .Xr tun 4 , .Xr vether 4 , .Xr vlan 4 , -.Xr vxlan 4 +.Xr vxlan 4 , +.Xr wg 4 .It Cm debug Enable driver-dependent debugging code; usually, this turns on extra console error logging. @@ -2042,6 +2043,129 @@ Clear the tag value. Packets on a VLAN interface without a tag set will use a value of 0 in their headers. .El +.Sh WIREGUARD +.nr nS 1 +.Bk -words +.Nm ifconfig +.Ar wg-interface +.Op Cm wgkey Ar privatekey +.Op Cm wgport Ar port +.Op Cm wgrtable Ar rtable +.Oo +.Oo Fl Oc Ns Cm wgpeer Ar publickey +.Op Cm wgpsk Ar presharedkey +.Op Fl wgpsk +.Op Cm wgpka Ar persistent-keepalive +.Op Cm wgpip Ar ip port +.Op Cm wgaip Ar allowed-ip/prefix +.Oc +.Op Fl wgpeerall +.Ek +.nr nS 0 +.Pp +The following options are available for +.Xr wg 4 +interfaces: +.Bl -tag -width Ds +.It Cm wgkey Ar privatekey +Set the local private key of the interface to +.Ar privatekey . +This is a random 32 byte value, encoded as base64. +It may be generated as follows: +.Pp +.Dl $ openssl rand -base64 32 +.Pp +A valid Curve25519 key is required to have 5 bits set to specific +values. +This is done by the interface, so it is safe to provide a random +32 byte base64 string. +.Pp +Once set, the corresponding public key will be displayed +in the interface status; it must be distributed to peers +that this interface intends to communicate with. +.It Cm wgport Ar port +Set the UDP +.Ar port +that the tunnel operates on. +The interface will bind to +.Dv INADDR_ANY +and +.Dv IN6ADDR_ANY_INIT . +If no port is configured, one will be chosen automatically. +.It Cm wgrtable Ar rtable +Use routing table +.Ar rtable +instead of the default table for the tunnel. +The tunnel does not need to terminate in the same routing domain as the +interface itself. +.Ar rtable +can be set to any valid routing table ID; the corresponding routing +domain is derived from this table. +.It Cm wgpeer Ar publickey +Select the peer to perform the subsequent operations on. +This creates a peer with the associated 32 byte, base64 encoded +.Ar publickey +if it does not yet exist. +This option can be specified multiple times in a single command. +.It Cm -wgpeer Ar publickey +Remove the peer with the associated +.Ar publickey . +.It Cm -wgpeerall +Remove all peers from the interface. +.El +.Pp +The following options configure peers for the interface. +Each interface can have multiple peers. +In order to add a peer, a +.Cm wgpeer +option must be specified, followed by its configuration options. +.Bl -tag -width Ds +.It Cm wgpsk Ar presharedkey +Set the preshared key for the peer. +This is a random 32 byte, base64 encoded string +that both ends must agree on. +It offers a post-quantum resistance to the Diffie-Hellman exchange. +If there is no preshared key, the exact same handshake is performed, +however the preshared key is set to all zero. +This can be generated in the same way as +.Ar privatekey . +.It Cm -wgpsk +Remove the preshared key from the specified peer. +.It Cm wgpka Ar persistent-keepalive +Set the interval of additional keepalive packets in seconds. +By default this functionality is disabled, equivalent to a value of 0. +This is often used to ensure a peer will be accessible when protected by +a firewall, as is when behind a NAT address. +A value of 25 is commonly used. +.It Cm wgpip Ar ip port +Set the IP address and port to send the encapsulated packets to. +If the peer changes address, the local interface will update the address +after receiving a correctly authenticated packet. +The IP address can be either +IPv4 or IPv6, and the port is a regular 16 bit UDP port. +.It Cm wgaip Ar allowed-ip/prefix +Set the allowed IPs for the peer. +The allowed IPs indicate the IP addresses a peer is allowed to send +from. +That is, in order for an incoming packet from a peer to reach the host, +the decryped IP source address must be in the peer's +.Ar allowed-ip +ranges. +.Pp +The +.Ar allowed-ip +list also provides an outgoing routing table for outgoing packets. +Overlapping ranges can be configured, with packets being +directed to the most specific route. +Likewise, packets can only be received for the most specific route. +.Pp +Both IPv4 and IPv6 addresses are supported. +To set multiple allowed IPs, specify the +.Cm wgaip +option multiple times in the same +.Nm +invocation. +.El .Sh EXAMPLES Assign the address of 192.168.1.10 with a network mask of diff --git a/sbin/ifconfig/ifconfig.c b/sbin/ifconfig/ifconfig.c index aefa23d157a..eaa76c693cf 100644 --- a/sbin/ifconfig/ifconfig.c +++ b/sbin/ifconfig/ifconfig.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ifconfig.c,v 1.421 2020/02/27 08:28:35 stsp Exp $ */ +/* $OpenBSD: ifconfig.c,v 1.422 2020/06/21 12:20:06 dlg Exp $ */ /* $NetBSD: ifconfig.c,v 1.40 1997/10/01 02:19:43 enami Exp $ */ /* @@ -82,6 +82,7 @@ #include <net/if_pflow.h> #include <net/if_pppoe.h> #include <net/if_trunk.h> +#include <net/if_wg.h> #include <net/trunklacp.h> #include <net/if_sppp.h> #include <net/ppp_defs.h> @@ -93,6 +94,7 @@ #include <net/if_vlan_var.h> #include <netmpls/mpls.h> +#include <openssl/evp.h> #include <ctype.h> #include <err.h> @@ -100,6 +102,7 @@ #include <stdio.h> #include <stdint.h> #include <stdlib.h> +#include <stddef.h> #include <string.h> #include <unistd.h> #include <limits.h> @@ -345,6 +348,22 @@ void utf16_to_char(uint16_t *, int, char *, size_t); int char_to_utf16(const char *, uint16_t *, size_t); void transceiver(const char *, int); void transceiverdump(const char *, int); + +/* WG */ +void setwgpeer(const char *, int); +void setwgpeerep(const char *, const char *); +void setwgpeeraip(const char *, int); +void setwgpeerpsk(const char *, int); +void setwgpeerpka(const char *, int); +void setwgport(const char *, int); +void setwgkey(const char *, int); +void setwgrtable(const char *, int); + +void unsetwgpeer(const char *, int); +void unsetwgpeerpsk(const char *, int); +void unsetwgpeerall(const char *, int); + +void wg_status(); #else void setignore(const char *, int); #endif @@ -369,6 +388,7 @@ int actions; /* Actions performed */ #define A_MEDIAINST 0x0008 /* instance or inst command */ #define A_MEDIAMODE 0x0010 /* mode command */ #define A_JOIN 0x0020 /* join */ +#define A_WIREGUARD 0x0040 /* any WireGuard command */ #define A_SILENT 0x8000000 /* doing operation, do not print */ #define NEXTARG0 0xffffff @@ -599,6 +619,19 @@ const struct cmd { { "transceiver", NEXTARG0, 0, transceiver }, { "sff", NEXTARG0, 0, transceiver }, { "sffdump", 0, 0, transceiverdump }, + + { "wgpeer", NEXTARG, A_WIREGUARD, setwgpeer}, + { "wgendpoint", NEXTARG2, A_WIREGUARD, NULL, setwgpeerep}, + { "wgaip", NEXTARG, A_WIREGUARD, setwgpeeraip}, + { "wgpsk", NEXTARG, A_WIREGUARD, setwgpeerpsk}, + { "wgpka", NEXTARG, A_WIREGUARD, setwgpeerpka}, + { "wgport", NEXTARG, A_WIREGUARD, setwgport}, + { "wgkey", NEXTARG, A_WIREGUARD, setwgkey}, + { "wgrtable", NEXTARG, A_WIREGUARD, setwgrtable}, + { "-wgpeer", NEXTARG, A_WIREGUARD, unsetwgpeer}, + { "-wgpsk", 0, A_WIREGUARD, unsetwgpeerpsk}, + { "-wgpeerall", 0, A_WIREGUARD, unsetwgpeerall}, + #else /* SMALL */ { "powersave", NEXTARG0, 0, setignore }, { "priority", NEXTARG, 0, setignore }, @@ -671,6 +704,8 @@ void init_current_media(void); void process_join_commands(void); +void process_wg_commands(void); + unsigned long get_ts_map(int, int, int); void in_status(int); @@ -913,6 +948,10 @@ nextarg: return (0); } +#ifndef SMALL + process_wg_commands(); +#endif + process_join_commands(); /* Process any media commands that may have been issued. */ @@ -3341,6 +3380,7 @@ status(int link, struct sockaddr_dl *sdl, int ls) mpls_status(); pflow_status(); umb_status(); + wg_status(); #endif trunk_status(); getifgroups(); @@ -5623,6 +5663,288 @@ setifpriority(const char *id, int param) warn("SIOCSIFPRIORITY"); } +/* + * WireGuard configuration + * + * WG_BASE64_KEY_LEN specifies the size of a base64 encoded WireGuard key. + * WG_TMP_KEY_LEN specifies the size of a decoded base64 key. For every 4 + * input (base64) bytes, 3 output bytes wil be produced. The output will be + * padded with 0 bits, therefore we need more than the regular 32 bytes of + * space. + */ +#define WG_BASE64_KEY_LEN (4 * ((WG_KEY_LEN + 2) / 3)) +#define WG_TMP_KEY_LEN (WG_BASE64_KEY_LEN / 4 * 3) +#define WG_LOAD_KEY(dst, src, fn_name) do { \ + uint8_t _tmp[WG_TMP_KEY_LEN]; \ + if (strlen(src) != WG_BASE64_KEY_LEN) \ + errx(1, fn_name " (key): invalid length"); \ + if (EVP_DecodeBlock(_tmp, src, \ + WG_BASE64_KEY_LEN) != WG_TMP_KEY_LEN) \ + errx(1, fn_name " (key): invalid base64"); \ + memcpy(dst, _tmp, WG_KEY_LEN); \ +} while (0) + +struct wg_data_io wgdata = { 0 }; +struct wg_interface_io *wg_interface = NULL; +struct wg_peer_io *wg_peer = NULL; +struct wg_aip_io *wg_aip = NULL; + +void +ensurewginterface(void) +{ + if (wg_interface != NULL) + return; + wgdata.wgd_size = sizeof(*wg_interface); + wgdata.wgd_interface = wg_interface = calloc(1, wgdata.wgd_size); + if (wg_interface == NULL) + err(1, "calloc"); +} + +void * +growwgdata(size_t by) +{ + ptrdiff_t peer_offset, aip_offset; + void *ret; + + if (wg_interface == NULL) + wgdata.wgd_size = sizeof(*wg_interface); + + peer_offset = (void *)wg_peer - (void *)wg_interface; + aip_offset = (void *)wg_aip - (void *)wg_interface; + + wgdata.wgd_size += by; + wgdata.wgd_interface = realloc(wg_interface, wgdata.wgd_size); + if (wgdata.wgd_interface == NULL) + err(1, "calloc"); + if (wg_interface == NULL) + bzero(wgdata.wgd_interface, sizeof(*wg_interface)); + wg_interface = wgdata.wgd_interface; + + if (wg_peer != NULL) + wg_peer = (void *)wg_interface + peer_offset; + if (wg_aip != NULL) + wg_aip = (void *)wg_interface + aip_offset; + + ret = (void *)wg_interface + wgdata.wgd_size - by; + bzero(ret, by); + + return ret; +} + +void +setwgpeer(const char *peerkey_b64, int param) +{ + wg_peer = growwgdata(sizeof(*wg_peer)); + wg_peer->p_flags |= WG_PEER_HAS_PUBLIC; + WG_LOAD_KEY(wg_peer->p_public, peerkey_b64, "wgpeer"); + wg_interface->i_peers_count++; +} + +void +setwgpeeraip(const char *aip, int param) +{ + int res; + if (wg_peer == NULL) + errx(1, "wgaip: wgpeer not set"); + + wg_aip = growwgdata(sizeof(*wg_aip)); + + if ((res = inet_net_pton(AF_INET, aip, &wg_aip->a_ipv4, + sizeof(wg_aip->a_ipv4))) != -1) { + wg_aip->a_af = AF_INET; + } else if ((res = inet_net_pton(AF_INET6, aip, &wg_aip->a_ipv6, + sizeof(wg_aip->a_ipv6))) != -1) { + wg_aip->a_af = AF_INET6; + } else { + errx(1, "wgaip: bad address"); + } + + wg_aip->a_cidr = res; + + wg_peer->p_flags |= WG_PEER_REPLACE_AIPS; + wg_peer->p_aips_count++; +} + +void +setwgpeerep(const char *host, const char *service) +{ + int error; + struct addrinfo *ai; + + if (wg_peer == NULL) + errx(1, "wgendpoint: wgpeer not set"); + + if ((error = getaddrinfo(host, service, NULL, &ai)) != 0) + errx(1, "%s", gai_strerror(error)); + + wg_peer->p_flags |= WG_PEER_HAS_ENDPOINT; + memcpy(&wg_peer->p_sa, ai->ai_addr, ai->ai_addrlen); + freeaddrinfo(ai); +} + +void +setwgpeerpsk(const char *psk_b64, int param) +{ + if (wg_peer == NULL) + errx(1, "wgpsk: wgpeer not set"); + wg_peer->p_flags |= WG_PEER_HAS_PSK; + WG_LOAD_KEY(wg_peer->p_psk, psk_b64, "wgpsk"); +} + +void +setwgpeerpka(const char *pka, int param) +{ + const char *errmsg = NULL; + if (wg_peer == NULL) + errx(1, "wgpka: wgpeer not set"); + /* 43200 == 12h, reasonable for a 16 bit value */ + wg_peer->p_flags |= WG_PEER_HAS_PKA; + wg_peer->p_pka = strtonum(pka, 0, 43200, &errmsg); + if (errmsg) + errx(1, "wgpka: %s, %s", pka, errmsg); +} + +void +setwgport(const char *port, int param) +{ + const char *errmsg = NULL; + ensurewginterface(); + wg_interface->i_flags |= WG_INTERFACE_HAS_PORT; + wg_interface->i_port = strtonum(port, 0, 65535, &errmsg); + if (errmsg) + errx(1, "wgport: %s, %s", port, errmsg); +} + +void +setwgkey(const char *private_b64, int param) +{ + ensurewginterface(); + wg_interface->i_flags |= WG_INTERFACE_HAS_PRIVATE; + WG_LOAD_KEY(wg_interface->i_private, private_b64, "wgkey"); +} + +void +setwgrtable(const char *id, int param) +{ + const char *errmsg = NULL; + ensurewginterface(); + wg_interface->i_flags |= WG_INTERFACE_HAS_RTABLE; + wg_interface->i_rtable = strtonum(id, 0, RT_TABLEID_MAX, &errmsg); + if (errmsg) + errx(1, "wgrtable %s: %s", id, errmsg); +} + +void +unsetwgpeer(const char *peerkey_b64, int param) +{ + setwgpeer(peerkey_b64, param); + wg_peer->p_flags |= WG_PEER_REMOVE; +} + +void +unsetwgpeerpsk(const char *value, int param) +{ + if (wg_peer == NULL) + errx(1, "wgpsk: wgpeer not set"); + wg_peer->p_flags |= WG_PEER_HAS_PSK; + bzero(wg_peer->p_psk, WG_KEY_LEN); +} + +void +unsetwgpeerall(const char *value, int param) +{ + ensurewginterface(); + wg_interface->i_flags |= WG_INTERFACE_REPLACE_PEERS; +} + +void +process_wg_commands(void) +{ + if (actions & A_WIREGUARD) { + strlcpy(wgdata.wgd_name, ifname, sizeof(wgdata.wgd_name)); + + if (ioctl(sock, SIOCSWG, (caddr_t)&wgdata) == -1) + err(1, "SIOCSWG"); + } +} + +void +wg_status(void) +{ + size_t i, j, last_size; + struct timespec now; + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + char key[WG_BASE64_KEY_LEN + 1]; + + strlcpy(wgdata.wgd_name, ifname, sizeof(wgdata.wgd_name)); + wgdata.wgd_size = 0; + wgdata.wgd_interface = NULL; + for (last_size = wgdata.wgd_size;; last_size = wgdata.wgd_size) { + if (ioctl(sock, SIOCGWG, (caddr_t)&wgdata) < 0) { + if (errno == ENOTTY) + goto out; + err(1, "SIOCGWG"); + } + if (last_size >= wgdata.wgd_size) + break; + wgdata.wgd_interface = realloc(wgdata.wgd_interface, + wgdata.wgd_size); + if (!wgdata.wgd_interface) + err(1, "realloc"); + } + wg_interface = wgdata.wgd_interface; + + if (wg_interface->i_flags & WG_INTERFACE_HAS_PORT) + printf("\twgport %hu\n", wg_interface->i_port); + if (wg_interface->i_flags & WG_INTERFACE_HAS_RTABLE) + printf("\twgrtable %d\n", wg_interface->i_rtable); + if (wg_interface->i_flags & WG_INTERFACE_HAS_PUBLIC) { + EVP_EncodeBlock(key, wg_interface->i_public, WG_KEY_LEN); + printf("\twgpubkey %s\n", key); + } + + wg_peer = &wg_interface->i_peers[0]; + for (i = 0; i < wg_interface->i_peers_count; i++) { + EVP_EncodeBlock(key, wg_peer->p_public, WG_KEY_LEN); + printf("\twgpeer %s\n", key); + + if (wg_peer->p_flags & WG_PEER_HAS_PSK) + printf("\t\twgpsk (present)\n"); + + if (wg_peer->p_flags & WG_PEER_HAS_PKA && wg_peer->p_pka) + printf("\t\twgpka %u (sec)\n", wg_peer->p_pka); + + if (wg_peer->p_flags & WG_PEER_HAS_ENDPOINT) { + if (getnameinfo(&wg_peer->p_sa, wg_peer->p_sa.sa_len, + hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), + NI_NUMERICHOST | NI_NUMERICSERV) == 0) + printf("\t\twgendpoint %s %s\n", hbuf, sbuf); + else + printf("\t\twgendpoint unable to print\n"); + } + + printf("\t\ttx: %llu, rx: %llu\n", + wg_peer->p_txbytes, wg_peer->p_rxbytes); + + if (wg_peer->p_last_handshake.tv_sec != 0) { + timespec_get(&now, TIME_UTC); + printf("\t\tlast handshake: %lld seconds ago\n", + now.tv_sec - wg_peer->p_last_handshake.tv_sec); + } + + + wg_aip = &wg_peer->p_aips[0]; + for (j = 0; j < wg_peer->p_aips_count; j++) { + inet_ntop(wg_aip->a_af, &wg_aip->a_addr, + hbuf, sizeof(hbuf)); + printf("\t\twgaip %s/%d\n", hbuf, wg_aip->a_cidr); + wg_aip++; + } + wg_peer = (struct wg_peer_io *)wg_aip; + } +out: + free(wgdata.wgd_interface); +} const struct umb_valdescr umb_regstate[] = MBIM_REGSTATE_DESCRIPTIONS; const struct umb_valdescr umb_dataclass[] = MBIM_DATACLASS_DESCRIPTIONS; |