diff --git a/plugins/ipsec_linux/ipsec_linux.c b/plugins/ipsec_linux/ipsec_linux.c new file mode 100644 index 00000000..fcd4c400 --- /dev/null +++ b/plugins/ipsec_linux/ipsec_linux.c @@ -0,0 +1,459 @@ +/* doubango tinyIPsec plugin for Linux + * + * Copyright (C) 2021 Harald Welte + * + * DOUBANGO is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DOUBANGO is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DOUBANGO. + */ + +#include "tipsec.h" +#include "netlink_xfrm.h" + +#include "tsk_memory.h" +#include "tsk_object.h" +#include "tsk_debug.h" +#include "tsk_plugin.h" + +#include + +#define LOGTIC(ctx, fmt, args...) \ + fprintf(stderr, "LINUX_IPSEC: (%p) %s: " fmt, ctx, __func__, ## args) + +typedef struct plugin_linux_ipsec_ctx_s { + TIPSEC_DECLARE_CTX; + + tipsec_ctx_t* pc_base; + /* any linux-specific state structure listed below; so far none */ +} plugin_linux_ipsec_ctx_t; + +static struct mnl_socket *g_mnl_s; + +/*********************************************************************** + * Private functions + ***********************************************************************/ + +static void sockaddr_from4(struct sockaddr_storage *out, const struct in_addr *ia4, uint16_t port) +{ + struct sockaddr_in *sa4 = (struct sockaddr_in *) out; + + memset(sa4, 0, sizeof(*sa4)); + sa4->sin_family = AF_INET; + sa4->sin_addr = *ia4; + sa4->sin_port = htons(port); +} + +static void sockaddr_from6(struct sockaddr_storage *out, const struct in6_addr *ia6, uint16_t port) +{ + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *) out; + + memset(sa6, 0, sizeof(*sa6)); + sa6->sin6_family = AF_INET6; + sa6->sin6_addr = *ia6; + sa6->sin6_port = htons(port); +} + +/* convert internal state to 4x sockaddr */ +static void gen_sockaddrs(struct sockaddr_storage *out_uc, struct sockaddr_storage *out_us, + struct sockaddr_storage *out_pc, struct sockaddr_storage *out_ps, + const tipsec_ctx_t *_p_ctx) +{ + if (_p_ctx->use_ipv6) { + sockaddr_from6(out_uc, _p_ctx->addr_local, _p_ctx->port_uc); + sockaddr_from6(out_us, _p_ctx->addr_local, _p_ctx->port_us); + sockaddr_from6(out_pc, _p_ctx->addr_remote, _p_ctx->port_pc); + sockaddr_from6(out_ps, _p_ctx->addr_remote, _p_ctx->port_ps); + } else { + sockaddr_from4(out_uc, _p_ctx->addr_local, _p_ctx->port_uc); + sockaddr_from4(out_us, _p_ctx->addr_local, _p_ctx->port_us); + sockaddr_from4(out_pc, _p_ctx->addr_remote, _p_ctx->port_pc); + sockaddr_from4(out_ps, _p_ctx->addr_remote, _p_ctx->port_ps); + } +} + +/*********************************************************************** + * Call-Back functions of tipsec core + ***********************************************************************/ + +static tipsec_error_t +_plugin_linux_ipsec_ctx_init(tipsec_ctx_t *_p_ctx) +{ + plugin_linux_ipsec_ctx_t *p_ctx = (plugin_linux_ipsec_ctx_t *) _p_ctx; + + if (p_ctx->pc_base->initialized) { + TSK_DEBUG_ERROR("Already initialized"); + return tipsec_error_invalid_state; + } + + /* FIXME */ + + p_ctx->pc_base->initialized = tsk_true; + p_ctx->pc_base->state = tipsec_state_initial; + + return tipsec_error_success; +} + +/* SIP stack tells us about local IPs/Ports and asks us to allocate SPIs */ +static tipsec_error_t +_plugin_linux_ipsec_ctx_set_local(tipsec_ctx_t *_p_ctx, const char *addr_local, const char *addr_remote, tipsec_port_t port_uc, tipsec_port_t port_us) +{ + //plugin_linux_ipsec_ctx_t *p_ctx = (plugin_linux_ipsec_ctx_t *) _p_ctx; + struct sockaddr_storage sa_local, sa_remote; + int rc; + + LOGTIC(_p_ctx, "%s:%u+%u -> %s\n", addr_local, port_uc, port_us, addr_remote); + + _p_ctx->addr_local = tsk_realloc(_p_ctx->addr_local, _p_ctx->use_ipv6 ? 16 : 4); + if (!_p_ctx->addr_local) + return tipsec_error_outofmemory; + + _p_ctx->addr_remote = tsk_realloc(_p_ctx->addr_remote, _p_ctx->use_ipv6 ? 16 : 4); + if (!_p_ctx->addr_remote) + return tipsec_error_outofmemory; + + if (_p_ctx->use_ipv6) { + if (inet_pton(AF_INET6, addr_local, _p_ctx->addr_local) != 1) + return tipsec_error_sys; + sockaddr_from6(&sa_local, _p_ctx->addr_local, 0); + if (inet_pton(AF_INET6, addr_remote, _p_ctx->addr_remote) != 1) + return tipsec_error_sys; + sockaddr_from6(&sa_remote, _p_ctx->addr_remote, 0); + } else { + if (inet_pton(AF_INET, addr_local, _p_ctx->addr_local) != 1) + return tipsec_error_sys; + sockaddr_from4(&sa_local, _p_ctx->addr_local, 0); + if (inet_pton(AF_INET, addr_remote, _p_ctx->addr_remote) != 1) + return tipsec_error_sys; + sockaddr_from4(&sa_remote, _p_ctx->addr_remote, 0); +#if 0 + /* FIXME: do we really need those in host byte order? */ + *((uint32_t *)_p_ctx->addr_local) = ntohl(*(uint32_t *)_p_ctx->addr_local); + *((uint32_t *)_p_ctx->addr_remote) = ntohl(*(uint32_t *)_p_ctx->addr_remote); +#endif + } + + _p_ctx->port_uc = port_uc; + _p_ctx->port_us = port_us; + + /* we need to allocate local SPIs here, one for TCP client and one for TCP server role. + * These will be passed to the P-CSCF in the Security-Client header. */ + rc = xfrm_spi_alloc(g_mnl_s, 1, &_p_ctx->spi_uc, (struct sockaddr *)&sa_local, (struct sockaddr *)&sa_remote); + if (rc != 0) + return tipsec_error_sys; + rc = xfrm_spi_alloc(g_mnl_s, 2, &_p_ctx->spi_us, (struct sockaddr *)&sa_local, (struct sockaddr *)&sa_remote); + if (rc != 0) + return tipsec_error_sys; + + _p_ctx->state = tipsec_state_inbound; + + return tipsec_error_success; +} + +/* SIP Stack informs us about the remote SPIs + TCP ports */ +static tipsec_error_t +_plugin_linux_ipsec_ctx_set_remote(tipsec_ctx_t *_p_ctx, tipsec_spi_t spi_pc, tipsec_spi_t spi_ps, tipsec_port_t port_pc, tipsec_port_t port_ps, tipsec_lifetime_t lifetime) +{ + //plugin_linux_ipsec_ctx_t *p_ctx = (plugin_linux_ipsec_ctx_t *) _p_ctx; + + LOGTIC(_p_ctx, "SPI_PC=0x%08x SPI_PS=0x%08x PORT_PC=%u, PORT_PS=%u lifetime=%lu\n", + spi_pc, spi_ps, port_pc, port_ps, lifetime); + + _p_ctx->lifetime = lifetime; + + _p_ctx->port_ps = port_ps; + _p_ctx->port_pc = port_pc; + + _p_ctx->spi_ps = spi_ps; + _p_ctx->spi_pc = spi_pc; + + /* we cannot yet create the SAs as we don't have the keys yet */ + + _p_ctx->state = tipsec_state_full; + + return tipsec_error_success; +} + +/* SIP stack informs us about the key material (obtained from SIM after '401 Unauthorized' with RAND+AUTN */ +static tipsec_error_t +_plugin_linux_ipsec_ctx_set_keys(tipsec_ctx_t *_p_ctx, const tipsec_key_t *ik, const tipsec_key_t *ck) +{ + //plugin_linux_ipsec_ctx_t *p_ctx = (plugin_linux_ipsec_ctx_t *) _p_ctx; + + LOGTIC(_p_ctx, "entered\n"); + + _p_ctx->ik = tsk_realloc(_p_ctx->ik, TIPSEC_KEY_LEN); + if (!_p_ctx->ik) + return tipsec_error_outofmemory; + memcpy(_p_ctx->ik, ik, TIPSEC_KEY_LEN); + + _p_ctx->ck = tsk_realloc(_p_ctx->ck, TIPSEC_KEY_LEN); + if (!_p_ctx->ck) + return tipsec_error_outofmemory; + memcpy(_p_ctx->ck, ck, TIPSEC_KEY_LEN); + + return tipsec_error_success; +} + +/* SIP stack asks us to start the IPsec processing */ +static tipsec_error_t +_plugin_linux_ipsec_ctx_start(tipsec_ctx_t *_p_ctx) +{ + //plugin_linux_ipsec_ctx_t *p_ctx = (plugin_linux_ipsec_ctx_t *) _p_ctx; + struct sockaddr_storage uc_saddr, us_saddr, pc_saddr, ps_saddr; + struct xfrm_algobuf auth, ciph; + int rc; + + LOGTIC(_p_ctx, "entered\n"); + + memset(&auth, 0, sizeof(auth)); + memset(&ciph, 0, sizeof(ciph)); + + /* build sockaddrs from the internal representations */ + gen_sockaddrs(&uc_saddr, &us_saddr, &pc_saddr, &ps_saddr, _p_ctx); + + /* build cipher specs from internal representations */ + switch (_p_ctx->alg) { + case tipsec_alg_hmac_md5_96: + strcpy(auth.algo.alg_name, "md5"); + break; + case tipsec_alg_hmac_sha_1_96: + strcpy(auth.algo.alg_name, "sha1"); + break; + default: + LOGTIC(_p_ctx, "Unsupported authentication algorithm %d\n", _p_ctx->alg); + return tipsec_error_notimplemented; + } + auth.algo.alg_key_len = TIPSEC_KEY_LEN * 8; + memcpy(auth.algo.alg_key, _p_ctx->ik, TIPSEC_KEY_LEN); + + switch (_p_ctx->ealg) { + case tipsec_ealg_null: + strcpy(ciph.algo.alg_name, "cipher_null"); + break; + case tipsec_ealg_aes: + strcpy(ciph.algo.alg_name, "aes"); + ciph.algo.alg_key_len = TIPSEC_KEY_LEN * 8; + memcpy(ciph.algo.alg_key, _p_ctx->ck, TIPSEC_KEY_LEN); + break; + case tipsec_ealg_des_ede3_cbc: + strcpy(ciph.algo.alg_name, "des3_ede"); + ciph.algo.alg_key_len = 192; + memcpy(ciph.algo.alg_key, _p_ctx->ck, TIPSEC_KEY_LEN); + memcpy(ciph.algo.alg_key+16, _p_ctx->ck, 8); + break; + default: + LOGTIC(_p_ctx, "Unsupported encryption algorithm %d\n", _p_ctx->ealg); + return tipsec_error_notimplemented; + } + + /* actually create the SAs and policies in the kernel */ + + /* UE client to P-CSCF server */ + rc = xfrm_sa_add(g_mnl_s, _p_ctx->spi_ps, (struct sockaddr *) &uc_saddr, (struct sockaddr *) &ps_saddr, + _p_ctx->spi_ps, &auth.algo, &ciph.algo); + if (rc < 0) + return tipsec_error_sys; + + rc = xfrm_policy_add(g_mnl_s, (struct sockaddr *) &uc_saddr, (struct sockaddr *) &ps_saddr, + _p_ctx->spi_ps, false); + if (rc < 0) + goto del_sa_1; + + /* P-CSCF client to UE server */ + rc = xfrm_sa_add(g_mnl_s, _p_ctx->spi_us, (struct sockaddr *) &pc_saddr, (struct sockaddr *) &us_saddr, + _p_ctx->spi_us, &auth.algo, &ciph.algo); + if (rc < 0) + goto del_policy_1; + rc = xfrm_policy_add(g_mnl_s, (struct sockaddr *) &pc_saddr, (struct sockaddr *) &us_saddr, + _p_ctx->spi_us, true); + if (rc < 0) + goto del_sa_2; + + /* P-CSCF server to UE client */ + rc = xfrm_sa_add(g_mnl_s, _p_ctx->spi_uc, (struct sockaddr *) &ps_saddr, (struct sockaddr *) &uc_saddr, + _p_ctx->spi_uc, &auth.algo, &ciph.algo); + if (rc < 0) + goto del_policy_2; + rc = xfrm_policy_add(g_mnl_s, (struct sockaddr *) &ps_saddr, (struct sockaddr *) &uc_saddr, + _p_ctx->spi_uc, true); + if (rc < 0) + goto del_sa_3; + + /* UE server to P-CSCF client */ + rc = xfrm_sa_add(g_mnl_s, _p_ctx->spi_pc, (struct sockaddr *) &us_saddr, (struct sockaddr *) &pc_saddr, + _p_ctx->spi_pc, &auth.algo, &ciph.algo); + if (rc < 0) + goto del_policy_3; + rc = xfrm_policy_add(g_mnl_s, (struct sockaddr *) &us_saddr, (struct sockaddr *) &pc_saddr, + _p_ctx->spi_pc, false); + if (rc < 0) + goto del_sa_4; + + _p_ctx->state = tipsec_state_active; + _p_ctx->started = 1; + + return tipsec_error_success; + + /* clean-up in case of failure */ +del_sa_4: + xfrm_sa_del(g_mnl_s, (struct sockaddr *) &us_saddr, (struct sockaddr *) &pc_saddr, _p_ctx->spi_pc); +del_policy_3: + xfrm_policy_del(g_mnl_s, (struct sockaddr *) &ps_saddr, (struct sockaddr *) &uc_saddr, true); +del_sa_3: + xfrm_sa_del(g_mnl_s, (struct sockaddr *) &ps_saddr, (struct sockaddr *) &uc_saddr, _p_ctx->spi_uc); +del_policy_2: + xfrm_policy_del(g_mnl_s, (struct sockaddr *) &pc_saddr, (struct sockaddr *) &us_saddr, true); +del_sa_2: + xfrm_sa_del(g_mnl_s, (struct sockaddr *) &pc_saddr, (struct sockaddr *) &us_saddr, _p_ctx->spi_us); +del_policy_1: + xfrm_policy_del(g_mnl_s, (struct sockaddr *) &uc_saddr, (struct sockaddr *) &ps_saddr, false); +del_sa_1: + xfrm_sa_del(g_mnl_s, (struct sockaddr *) &uc_saddr, (struct sockaddr *) &ps_saddr, _p_ctx->spi_ps); + + return tipsec_error_sys; +} + +/* SIP stack asks us to stop the IPsec processing */ +static tipsec_error_t +_plugin_linux_ipsec_ctx_stop(tipsec_ctx_t *_p_ctx) +{ + //plugin_linux_ipsec_ctx_t *p_ctx = (plugin_linux_ipsec_ctx_t *) _p_ctx; + struct sockaddr_storage uc_saddr, us_saddr, pc_saddr, ps_saddr; + + LOGTIC(_p_ctx, "entered\n"); + + /* build sockaddrs from the internal representations */ + gen_sockaddrs(&uc_saddr, &us_saddr, &pc_saddr, &ps_saddr, _p_ctx); + + /* remove the SAs and policies from the kernel */ + xfrm_policy_del(g_mnl_s, (struct sockaddr *) &us_saddr, (struct sockaddr *) &pc_saddr, false); + xfrm_sa_del(g_mnl_s, (struct sockaddr *) &us_saddr, (struct sockaddr *) &pc_saddr, _p_ctx->spi_pc); + + xfrm_policy_del(g_mnl_s, (struct sockaddr *) &ps_saddr, (struct sockaddr *) &uc_saddr, true); + xfrm_sa_del(g_mnl_s, (struct sockaddr *) &ps_saddr, (struct sockaddr *) &uc_saddr, _p_ctx->spi_uc); + + xfrm_policy_del(g_mnl_s, (struct sockaddr *) &pc_saddr, (struct sockaddr *) &us_saddr, true); + xfrm_sa_del(g_mnl_s, (struct sockaddr *) &pc_saddr, (struct sockaddr *) &us_saddr, _p_ctx->spi_us); + + xfrm_policy_del(g_mnl_s, (struct sockaddr *) &uc_saddr, (struct sockaddr *) &ps_saddr, false); + xfrm_sa_del(g_mnl_s, (struct sockaddr *) &uc_saddr, (struct sockaddr *) &ps_saddr, _p_ctx->spi_ps); + + _p_ctx->started = 0; + _p_ctx->state = tipsec_state_initial; + + return tipsec_error_success; +} + + +/*********************************************************************** + * tipsec Plugin Definition + ***********************************************************************/ + +static tsk_object_t *_plugin_linux_ipsec_ctx_ctor(tsk_object_t *self, va_list *app) +{ + plugin_linux_ipsec_ctx_t *p_ctx = (plugin_linux_ipsec_ctx_t *) self; + + if (p_ctx) + p_ctx->pc_base = TIPSEC_CTX(p_ctx); + + g_mnl_s = xfrm_init_mnl_socket(); + LOGTIC(p_ctx, "context created\n"); + + return self; +} + +static tsk_object_t *_plugin_linux_ipsec_ctx_dtor(tsk_object_t *self) +{ + plugin_linux_ipsec_ctx_t *p_ctx = (plugin_linux_ipsec_ctx_t *) self; + + if (!p_ctx) + return self; + + if (p_ctx->pc_base->started) + tipsec_ctx_stop(p_ctx->pc_base); + + /* FIXME */ + + TSK_FREE(p_ctx->pc_base->addr_local); + TSK_FREE(p_ctx->pc_base->addr_remote); + + TSK_FREE(p_ctx->pc_base->ik); + TSK_FREE(p_ctx->pc_base->ck); + + LOGTIC(p_ctx, "context destroyed\n"); + + return self; +} + +/* object definition */ +static const tsk_object_def_t plugin_linux_ipsec_ctx_def_s = { + sizeof(plugin_linux_ipsec_ctx_t), + _plugin_linux_ipsec_ctx_ctor, + _plugin_linux_ipsec_ctx_dtor, + tsk_null, +}; + +/* plugin definition */ +static const tipsec_plugin_def_t plugin_linux_ipsec_plugin_def_s = { + &plugin_linux_ipsec_ctx_def_s, + tipsec_impl_type_ltools, + "Linux kernel IPSec", + _plugin_linux_ipsec_ctx_init, + _plugin_linux_ipsec_ctx_set_local, + _plugin_linux_ipsec_ctx_set_remote, + _plugin_linux_ipsec_ctx_set_keys, + _plugin_linux_ipsec_ctx_start, + _plugin_linux_ipsec_ctx_stop, +}; +//static const tipsec_plugin_def_t *plugin_win_ipsec_vista_plugin_def_t = &plugin_win_ipsec_vista_plugin_def_s; + +/*********************************************************************** + * core Plugin Definition + ***********************************************************************/ + +int __plugin_get_def_count() +{ + return 1; +} + +tsk_plugin_def_type_t __plugin_get_def_type_at(int index) +{ + switch (index) { + case 0: + return tsk_plugin_def_type_ipsec; + default: + TSK_DEBUG_ERROR("No plugin at index %d", index); + return tsk_plugin_def_type_none; + } +} + +tsk_plugin_def_media_type_t __plugin_get_def_media_type_at(int index) +{ + switch (index) { + case 0: + return tsk_plugin_def_media_type_all; + default: + TSK_DEBUG_ERROR("No plugin at index %d", index); + return tsk_plugin_def_media_type_none; + } +} + +tsk_plugin_def_ptr_const_t __plugin_get_def_at(int index) +{ + switch (index) { + case 0: + return &plugin_linux_ipsec_plugin_def_s; + default: + TSK_DEBUG_ERROR("No plugin at index %d", index); + return tsk_null; + } +} diff --git a/plugins/ipsec_linux/makefile b/plugins/ipsec_linux/makefile new file mode 100644 index 00000000..76de4374 --- /dev/null +++ b/plugins/ipsec_linux/makefile @@ -0,0 +1,17 @@ +CFLAGS=-g -O0 -Wall +CFLAGS+=-fPIC -I../../tinySAK/src -I../../tinyIPSec/src $(shell pkg-config --cflags libmnl) +LIBS=$(shell pkg-config --libs libmnl) -lpthread -ldl -ltinySAK -ltinyIPSec + +all: ipsec_linux.so linux-ipsec-tool + +linux-ipsec-tool: tool.o netlink_xfrm.o + $(CC) -o $@ $^ $(LIBS) + +%.o: %.c + $(CC) $(CFLAGS) -o $@ -c $< + +ipsec_linux.so: ipsec_linux.o netlink_xfrm.o + $(CC) -o $@ -shared $^ $(LIBS) + +clean: + @rm -f linux-ipsec-tool ipsec_linux.so *.o diff --git a/plugins/ipsec_linux/netlink_xfrm.c b/plugins/ipsec_linux/netlink_xfrm.c new file mode 100644 index 00000000..aadffeca --- /dev/null +++ b/plugins/ipsec_linux/netlink_xfrm.c @@ -0,0 +1,432 @@ +/* Linux kernel IPsec interfacing via netlink XFRM + * + * Copyright (C) 2021 Harald Welte + * + * DOUBANGO is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * DOUBANGO is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DOUBANGO. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "netlink_xfrm.h" + +#define XFRM_USER_ID 0x240299 /* some random number; let's use TS 24.299 */ + +struct mnl_socket *xfrm_init_mnl_socket(void) +{ + struct mnl_socket *mnl_socket = mnl_socket_open(NETLINK_XFRM); + if (!mnl_socket) { + fprintf(stderr, "ERR: Could not open XFRM netlink socket: %s", strerror(errno)); + return NULL; + } + + if (mnl_socket_bind(mnl_socket, 0, MNL_SOCKET_AUTOPID) < 0) { + fprintf(stderr, "ERR: Could not open XFRM netlink socket: %s", strerror(errno)); + mnl_socket_close(mnl_socket); + return NULL; + } + + return mnl_socket; +} + +static unsigned int get_next_nlmsg_seq(void) +{ + static unsigned int next_seq; + return next_seq++; +} + + +/* this is just a simple call-back which returns the nlmsghdr via 'data' */ +static int data_cb(const struct nlmsghdr *nlh, void *data) +{ + const struct nlmsghdr **rx = data; + + *rx = nlh; + + /* FIXME: is there a situation in which we'd want to return OK and not STOP? */ + return MNL_CB_STOP; +} + +/* send 'tx' via 'mnl_sock' and receive messages from kernel, using caller-provided + * rx_buf/rx_buf_size as temporary storage buffer; return response nlmsghdr in 'rx' */ +static int transceive_mnl(struct mnl_socket *mnl_sock, const struct nlmsghdr *tx, + uint8_t *rx_buf, size_t rx_buf_size, struct nlmsghdr **rx) +{ + int rc; + + rc = mnl_socket_sendto(mnl_sock, tx, tx->nlmsg_len); + if (rc < 0) { + fprintf(stderr, "ERR: cannot create IPsec SA: %s\n", strerror(errno)); + return -1; + } + + /* iterate until it is our answer, handing to mnl_cb_run, ... */ + while (1) { + rc = mnl_socket_recvfrom(mnl_sock, rx_buf, rx_buf_size); + if (rc == -1) { + perror("mnl_socket_recvfrom"); + return -EIO; + } + + rc = mnl_cb_run(rx_buf, rc, tx->nlmsg_seq, mnl_socket_get_portid(mnl_sock), data_cb, rx); + if (rc == -1) { + perror("mnl_cb_run"); + return -EIO; + } else if (rc <= MNL_CB_STOP) + break; + } + + return 0; +} + +static int sockaddrs2xfrm_sel(struct xfrm_selector *sel, const struct sockaddr *src, + const struct sockaddr *dst) +{ + const struct sockaddr_in *sin; + const struct sockaddr_in6 *sin6; + + switch (src->sa_family) { + case AF_INET: + sin = (const struct sockaddr_in *) src; + sel->saddr.a4 = sin->sin_addr.s_addr; + sel->prefixlen_s = 32; + sel->sport = sin->sin_port; + sin = (const struct sockaddr_in *) dst; + sel->daddr.a4 = sin->sin_addr.s_addr; + sel->prefixlen_d = 32; + sel->dport = sin->sin_port; + break; + case AF_INET6: + sin6 = (const struct sockaddr_in6 *) src; + memcpy(sel->saddr.a6, &sin6->sin6_addr, sizeof(sel->saddr.a6)); + sel->prefixlen_s = 128; + sel->sport = sin6->sin6_port; + sin6 = (const struct sockaddr_in6 *) dst; + memcpy(sel->daddr.a6, &sin6->sin6_addr, sizeof(sel->daddr.a6)); + sel->prefixlen_d = 128; + sel->dport = sin6->sin6_port; + break; + default: + return -EINVAL; + } + sel->dport_mask = 0xffff; + sel->sport_mask = 0xffff; + sel->family = src->sa_family; + + return 0; +} + + +/*********************************************************************** + * SPI Allocation + ***********************************************************************/ + +/* allocate a local SPI for ESP between given src+dst address */ +int xfrm_spi_alloc(struct mnl_socket *mnl_sock, uint32_t reqid, uint32_t *spi_out, + const struct sockaddr *src, const struct sockaddr *dst) +{ + uint8_t msg_buf[MNL_SOCKET_BUFFER_SIZE]; + uint8_t rx_buf[MNL_SOCKET_BUFFER_SIZE]; + struct xfrm_userspi_info *xui, *rx_xui; + struct nlmsghdr *nlh, *rx_nlh = NULL; + const struct sockaddr_in *sin; + const struct sockaddr_in6 *sin6; + int rc; + + memset(msg_buf, 0, sizeof(msg_buf)); + + if (src->sa_family != dst->sa_family) + return -EINVAL; + + nlh = mnl_nlmsg_put_header(msg_buf); + nlh->nlmsg_flags = NLM_F_REQUEST, + nlh->nlmsg_type = XFRM_MSG_ALLOCSPI, + nlh->nlmsg_seq = get_next_nlmsg_seq(); + //nlh->nlmsg_pid = reqid; //FIXME + + xui = (struct xfrm_userspi_info *) mnl_nlmsg_put_extra_header(nlh, sizeof(*xui)); + + xui->info.family = src->sa_family; + + /* RFC4303 reserves 0..255 */ + xui->min = 0x100; + xui->max = 0xffffffff; + + /* ID src, dst, proto */ + switch (src->sa_family) { + case AF_INET: + sin = (const struct sockaddr_in *) src; + printf("src=%s ", inet_ntoa(sin->sin_addr)); + xui->info.saddr.a4 = sin->sin_addr.s_addr; + sin = (const struct sockaddr_in *) dst; + printf("dst=%s ", inet_ntoa(sin->sin_addr)); + xui->info.id.daddr.a4 = sin->sin_addr.s_addr; + //xui->info.sel.prefixlen_d = 32; + break; + case AF_INET6: + sin6 = (const struct sockaddr_in6 *) src; + memcpy(xui->info.saddr.a6, &sin6->sin6_addr, sizeof(xui->info.saddr.a6)); + //xui->info.sel.prefixlen_s = 128; + sin6 = (const struct sockaddr_in6 *) dst; + memcpy(xui->info.id.daddr.a6, &sin6->sin6_addr, sizeof(xui->info.id.daddr.a6)); + //xui->info.sel.prefixlen_d = 128; + break; + default: + fprintf(stderr, "ERR: unsupported address family %u\n", src->sa_family); + return -1; + } + + xui->info.id.proto = IPPROTO_ESP; + xui->info.reqid = reqid; + xui->info.mode = XFRM_MODE_TRANSPORT; + //xui->info.replay_window = 32; // TODO: check spec + + rc = transceive_mnl(mnl_sock, nlh, rx_buf, MNL_SOCKET_BUFFER_SIZE, &rx_nlh); + if (rc < 0) { + fprintf(stderr, "ERR: cannot create IPsec SA: %s\n", strerror(errno)); + return -1; + } + + /* parse response */ + rx_xui = (void *)rx_nlh + sizeof(*rx_nlh); + //printf("Allocated SPI=0x%08x\n", ntohl(xui->info.id.spi)); + *spi_out = ntohl(rx_xui->info.id.spi); + + return 0; +} + +/*********************************************************************** + * SA (Security Association) + ***********************************************************************/ + +int xfrm_sa_del(struct mnl_socket *mnl_sock, + const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi) +{ + uint8_t msg_buf[MNL_SOCKET_BUFFER_SIZE]; + uint8_t rx_buf[MNL_SOCKET_BUFFER_SIZE]; + struct xfrm_usersa_id *said; + struct nlmsghdr *nlh, *rx_nlh; + const struct sockaddr_in *sin; + const struct sockaddr_in6 *sin6; + xfrm_address_t saddr; + int rc; + + memset(&saddr, 0, sizeof(saddr)); + + nlh = mnl_nlmsg_put_header(msg_buf); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_type = XFRM_MSG_DELSA; + nlh->nlmsg_seq = get_next_nlmsg_seq(); + //nlh->nlmsg_pid = reqid; //FIXME + + said = (struct xfrm_usersa_id *) mnl_nlmsg_put_extra_header(nlh, sizeof(*said)); + said->spi = htonl(spi); + said->proto = IPPROTO_ESP; + + said->family = src->sa_family; + switch (src->sa_family) { + case AF_INET: + sin = (const struct sockaddr_in *) src; + saddr.a4 = sin->sin_addr.s_addr; + sin = (const struct sockaddr_in *) dst; + said->daddr.a4 = sin->sin_addr.s_addr; + break; + case AF_INET6: + sin6 = (const struct sockaddr_in6 *) src; + memcpy(saddr.a6, &sin6->sin6_addr, sizeof(saddr.a6)); + sin6 = (const struct sockaddr_in6 *) dst; + memcpy(said->daddr.a6, &sin6->sin6_addr, sizeof(said->daddr.a6)); + break; + default: + fprintf(stderr, "ERR: unsupported address family %u\n", src->sa_family); + return -1; + } + + mnl_attr_put(nlh, XFRMA_SRCADDR, sizeof(saddr), (void *)&saddr); + + rc = transceive_mnl(mnl_sock, nlh, rx_buf, MNL_SOCKET_BUFFER_SIZE, &rx_nlh); + if (rc < 0) { + fprintf(stderr, "ERR: cannot delete IPsec SA: %s\n", strerror(errno)); + return -1; + } + + /* FIXME: parse response */ + + return 0; +} + +int xfrm_sa_add(struct mnl_socket *mnl_sock, uint32_t reqid, + const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi, + const struct xfrm_algo *auth_algo, const struct xfrm_algo *ciph_algo) +{ + uint8_t msg_buf[MNL_SOCKET_BUFFER_SIZE]; + uint8_t rx_buf[MNL_SOCKET_BUFFER_SIZE]; + struct xfrm_usersa_info *sainfo; + struct nlmsghdr *nlh, *rx_nlh; + int rc; + + nlh = mnl_nlmsg_put_header(msg_buf); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK; + nlh->nlmsg_type = XFRM_MSG_NEWSA; + nlh->nlmsg_seq = get_next_nlmsg_seq(); + //nlh->nlmsg_pid = reqid; //FIXME + + sainfo = (struct xfrm_usersa_info *) mnl_nlmsg_put_extra_header(nlh, sizeof(*sainfo)); + sainfo->sel.family = src->sa_family; + rc = sockaddrs2xfrm_sel(&sainfo->sel, src, dst); + if (rc < 0) + return -EINVAL; + + sainfo->sel.user = htonl(XFRM_USER_ID); + + sainfo->saddr = sainfo->sel.saddr; + sainfo->id.daddr = sainfo->sel.daddr; + + sainfo->id.spi = htonl(spi); + sainfo->id.proto = IPPROTO_ESP; + + sainfo->lft.soft_byte_limit = XFRM_INF; + sainfo->lft.hard_byte_limit = XFRM_INF; + sainfo->lft.soft_packet_limit = XFRM_INF; + sainfo->lft.hard_packet_limit = XFRM_INF; + sainfo->reqid = reqid; + sainfo->family = src->sa_family; + sainfo->mode = XFRM_MODE_TRANSPORT; + sainfo->replay_window = 32; + + mnl_attr_put(nlh, XFRMA_ALG_AUTH, sizeof(struct xfrm_algo) + auth_algo->alg_key_len, auth_algo); + + mnl_attr_put(nlh, XFRMA_ALG_CRYPT, sizeof(struct xfrm_algo) + ciph_algo->alg_key_len, ciph_algo); + + rc = transceive_mnl(mnl_sock, nlh, rx_buf, MNL_SOCKET_BUFFER_SIZE, &rx_nlh); + if (rc < 0) { + fprintf(stderr, "ERR: cannot create IPsec SA: %s\n", strerror(errno)); + return -1; + } + + /* FIXME: parse response */ + + return 0; +} + +/*********************************************************************** + * Security Policy + ***********************************************************************/ + +int xfrm_policy_add(struct mnl_socket *mnl_sock, + const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi, bool dir_in) +{ + uint8_t msg_buf[MNL_SOCKET_BUFFER_SIZE]; + uint8_t rx_buf[MNL_SOCKET_BUFFER_SIZE]; + struct xfrm_userpolicy_info *pinfo; + struct xfrm_user_tmpl tmpl; + struct nlmsghdr *nlh, *rx_nlh; + int rc; + + memset(&tmpl, 0, sizeof(tmpl)); + + nlh = mnl_nlmsg_put_header(msg_buf); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK; + nlh->nlmsg_type = XFRM_MSG_NEWPOLICY; + nlh->nlmsg_seq = get_next_nlmsg_seq(); + //nlh->nlmsg_pid = reqid; //FIXME + + pinfo = (struct xfrm_userpolicy_info *) mnl_nlmsg_put_extra_header(nlh, sizeof(*pinfo)); + + rc = sockaddrs2xfrm_sel(&pinfo->sel, src, dst); + if (rc < 0) + return -EINVAL; + + pinfo->sel.user = htonl(XFRM_USER_ID); + + pinfo->lft.soft_byte_limit = XFRM_INF; + pinfo->lft.hard_byte_limit = XFRM_INF; + pinfo->lft.soft_packet_limit = XFRM_INF; + pinfo->lft.hard_packet_limit = XFRM_INF; + pinfo->priority = 2342; // FIXME + pinfo->action = XFRM_POLICY_ALLOW; + pinfo->share = XFRM_SHARE_ANY; + + if (dir_in) + pinfo->dir = XFRM_POLICY_IN; + else + pinfo->dir = XFRM_POLICY_OUT; + + tmpl.id.proto = IPPROTO_ESP; + tmpl.id.daddr = pinfo->sel.daddr; + tmpl.saddr = pinfo->sel.saddr; + tmpl.family = pinfo->sel.family; + tmpl.reqid = spi; + tmpl.mode = XFRM_MODE_TRANSPORT; + tmpl.aalgos = 0xffffffff; + tmpl.ealgos = 0xffffffff; + tmpl.calgos = 0xffffffff; + mnl_attr_put(nlh, XFRMA_TMPL, sizeof(tmpl), &tmpl); + + rc = transceive_mnl(mnl_sock, nlh, rx_buf, MNL_SOCKET_BUFFER_SIZE, &rx_nlh); + if (rc < 0) { + fprintf(stderr, "ERR: cannot create IPsec policy: %s\n", strerror(errno)); + return -1; + } + + /* FIXME: parse response */ + + return 0; +} + + +int xfrm_policy_del(struct mnl_socket *mnl_sock, + const struct sockaddr *src, const struct sockaddr *dst, bool dir_in) +{ + uint8_t msg_buf[MNL_SOCKET_BUFFER_SIZE]; + uint8_t rx_buf[MNL_SOCKET_BUFFER_SIZE]; + struct xfrm_userpolicy_id *pid; + struct nlmsghdr *nlh, *rx_nlh; + int rc; + + nlh = mnl_nlmsg_put_header(msg_buf); + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_type = XFRM_MSG_DELPOLICY; + nlh->nlmsg_seq = get_next_nlmsg_seq(); + //nlh->nlmsg_pid = reqid; //FIXME + + pid = (struct xfrm_userpolicy_id *) mnl_nlmsg_put_extra_header(nlh, sizeof(*pid)); + + rc = sockaddrs2xfrm_sel(&pid->sel, src, dst); + if (rc < 0) + return -EINVAL; + + pid->sel.user = htonl(XFRM_USER_ID); + + if (dir_in) + pid->dir = XFRM_POLICY_IN; + else + pid->dir = XFRM_POLICY_OUT; + + rc = transceive_mnl(mnl_sock, nlh, rx_buf, MNL_SOCKET_BUFFER_SIZE, &rx_nlh); + if (rc < 0) { + fprintf(stderr, "ERR: cannot delete IPsec policy: %s\n", strerror(errno)); + return -1; + } + + /* FIXME: parse response */ + + return 0; +} diff --git a/plugins/ipsec_linux/netlink_xfrm.h b/plugins/ipsec_linux/netlink_xfrm.h new file mode 100644 index 00000000..981b9755 --- /dev/null +++ b/plugins/ipsec_linux/netlink_xfrm.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include + +struct mnl_socket; + +struct xfrm_algobuf { + struct xfrm_algo algo; + uint8_t buf[sizeof(struct xfrm_algo) + 128]; +}; + +struct mnl_socket *xfrm_init_mnl_socket(void); + +int xfrm_spi_alloc(struct mnl_socket *mnl_sock, uint32_t reqid, uint32_t *spi_out, + const struct sockaddr *src, const struct sockaddr *dst); + +int xfrm_sa_add(struct mnl_socket *mnl_sock, uint32_t reqid, + const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi, + const struct xfrm_algo *auth_algo, const struct xfrm_algo *ciph_algo); + +int xfrm_sa_del(struct mnl_socket *mnl_sock, + const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi); + +int xfrm_policy_add(struct mnl_socket *mnl_sock, + const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi, bool dir_in); + +int xfrm_policy_del(struct mnl_socket *mnl_sock, + const struct sockaddr *src, const struct sockaddr *dst, bool dir_in); diff --git a/plugins/ipsec_linux/tool.c b/plugins/ipsec_linux/tool.c new file mode 100644 index 00000000..e4ac1f85 --- /dev/null +++ b/plugins/ipsec_linux/tool.c @@ -0,0 +1,440 @@ +/* Demo/Debug tool for Linux kernel IPsec interfacing via netlink XFRM + * + * Copyright (C) 2021 Harald Welte + * + * DOUBANGO is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * DOUBANGO is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DOUBANGO. + */ + +#include +#include +#include +#include +#include +#include + +#define _GNU_SOURCE +#include + +#include +#include +#include + +#include "netlink_xfrm.h" + +static struct mnl_socket *g_mnl_socket; + +static int gai_helper(struct sockaddr_storage *out, const char *node, const char *port) +{ + struct addrinfo hints = { + .ai_flags = AI_NUMERICSERV | AI_NUMERICHOST, + }; + struct addrinfo *res; + int rc; + + rc = getaddrinfo(node, port, &hints, &res); + if (rc != 0) { + fprintf(stderr, "getaddrinfo(%s): %s\n", node, gai_strerror(rc)); + return -1; + } + + memcpy(out, res->ai_addr, res->ai_addrlen); + + freeaddrinfo(res); + + return 0; +} + +static int cmd_alloc_spi(int argc, char **argv) +{ + char *src_ip_str = NULL; + char *dst_ip_str = NULL; + struct sockaddr_storage src_addr, dst_addr; + uint32_t spi_out; + int rc; + + while (1) { + int option_index = 0, c; + static const struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"src-ip", 1, 0, 's'}, + {"dst-ip", 1, 0, 'd'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hs:d:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + break; + case 's': + src_ip_str = optarg; + break; + case 'd': + dst_ip_str = optarg; + break; + } + } + + if (!src_ip_str || !dst_ip_str) { + fprintf(stderr, "Both src and dst IP must be provided\n"); + exit(1); + } + + gai_helper(&src_addr, src_ip_str, NULL); + gai_helper(&dst_addr, dst_ip_str, NULL); + + rc = xfrm_spi_alloc(g_mnl_socket, 2342, &spi_out, (const struct sockaddr *)&src_addr, (const struct sockaddr *)&dst_addr); + if (rc < 0) { + fprintf(stderr, "Error allocating SPI: %s\n", strerror(errno)); + exit(1); + } + printf("Allocated SPI 0x%08x\n", spi_out); + + return 0; +} + +static int cmd_sa_add(int argc, char **argv) +{ + char *src_ip_str = NULL; + char *dst_ip_str = NULL; + char *sport = NULL; + char *dport = NULL; + char *ciph_alg_str = NULL; + char *auth_alg_str = NULL; + struct sockaddr_storage src_addr, dst_addr; + struct xfrm_algobuf auth_algo, ciph_algo; + uint32_t spi = 0; + int rc; + + memset(&auth_algo, 0, sizeof(auth_algo)); + memset(&ciph_algo, 0, sizeof(ciph_algo)); + + while (1) { + int option_index = 0, c; + static const struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"src-ip", 1, 0, 's'}, + {"dst-ip", 1, 0, 'd'}, + {"src-port", 1, 0, 'S'}, + {"dst-port", 1, 0, 'D'}, + {"spi", 1, 0, 'p'}, + {"auth-alg", 1, 0, 'a'}, + {"ciph-alg", 1, 0, 'c'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hs:d:S:D:p:a:c:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + break; + case 's': + src_ip_str = optarg; + break; + case 'd': + dst_ip_str = optarg; + break; + case 'S': + sport = optarg; + break; + case 'D': + dport = optarg; + break; + case 'p': + spi = atoi(optarg); + break; + case 'a': + auth_alg_str = optarg; + break; + case 'c': + ciph_alg_str = optarg; + break; + } + } + + if (!src_ip_str || !dst_ip_str) { + fprintf(stderr, "Both src and dst IP must be provided\n"); + exit(1); + } + + if (!sport || !dport) { + fprintf(stderr, "Both src and dst port must be provided\n"); + exit(1); + } + + if (!auth_alg_str) { + fprintf(stderr, "Auth alg must be provided\n"); + exit(1); + } + + if (!ciph_alg_str) + ciph_alg_str = "cipher_null"; + + strcpy(auth_algo.algo.alg_name, auth_alg_str); + /* FIXME: key len/data */ + + strcpy(ciph_algo.algo.alg_name, ciph_alg_str); + /* FIXME: key len/data */ + + gai_helper(&src_addr, src_ip_str, sport); + gai_helper(&dst_addr, dst_ip_str, dport); + + rc = xfrm_sa_add(g_mnl_socket, 2325, (struct sockaddr *)&src_addr, (struct sockaddr *)&dst_addr, + spi, &auth_algo.algo, &ciph_algo.algo); + if (rc < 0) { + fprintf(stderr, "Error adding SA: %s\n", strerror(errno)); + exit(1); + } + + return 0; +} + +static int cmd_sa_del(int argc, char **argv) +{ + char *src_ip_str = NULL; + char *dst_ip_str = NULL; + char *sport = NULL; + char *dport = NULL; + struct sockaddr_storage src_addr, dst_addr; + uint32_t spi = 0; + int rc; + + while (1) { + int option_index = 0, c; + static const struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"src-ip", 1, 0, 's'}, + {"dst-ip", 1, 0, 'd'}, + {"src-port", 1, 0, 'S'}, + {"dst-port", 1, 0, 'D'}, + {"spi", 1, 0, 'p'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hs:d:S:D:p:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + break; + case 's': + src_ip_str = optarg; + break; + case 'd': + dst_ip_str = optarg; + break; + case 'S': + sport = optarg; + break; + case 'D': + dport = optarg; + break; + case 'p': + spi = atoi(optarg); + break; + } + } + + if (!src_ip_str || !dst_ip_str) { + fprintf(stderr, "Both src and dst IP must be provided\n"); + exit(1); + } + + if (!sport || !dport) { + fprintf(stderr, "Both src and dst port must be provided\n"); + exit(1); + } + + gai_helper(&src_addr, src_ip_str, sport); + gai_helper(&dst_addr, dst_ip_str, dport); + + rc = xfrm_sa_del(g_mnl_socket, (struct sockaddr *)&src_addr, (struct sockaddr *)&dst_addr, spi); + if (rc < 0) { + fprintf(stderr, "Error deleting SA: %s\n", strerror(errno)); + exit(1); + } + + return 0; +} + + +static int cmd_policy_add(int argc, char **argv) +{ + char *src_ip_str = NULL; + char *dst_ip_str = NULL; + char *sport = NULL; + char *dport = NULL; + struct sockaddr_storage src_addr, dst_addr; + uint32_t spi = 0; + int rc; + + while (1) { + int option_index = 0, c; + static const struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"src-ip", 1, 0, 's'}, + {"dst-ip", 1, 0, 'd'}, + {"src-port", 1, 0, 'S'}, + {"dst-port", 1, 0, 'D'}, + {"spi", 1, 0, 'p'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hs:d:S:D:p:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + break; + case 's': + src_ip_str = optarg; + break; + case 'd': + dst_ip_str = optarg; + break; + case 'S': + sport = optarg; + break; + case 'D': + dport = optarg; + break; + case 'p': + spi = atoi(optarg); + break; + } + } + + if (!src_ip_str || !dst_ip_str) { + fprintf(stderr, "Both src and dst IP must be provided\n"); + exit(1); + } + + if (!sport || !dport) { + fprintf(stderr, "Both src and dst port must be provided\n"); + exit(1); + } + + gai_helper(&src_addr, src_ip_str, sport); + gai_helper(&dst_addr, dst_ip_str, dport); + + rc = xfrm_policy_add(g_mnl_socket, (struct sockaddr *)&src_addr, (struct sockaddr *)&dst_addr, + spi, true); + if (rc < 0) { + fprintf(stderr, "Error adding SA: %s\n", strerror(errno)); + exit(1); + } + + return 0; +} + +static int cmd_policy_del(int argc, char **argv) +{ + char *src_ip_str = NULL; + char *dst_ip_str = NULL; + char *sport = NULL; + char *dport = NULL; + struct sockaddr_storage src_addr, dst_addr; + int rc; + + while (1) { + int option_index = 0, c; + static const struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"src-ip", 1, 0, 's'}, + {"dst-ip", 1, 0, 'd'}, + {"src-port", 1, 0, 'S'}, + {"dst-port", 1, 0, 'D'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hs:d:S:D:p:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + break; + case 's': + src_ip_str = optarg; + break; + case 'd': + dst_ip_str = optarg; + break; + case 'S': + sport = optarg; + break; + case 'D': + dport = optarg; + break; + } + } + + if (!src_ip_str || !dst_ip_str) { + fprintf(stderr, "Both src and dst IP must be provided\n"); + exit(1); + } + + if (!sport || !dport) { + fprintf(stderr, "Both src and dst port must be provided\n"); + exit(1); + } + + gai_helper(&src_addr, src_ip_str, sport); + gai_helper(&dst_addr, dst_ip_str, dport); + + rc = xfrm_policy_del(g_mnl_socket, (struct sockaddr *)&src_addr, (struct sockaddr *)&dst_addr, + true); + if (rc < 0) { + fprintf(stderr, "Error adding SA: %s\n", strerror(errno)); + exit(1); + } + + return 0; +} + + +int main(int argc, char **argv) +{ + const char *cmd; + + if (argc <= 1) { + fprintf(stderr, "Missing first argument (command)\n"); + exit(1); + } + cmd = argv[1]; + + g_mnl_socket = xfrm_init_mnl_socket(); + + if (!strcmp(cmd, "spi-alloc")) + cmd_alloc_spi(argc-1, argv+1); + else if (!strcmp(cmd, "sa-add")) + cmd_sa_add(argc-1, argv+1); + else if (!strcmp(cmd, "sa-del")) + cmd_sa_del(argc-1, argv+1); + else if (!strcmp(cmd, "policy-add")) + cmd_policy_add(argc-1, argv+1); + else if (!strcmp(cmd, "policy-del")) + cmd_policy_del(argc-1, argv+1); + else { + fprintf(stderr, "Invalid first argument (command)\n"); + exit(1); + } + +}