Add Linux IPsec plugin
This adds an IPsec plugin for the Linux kernel. It should allow doubango to establish IMS registration with a real-world P-CSCF, which always requires mandatory IPsec (ESP in transport mode).
This commit is contained in:
parent
9155daeeba
commit
557b37ec91
|
@ -0,0 +1,459 @@
|
|||
/* doubango tinyIPsec plugin for Linux
|
||||
*
|
||||
* Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
|
||||
*
|
||||
* 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 <arpa/inet.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,432 @@
|
|||
/* Linux kernel IPsec interfacing via netlink XFRM
|
||||
*
|
||||
* Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
|
||||
*
|
||||
* 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 <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <libmnl/libmnl.h>
|
||||
#include <linux/xfrm.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/xfrm.h>
|
||||
|
||||
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);
|
|
@ -0,0 +1,440 @@
|
|||
/* Demo/Debug tool for Linux kernel IPsec interfacing via netlink XFRM
|
||||
*
|
||||
* Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
|
||||
*
|
||||
* 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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <getopt.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue