BSD: Add the IPsec SA/SP database entries control

Add code for manipulation with TCP-MD5 keys in the IPsec SA/SP database
at FreeBSD systems. Now, BGP MD5 authentication (RFC 2385) keys are
handled automatically on both Linux and FreeBSD.

Based on patches from Pavel Tvrdik.
This commit is contained in:
Ondrej Zajicek (work) 2016-04-13 14:30:28 +02:00
parent 43fc6bb0fb
commit a7baa09862
10 changed files with 226 additions and 34 deletions

View file

@ -1764,9 +1764,20 @@ using the following configuration parameters:
only. Default: disabled.
<tag>password <m/string/</tag>
Use this password for MD5 authentication of BGP sessions. Default: no
authentication. Password has to be set by external utility
(e.g. setkey(8)) on BSD systems.
Use this password for MD5 authentication of BGP sessions (RFC 2385).
When used on BSD systems, see also <cf/setkey/ option below. Default:
no authentication.
<tag>setkey <m/switch/</tag>
On BSD systems, keys for TCP MD5 authentication are stored in the global
SA/SP database, which can be accessed by external utilities (e.g.
setkey(8)). BIRD configures security associations in the SA/SP database
automatically based on <cf/password/ options (see above), this option
allows to disable automatic updates by BIRD when manual configuration by
external utilities is preferred. Note that automatic SA/SP database
updates are currently implemented only for FreeBSD. Passwords have to be
set manually by an external utility on NetBSD and OpenBSD. Default:
enabled (ignored on non-FreeBSD).
<tag>passive <m/switch/</tag>
Standard BGP behavior is both initiating outgoing connections and

View file

@ -87,7 +87,7 @@ int sk_leave_group(sock *s, ip_addr maddr); /* Leave multicast group on sk iface
int sk_setup_broadcast(sock *s);
int sk_set_ttl(sock *s, int ttl); /* Set transmit TTL for given socket */
int sk_set_min_ttl(sock *s, int ttl); /* Set minimal accepted TTL for given socket */
int sk_set_md5_auth(sock *s, ip_addr a, struct iface *ifa, char *passwd);
int sk_set_md5_auth(sock *s, ip_addr local, ip_addr remote, struct iface *ifa, char *passwd, int setkey);
int sk_set_ipv6_checksum(sock *s, int offset);
int sk_set_icmp6_filter(sock *s, int p1, int p2);
void sk_log_error(sock *s, const char *p);

View file

@ -121,7 +121,8 @@ bgp_open(struct bgp_proto *p)
bgp_counter++;
if (p->cf->password)
if (sk_set_md5_auth(bgp_listen_sk, p->cf->remote_ip, p->cf->iface, p->cf->password) < 0)
if (sk_set_md5_auth(bgp_listen_sk, p->cf->source_addr, p->cf->remote_ip,
p->cf->iface, p->cf->password, p->cf->setkey) < 0)
{
sk_log_error(bgp_listen_sk, p->p.name);
bgp_close(p, 0);
@ -191,7 +192,8 @@ bgp_close(struct bgp_proto *p, int apply_md5)
bgp_counter--;
if (p->cf->password && apply_md5)
if (sk_set_md5_auth(bgp_listen_sk, p->cf->remote_ip, p->cf->iface, NULL) < 0)
if (sk_set_md5_auth(bgp_listen_sk, p->cf->source_addr, p->cf->remote_ip,
p->cf->iface, NULL, p->cf->setkey) < 0)
sk_log_error(bgp_listen_sk, p->p.name);
if (!bgp_counter)

View file

@ -51,6 +51,7 @@ struct bgp_config {
int add_path; /* Use ADD-PATH extension [draft] */
int allow_local_as; /* Allow that number of local ASNs in incoming AS_PATHs */
int gr_mode; /* Graceful restart mode (BGP_GR_*) */
int setkey; /* Set MD5 password to system SA/SP database */
unsigned gr_time; /* Graceful restart timeout */
unsigned connect_delay_time; /* Minimum delay between connect attempts */
unsigned connect_retry_time; /* Timeout for connect attempts */

View file

@ -27,7 +27,7 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY,
INTERPRET, COMMUNITIES, BGP_ORIGINATOR_ID, BGP_CLUSTER_LIST, IGP,
TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL, SECURITY, DETERMINISTIC,
SECONDARY, ALLOW, BFD, ADD, PATHS, RX, TX, GRACEFUL, RESTART, AWARE,
CHECK, LINK, PORT, EXTENDED, MESSAGES)
CHECK, LINK, PORT, EXTENDED, MESSAGES, SETKEY)
CF_GRAMMAR
@ -54,6 +54,7 @@ bgp_proto_start: proto_start BGP {
BGP_CFG->default_local_pref = 100;
BGP_CFG->gr_mode = BGP_GR_AWARE;
BGP_CFG->gr_time = 120;
BGP_CFG->setkey = 1;
}
;
@ -112,6 +113,7 @@ bgp_proto:
| bgp_proto CAPABILITIES bool ';' { BGP_CFG->capabilities = $3; }
| bgp_proto ADVERTISE IPV4 bool ';' { BGP_CFG->advertise_ipv4 = $4; }
| bgp_proto PASSWORD text ';' { BGP_CFG->password = $3; }
| bgp_proto SETKEY bool ';' { BGP_CFG->setkey = $3; }
| bgp_proto ROUTE LIMIT expr ';' {
this_proto->in_limit = cfg_allocz(sizeof(struct proto_limit));
this_proto->in_limit->limit = $4;

View file

@ -2,3 +2,4 @@ krt-sock.c
krt-sock.Y
krt-sys.h
sysio.h
setkey.h

170
sysdep/bsd/setkey.h Normal file
View file

@ -0,0 +1,170 @@
/*
* BIRD -- Manipulation the IPsec SA/SP database using setkey(8) utility
*
* (c) 2016 CZ.NIC z.s.p.o.
*/
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/pfkeyv2.h>
#include <netipsec/ipsec.h>
#include "nest/bird.h"
#include "lib/unix.h"
/*
* Open a socket for manage the IPsec SA/SP database entries
*/
static int
setkey_open_socket(void)
{
int s = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);
if (s < 0)
{
log(L_ERR "SETKEY: socket: %m");
return -1;
}
return s;
}
static int
setkey_send(struct sadb_msg *msg, uint len)
{
int s = setkey_open_socket();
if (s < 0)
return -1;
if (msg->sadb_msg_type == SADB_ADD)
{
/* Delete possible current key in the IPsec SA/SP database */
msg->sadb_msg_type = SADB_DELETE;
send(s, msg, len, 0);
msg->sadb_msg_type = SADB_ADD;
}
if (send(s, msg, len, 0) < 0)
{
log(L_ERR "SETKEY: send: %m");
close(s);
return -1;
}
close(s);
return 0;
}
/*
* Perform setkey(8)-like operation for set the password for TCP MD5 Signature.
* Could be called with SABD_ADD or SADB_DELETE argument. Note that SADB_ADD
* argument is internally processed as a pair of SADB_ADD and SADB_DELETE
* operations to implement replace.
*/
static int
setkey_md5(sockaddr *src, sockaddr *dst, char *passwd, uint type)
{
uint passwd_len = passwd ? strlen(passwd) : 0;
uint total =
sizeof(struct sadb_msg) +
sizeof(struct sadb_key) + PFKEY_ALIGN8(passwd_len) +
sizeof(struct sadb_sa) +
sizeof(struct sadb_x_sa2) +
sizeof(struct sadb_address) + PFKEY_ALIGN8(src->sa.sa_len) +
sizeof(struct sadb_address) + PFKEY_ALIGN8(dst->sa.sa_len);
char *buf = alloca(total);
char *pos = buf;
uint len;
memset(buf, 0, total);
struct sadb_msg *msg = (void *) pos;
len = sizeof(struct sadb_msg);
msg->sadb_msg_version = PF_KEY_V2;
msg->sadb_msg_type = type;
msg->sadb_msg_satype = SADB_X_SATYPE_TCPSIGNATURE;
msg->sadb_msg_len = 0; /* Fix it later */
msg->sadb_msg_pid = getpid();
pos += len;
/* Set authentication algorithm and password */
struct sadb_key *key = (void *) pos;
len = sizeof(struct sadb_key) + PFKEY_ALIGN8(passwd_len);
key->sadb_key_len = PFKEY_UNIT64(len);
key->sadb_key_exttype = SADB_EXT_KEY_AUTH;
key->sadb_key_bits = passwd_len * 8;
memcpy(pos + sizeof(struct sadb_key), passwd, passwd_len);
pos += len;
struct sadb_sa *sa = (void *) pos;
len = sizeof(struct sadb_sa);
sa->sadb_sa_len = PFKEY_UNIT64(len);
sa->sadb_sa_exttype = SADB_EXT_SA;
sa->sadb_sa_spi = htonl((u32) TCP_SIG_SPI);
sa->sadb_sa_auth = SADB_X_AALG_TCP_MD5;
sa->sadb_sa_encrypt = SADB_EALG_NONE;
sa->sadb_sa_flags = SADB_X_EXT_CYCSEQ;
pos += len;
struct sadb_x_sa2 *sa2 = (void *) pos;
len = sizeof(struct sadb_x_sa2);
sa2->sadb_x_sa2_len = PFKEY_UNIT64(len);
sa2->sadb_x_sa2_exttype = SADB_X_EXT_SA2;
sa2->sadb_x_sa2_mode = IPSEC_MODE_ANY;
pos += len;
/* Set source address */
struct sadb_address *saddr = (void *) pos;
len = sizeof(struct sadb_address) + PFKEY_ALIGN8(src->sa.sa_len);
saddr->sadb_address_len = PFKEY_UNIT64(len);
saddr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
saddr->sadb_address_proto = IPSEC_ULPROTO_ANY;
saddr->sadb_address_prefixlen = MAX_PREFIX_LENGTH;
memcpy(pos + sizeof(struct sadb_address), &src->sa, src->sa.sa_len);
pos += len;
/* Set destination address */
struct sadb_address *daddr = (void *) pos;
len = sizeof(struct sadb_address) + PFKEY_ALIGN8(dst->sa.sa_len);
daddr->sadb_address_len = PFKEY_UNIT64(len);
daddr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
daddr->sadb_address_proto = IPSEC_ULPROTO_ANY;
daddr->sadb_address_prefixlen = MAX_PREFIX_LENGTH;
memcpy(pos + sizeof(struct sadb_address), &dst->sa, dst->sa.sa_len);
pos += len;
len = pos - buf;
msg->sadb_msg_len = PFKEY_UNIT64(len);
return setkey_send(msg, len);
}
/*
* Manipulation with the IPsec SA/SP database
*/
static int
sk_set_md5_in_sasp_db(sock *s, ip_addr local, ip_addr remote, struct iface *ifa, char *passwd)
{
sockaddr src, dst;
sockaddr_fill(&src, s->af, local, ifa, 0);
sockaddr_fill(&dst, s->af, remote, ifa, 0);
if (passwd && *passwd)
{
int len = strlen(passwd);
if (len > TCP_KEYLEN_MAX)
ERR_MSG("The password for TCP MD5 Signature is too long");
if (setkey_md5(&src, &dst, passwd, SADB_ADD) < 0)
ERR_MSG("Cannot add TCP-MD5 password into the IPsec SA/SP database");
}
else
{
if (setkey_md5(&src, &dst, NULL, SADB_DELETE) < 0)
ERR_MSG("Cannot delete TCP-MD5 password from the IPsec SA/SP database");
}
return 0;
}

View file

@ -189,30 +189,26 @@ sk_prepare_ip_header(sock *s, void *hdr, int dlen)
#ifndef TCP_KEYLEN_MAX
#define TCP_KEYLEN_MAX 80
#endif
#ifndef TCP_SIG_SPI
#define TCP_SIG_SPI 0x1000
#endif
/*
* FIXME: Passwords has to be set by setkey(8) command. This is the same
* behaviour like Quagga. We need to add code for SA/SP entries
* management.
*/
#if defined(__FreeBSD__)
#define USE_MD5SIG_SETKEY
#include "lib/setkey.h"
#endif
int
sk_set_md5_auth(sock *s, ip_addr a, struct iface *ifa, char *passwd)
sk_set_md5_auth(sock *s, ip_addr local, ip_addr remote, struct iface *ifa, char *passwd, int setkey UNUSED)
{
int enable = 0;
if (passwd && *passwd)
{
int len = strlen(passwd);
enable = TCP_SIG_SPI;
if (len > TCP_KEYLEN_MAX)
ERR_MSG("MD5 password too long");
}
#ifdef USE_MD5SIG_SETKEY
if (setkey)
if (sk_set_md5_in_sasp_db(s, local, remote, ifa, passwd) < 0)
return -1;
#endif
int enable = (passwd && *passwd) ? TCP_SIG_SPI : 0;
if (setsockopt(s->fd, IPPROTO_TCP, TCP_MD5SIG, &enable, sizeof(enable)) < 0)
{
if (errno == ENOPROTOOPT)

View file

@ -179,19 +179,19 @@ sk_prepare_cmsgs4(sock *s, struct msghdr *msg, void *cbuf, size_t cbuflen)
*/
int
sk_set_md5_auth(sock *s, ip_addr a, struct iface *ifa, char *passwd)
sk_set_md5_auth(sock *s, ip_addr local UNUSED, ip_addr remote, struct iface *ifa, char *passwd, int setkey UNUSED)
{
struct tcp_md5sig md5;
memset(&md5, 0, sizeof(md5));
sockaddr_fill((sockaddr *) &md5.tcpm_addr, s->af, a, ifa, 0);
sockaddr_fill((sockaddr *) &md5.tcpm_addr, s->af, remote, ifa, 0);
if (passwd)
{
int len = strlen(passwd);
if (len > TCP_MD5SIG_MAXKEYLEN)
ERR_MSG("MD5 password too long");
ERR_MSG("The password for TCP MD5 Signature is too long");
md5.tcpm_keylen = len;
memcpy(&md5.tcpm_key, passwd, len);

View file

@ -951,23 +951,32 @@ sk_set_min_ttl(sock *s, int ttl)
/**
* sk_set_md5_auth - add / remove MD5 security association for given socket
* @s: socket
* @a: IP address of the other side
* @local: IP address of local side
* @remote: IP address of remote side
* @ifa: Interface for link-local IP address
* @passwd: password used for MD5 authentication
* @passwd: Password used for MD5 authentication
* @setkey: Update also system SA/SP database
*
* In TCP MD5 handling code in kernel, there is a set of pairs (address,
* password) used to choose password according to address of the other side.
* This function is useful for listening socket, for active sockets it is enough
* to set s->password field.
* In TCP MD5 handling code in kernel, there is a set of security associations
* used for choosing password and other authentication parameters according to
* the local and remote address. This function is useful for listening socket,
* for active sockets it may be enough to set s->password field.
*
* When called with passwd != NULL, the new pair is added,
* When called with passwd == NULL, the existing pair is removed.
*
* Note that while in Linux, the MD5 SAs are specific to socket, in BSD they are
* stored in global SA/SP database (but the behavior also must be enabled on
* per-socket basis). In case of multiple sockets to the same neighbor, the
* socket-specific state must be configured for each socket while global state
* just once per src-dst pair. The @setkey argument controls whether the global
* state (SA/SP database) is also updated.
*
* Result: 0 for success, -1 for an error.
*/
int
sk_set_md5_auth(sock *s, ip_addr a, struct iface *ifa, char *passwd)
sk_set_md5_auth(sock *s, ip_addr local, ip_addr remote, struct iface *ifa, char *passwd, int setkey)
{ DUMMY; }
#endif
@ -1437,7 +1446,7 @@ sk_open(sock *s)
}
if (s->password)
if (sk_set_md5_auth(s, s->daddr, s->iface, s->password) < 0)
if (sk_set_md5_auth(s, s->saddr, s->daddr, s->iface, s->password, 0) < 0)
goto err;
switch (s->type)