446 lines
10 KiB
C
446 lines
10 KiB
C
/*
|
|
* Copyright (C) 2019-2023 Tobias Brunner
|
|
*
|
|
* Copyright (C) secunet Security Networks AG
|
|
*
|
|
* This program 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. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* This program 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.
|
|
*/
|
|
|
|
#include <net/if.h>
|
|
|
|
#include "kernel_netlink_xfrmi.h"
|
|
#include "kernel_netlink_shared.h"
|
|
|
|
#ifndef IFLA_XFRM_MAX
|
|
enum {
|
|
IFLA_XFRM_UNSPEC,
|
|
IFLA_XFRM_LINK,
|
|
IFLA_XFRM_IF_ID,
|
|
__IFLA_XFRM_MAX
|
|
};
|
|
#define IFLA_XFRM_MAX (__IFLA_XFRM_MAX - 1)
|
|
#endif
|
|
|
|
typedef struct private_kernel_netlink_xfrmi_t private_kernel_netlink_xfrmi_t;
|
|
|
|
/**
|
|
* Private data
|
|
*/
|
|
struct private_kernel_netlink_xfrmi_t {
|
|
|
|
/**
|
|
* Public interface
|
|
*/
|
|
kernel_netlink_xfrmi_t public;
|
|
|
|
/**
|
|
* Netlink socket
|
|
*/
|
|
netlink_socket_t *socket;
|
|
};
|
|
|
|
/**
|
|
* "up" the interface with the given name
|
|
*/
|
|
static bool interface_up(private_kernel_netlink_xfrmi_t *this, char *name)
|
|
{
|
|
netlink_buf_t request;
|
|
struct nlmsghdr *hdr;
|
|
struct ifinfomsg *msg;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
|
|
hdr = &request.hdr;
|
|
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
hdr->nlmsg_type = RTM_SETLINK;
|
|
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
|
|
|
|
msg = NLMSG_DATA(hdr);
|
|
msg->ifi_family = AF_UNSPEC;
|
|
msg->ifi_change |= IFF_UP;
|
|
msg->ifi_flags |= IFF_UP;
|
|
|
|
netlink_add_attribute(hdr, IFLA_IFNAME, chunk_from_str(name),
|
|
sizeof(request));
|
|
|
|
if (this->socket->send_ack(this->socket, hdr) != SUCCESS)
|
|
{
|
|
DBG1(DBG_KNL, "failed to bring up XFRM interface '%s'", name);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
METHOD(kernel_netlink_xfrmi_t, create, bool,
|
|
private_kernel_netlink_xfrmi_t *this, char *name, uint32_t if_id,
|
|
char *phys, uint32_t mtu)
|
|
{
|
|
netlink_buf_t request;
|
|
struct nlmsghdr *hdr;
|
|
struct ifinfomsg *msg;
|
|
struct rtattr *linkinfo, *info_data;
|
|
uint32_t ifindex = 0;
|
|
|
|
if (phys)
|
|
{
|
|
ifindex = if_nametoindex(phys);
|
|
if (!ifindex)
|
|
{
|
|
DBG1(DBG_KNL, "physical interface '%s' not found", phys);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
|
|
hdr = &request.hdr;
|
|
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
|
|
hdr->nlmsg_type = RTM_NEWLINK;
|
|
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
|
|
|
|
msg = NLMSG_DATA(hdr);
|
|
msg->ifi_family = AF_UNSPEC;
|
|
|
|
netlink_add_attribute(hdr, IFLA_IFNAME, chunk_from_str(name),
|
|
sizeof(request));
|
|
if (mtu)
|
|
{
|
|
netlink_add_attribute(hdr, IFLA_MTU, chunk_from_thing(mtu),
|
|
sizeof(request));
|
|
}
|
|
|
|
linkinfo = netlink_nested_start(hdr, sizeof(request), IFLA_LINKINFO);
|
|
|
|
netlink_add_attribute(hdr, IFLA_INFO_KIND, chunk_from_str("xfrm"),
|
|
sizeof(request));
|
|
|
|
info_data = netlink_nested_start(hdr, sizeof(request), IFLA_INFO_DATA);
|
|
|
|
netlink_add_attribute(hdr, IFLA_XFRM_IF_ID, chunk_from_thing(if_id),
|
|
sizeof(request));
|
|
if (ifindex)
|
|
{
|
|
netlink_add_attribute(hdr, IFLA_XFRM_LINK, chunk_from_thing(ifindex),
|
|
sizeof(request));
|
|
}
|
|
|
|
netlink_nested_end(hdr, info_data);
|
|
netlink_nested_end(hdr, linkinfo);
|
|
|
|
switch (this->socket->send_ack(this->socket, hdr))
|
|
{
|
|
case SUCCESS:
|
|
return interface_up(this, name);
|
|
case ALREADY_DONE:
|
|
DBG1(DBG_KNL, "XFRM interface '%s' already exists", name);
|
|
break;
|
|
default:
|
|
DBG1(DBG_KNL, "failed to create XFRM interface '%s'", name);
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/** enumerator over XFRM interfaces */
|
|
typedef struct {
|
|
/** public interface */
|
|
enumerator_t public;
|
|
/** message from the kernel */
|
|
struct nlmsghdr *msg;
|
|
/** current message from the kernel */
|
|
struct nlmsghdr *current;
|
|
/** remaining length */
|
|
size_t len;
|
|
/** current physical interface (if any) */
|
|
char phys[IFNAMSIZ];
|
|
} interface_enumerator_t;
|
|
|
|
METHOD(enumerator_t, destroy_enumerator, void,
|
|
interface_enumerator_t *this)
|
|
{
|
|
free(this->msg);
|
|
free(this);
|
|
}
|
|
|
|
/**
|
|
* Parse attributes nested in IFLA_INFO_DATA
|
|
*/
|
|
static void parse_info_data(struct rtattr *rta, size_t rtasize, bool *has_phys,
|
|
char *phys, uint32_t *if_id)
|
|
{
|
|
uint32_t ifindex;
|
|
|
|
while (RTA_OK(rta, rtasize))
|
|
{
|
|
switch (rta->rta_type)
|
|
{
|
|
case IFLA_XFRM_IF_ID:
|
|
if (RTA_PAYLOAD(rta) == sizeof(*if_id))
|
|
{
|
|
*if_id = *(uint32_t*)RTA_DATA(rta);
|
|
}
|
|
break;
|
|
case IFLA_XFRM_LINK:
|
|
if (RTA_PAYLOAD(rta) == sizeof(ifindex))
|
|
{
|
|
ifindex = *(uint32_t*)RTA_DATA(rta);
|
|
if (ifindex)
|
|
{
|
|
if_indextoname(ifindex, phys);
|
|
*has_phys = TRUE;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
rta = RTA_NEXT(rta, rtasize);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse attributes nested in IFLA_LINKINFO
|
|
*/
|
|
static void parse_linkinfo(struct rtattr *rta, size_t rtasize, bool *type_match,
|
|
bool *has_phys, char *phys, uint32_t *if_id)
|
|
{
|
|
while (RTA_OK(rta, rtasize))
|
|
{
|
|
switch (rta->rta_type)
|
|
{
|
|
case IFLA_INFO_KIND:
|
|
*type_match = streq("xfrm", RTA_DATA(rta));
|
|
break;
|
|
case IFLA_INFO_DATA:
|
|
parse_info_data(RTA_DATA(rta), RTA_PAYLOAD(rta), has_phys,
|
|
phys, if_id);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
rta = RTA_NEXT(rta, rtasize);
|
|
}
|
|
}
|
|
|
|
METHOD(enumerator_t, enumerate, bool,
|
|
interface_enumerator_t *this, va_list args)
|
|
{
|
|
char **name;
|
|
uint32_t *if_id, *mtu;
|
|
char **phys;
|
|
|
|
VA_ARGS_VGET(args, name, if_id, phys, mtu);
|
|
|
|
if (!this->current)
|
|
{
|
|
this->current = this->msg;
|
|
}
|
|
else
|
|
{
|
|
this->current = NLMSG_NEXT(this->current, this->len);
|
|
}
|
|
|
|
while (NLMSG_OK(this->current, this->len))
|
|
{
|
|
switch (this->current->nlmsg_type)
|
|
{
|
|
case NLMSG_DONE:
|
|
break;
|
|
case RTM_NEWLINK:
|
|
{
|
|
struct ifinfomsg *msg = NLMSG_DATA(this->current);
|
|
struct rtattr *rta = IFLA_RTA(msg);
|
|
size_t rtasize = IFLA_PAYLOAD(this->current);
|
|
bool type_match = FALSE, has_phys = FALSE;
|
|
|
|
*name = NULL;
|
|
|
|
while (RTA_OK(rta, rtasize))
|
|
{
|
|
switch (rta->rta_type)
|
|
{
|
|
case IFLA_IFNAME:
|
|
*name = RTA_DATA(rta);
|
|
break;
|
|
case IFLA_MTU:
|
|
if (mtu && RTA_PAYLOAD(rta) == sizeof(*mtu))
|
|
{
|
|
*mtu = *(uint32_t*)RTA_DATA(rta);
|
|
}
|
|
break;
|
|
case IFLA_LINKINFO:
|
|
parse_linkinfo(RTA_DATA(rta), RTA_PAYLOAD(rta),
|
|
&type_match, &has_phys, this->phys,
|
|
if_id);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
rta = RTA_NEXT(rta, rtasize);
|
|
}
|
|
if (*name && type_match)
|
|
{
|
|
if (phys)
|
|
{
|
|
*phys = has_phys ? this->phys : NULL;
|
|
}
|
|
return TRUE;
|
|
}
|
|
/* fall-through */
|
|
}
|
|
default:
|
|
this->current = NLMSG_NEXT(this->current, this->len);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
METHOD(kernel_netlink_xfrmi_t, create_enumerator, enumerator_t*,
|
|
private_kernel_netlink_xfrmi_t *this)
|
|
{
|
|
netlink_buf_t request;
|
|
struct nlmsghdr *hdr, *out;
|
|
struct ifinfomsg *msg;
|
|
struct rtattr *linkinfo;
|
|
size_t len;
|
|
interface_enumerator_t *enumerator;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
|
|
hdr = &request.hdr;
|
|
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
|
|
hdr->nlmsg_type = RTM_GETLINK;
|
|
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
|
|
|
|
msg = NLMSG_DATA(hdr);
|
|
msg->ifi_family = AF_UNSPEC;
|
|
|
|
/* if the kernel doesn't know the type we set here, it will just return all
|
|
* interfaces, so we filter the type ourselves too in the callback */
|
|
linkinfo = netlink_nested_start(hdr, sizeof(request), IFLA_LINKINFO);
|
|
|
|
netlink_add_attribute(hdr, IFLA_INFO_KIND, chunk_from_str("xfrm"),
|
|
sizeof(request));
|
|
|
|
netlink_nested_end(hdr, linkinfo);
|
|
|
|
if (this->socket->send(this->socket, hdr, &out, &len) != SUCCESS)
|
|
{
|
|
DBG2(DBG_KNL, "enumerating XFRM interfaces failed");
|
|
return enumerator_create_empty();
|
|
}
|
|
|
|
INIT(enumerator,
|
|
.public = {
|
|
.enumerate = enumerator_enumerate_default,
|
|
.venumerate = _enumerate,
|
|
.destroy = _destroy_enumerator,
|
|
},
|
|
.msg = out,
|
|
.len = len,
|
|
);
|
|
return &enumerator->public;
|
|
}
|
|
|
|
METHOD(kernel_netlink_xfrmi_t, delete, bool,
|
|
private_kernel_netlink_xfrmi_t *this, char *name)
|
|
{
|
|
netlink_buf_t request;
|
|
struct nlmsghdr *hdr;
|
|
struct ifinfomsg *msg;
|
|
struct rtattr *linkinfo;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
|
|
hdr = &request.hdr;
|
|
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
hdr->nlmsg_type = RTM_DELLINK;
|
|
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
|
|
|
|
msg = NLMSG_DATA(hdr);
|
|
msg->ifi_family = AF_UNSPEC;
|
|
|
|
netlink_add_attribute(hdr, IFLA_IFNAME, chunk_from_str(name),
|
|
sizeof(request));
|
|
|
|
/* the type doesn't seem to matter, but let's still set it */
|
|
linkinfo = netlink_nested_start(hdr, sizeof(request), IFLA_LINKINFO);
|
|
|
|
netlink_add_attribute(hdr, IFLA_INFO_KIND, chunk_from_str("xfrm"),
|
|
sizeof(request));
|
|
|
|
netlink_nested_end(hdr, linkinfo);
|
|
|
|
switch (this->socket->send_ack(this->socket, hdr))
|
|
{
|
|
case SUCCESS:
|
|
return TRUE;
|
|
case NOT_FOUND:
|
|
DBG1(DBG_KNL, "XFRM interface '%s' not found to delete", name);
|
|
break;
|
|
default:
|
|
DBG1(DBG_KNL, "failed to delete XFRM interface '%s'", name);
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void kernel_netlink_xfrmi_destroy(kernel_netlink_xfrmi_t *pub)
|
|
{
|
|
private_kernel_netlink_xfrmi_t *this = (private_kernel_netlink_xfrmi_t*)pub;
|
|
|
|
this->socket->destroy(this->socket);
|
|
free(this);
|
|
}
|
|
|
|
/*
|
|
* Described in header
|
|
*/
|
|
kernel_netlink_xfrmi_t *kernel_netlink_xfrmi_create(bool test)
|
|
{
|
|
private_kernel_netlink_xfrmi_t *this;
|
|
char name[IFNAMSIZ] = {};
|
|
uint32_t if_id;
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.create = _create,
|
|
.create_enumerator = _create_enumerator,
|
|
.delete = _delete,
|
|
},
|
|
.socket = netlink_socket_create(NETLINK_ROUTE, NULL, FALSE),
|
|
);
|
|
|
|
if (!this->socket)
|
|
{
|
|
free(this);
|
|
return NULL;
|
|
}
|
|
if (test)
|
|
{
|
|
/* try to create an XFRM interface to see if the kernel supports it, use
|
|
* a random ID and name for the test to avoid conflicts */
|
|
if_id = random();
|
|
snprintf(name, sizeof(name), "xfrmi-test-%u", if_id);
|
|
|
|
if (!create(this, name, if_id, NULL, 0))
|
|
{
|
|
kernel_netlink_xfrmi_destroy(&this->public);
|
|
return NULL;
|
|
}
|
|
DBG2(DBG_KNL, "XFRM interfaces supported by kernel");
|
|
delete(this, name);
|
|
}
|
|
return &this->public;
|
|
}
|