280 lines
9.8 KiB
C
280 lines
9.8 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <linux/fou.h>
|
|
#include <net/if.h>
|
|
#include <netinet/in.h>
|
|
#include <linux/ip.h>
|
|
|
|
#include "conf-parser.h"
|
|
#include "fou-tunnel.h"
|
|
#include "ip-protocol-list.h"
|
|
#include "netlink-util.h"
|
|
#include "networkd-manager.h"
|
|
#include "parse-util.h"
|
|
#include "string-table.h"
|
|
#include "string-util.h"
|
|
#include "util.h"
|
|
|
|
static const char* const fou_encap_type_table[_NETDEV_FOO_OVER_UDP_ENCAP_MAX] = {
|
|
[NETDEV_FOO_OVER_UDP_ENCAP_DIRECT] = "FooOverUDP",
|
|
[NETDEV_FOO_OVER_UDP_ENCAP_GUE] = "GenericUDPEncapsulation",
|
|
};
|
|
|
|
DEFINE_STRING_TABLE_LOOKUP(fou_encap_type, FooOverUDPEncapType);
|
|
DEFINE_CONFIG_PARSE_ENUM(config_parse_fou_encap_type, fou_encap_type, FooOverUDPEncapType,
|
|
"Failed to parse Encapsulation=");
|
|
|
|
static int netdev_fill_fou_tunnel_message(NetDev *netdev, sd_netlink_message **ret) {
|
|
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
|
|
FouTunnel *t;
|
|
uint8_t encap_type;
|
|
int r;
|
|
|
|
assert(netdev);
|
|
|
|
t = FOU(netdev);
|
|
|
|
assert(t);
|
|
|
|
r = sd_genl_message_new(netdev->manager->genl, SD_GENL_FOU, FOU_CMD_ADD, &m);
|
|
if (r < 0)
|
|
return log_netdev_error_errno(netdev, r, "Failed to allocate generic netlink message: %m");
|
|
|
|
r = sd_netlink_message_append_u16(m, FOU_ATTR_PORT, htobe16(t->port));
|
|
if (r < 0)
|
|
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_PORT attribute: %m");
|
|
|
|
if (IN_SET(t->peer_family, AF_INET, AF_INET6)) {
|
|
r = sd_netlink_message_append_u16(m, FOU_ATTR_PEER_PORT, htobe16(t->peer_port));
|
|
if (r < 0)
|
|
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_PEER_PORT attribute: %m");
|
|
}
|
|
|
|
switch (t->fou_encap_type) {
|
|
case NETDEV_FOO_OVER_UDP_ENCAP_DIRECT:
|
|
encap_type = FOU_ENCAP_DIRECT;
|
|
break;
|
|
case NETDEV_FOO_OVER_UDP_ENCAP_GUE:
|
|
encap_type = FOU_ENCAP_GUE;
|
|
break;
|
|
default:
|
|
assert_not_reached("invalid encap type");
|
|
}
|
|
|
|
r = sd_netlink_message_append_u8(m, FOU_ATTR_TYPE, encap_type);
|
|
if (r < 0)
|
|
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_TYPE attribute: %m");
|
|
|
|
r = sd_netlink_message_append_u8(m, FOU_ATTR_AF, AF_INET);
|
|
if (r < 0)
|
|
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_AF attribute: %m");
|
|
|
|
r = sd_netlink_message_append_u8(m, FOU_ATTR_IPPROTO, t->fou_protocol);
|
|
if (r < 0)
|
|
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_IPPROTO attribute: %m");
|
|
|
|
if (t->local_family == AF_INET) {
|
|
r = sd_netlink_message_append_in_addr(m, FOU_ATTR_LOCAL_V4, &t->local.in);
|
|
if (r < 0)
|
|
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_LOCAL_V4 attribute: %m");
|
|
} else if (t->local_family == AF_INET6) {
|
|
r = sd_netlink_message_append_in6_addr(m, FOU_ATTR_LOCAL_V6, &t->local.in6);
|
|
if (r < 0)
|
|
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_LOCAL_V6 attribute: %m");
|
|
}
|
|
|
|
if (t->peer_family == AF_INET) {
|
|
r = sd_netlink_message_append_in_addr(m, FOU_ATTR_PEER_V4, &t->peer.in);
|
|
if (r < 0)
|
|
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_PEER_V4 attribute: %m");
|
|
} else if (t->peer_family == AF_INET6){
|
|
r = sd_netlink_message_append_in6_addr(m, FOU_ATTR_PEER_V6, &t->peer.in6);
|
|
if (r < 0)
|
|
return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_PEER_V6 attribute: %m");
|
|
}
|
|
|
|
*ret = TAKE_PTR(m);
|
|
return 0;
|
|
}
|
|
|
|
static int fou_tunnel_create_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
|
|
int r;
|
|
|
|
assert(netdev);
|
|
assert(netdev->state != _NETDEV_STATE_INVALID);
|
|
|
|
r = sd_netlink_message_get_errno(m);
|
|
if (r == -EEXIST)
|
|
log_netdev_info(netdev, "netdev exists, using existing without changing its parameters");
|
|
else if (r < 0) {
|
|
log_netdev_warning_errno(netdev, r, "netdev could not be created: %m");
|
|
netdev_drop(netdev);
|
|
|
|
return 1;
|
|
}
|
|
|
|
log_netdev_debug(netdev, "FooOverUDP tunnel is created");
|
|
return 1;
|
|
}
|
|
|
|
static int netdev_fou_tunnel_create(NetDev *netdev) {
|
|
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
|
|
int r;
|
|
|
|
assert(netdev);
|
|
assert(FOU(netdev));
|
|
|
|
r = netdev_fill_fou_tunnel_message(netdev, &m);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = netlink_call_async(netdev->manager->genl, NULL, m, fou_tunnel_create_handler,
|
|
netdev_destroy_callback, netdev);
|
|
if (r < 0)
|
|
return log_netdev_error_errno(netdev, r, "Failed to create FooOverUDP tunnel: %m");
|
|
|
|
netdev_ref(netdev);
|
|
return 0;
|
|
}
|
|
|
|
int config_parse_ip_protocol(
|
|
const char *unit,
|
|
const char *filename,
|
|
unsigned line,
|
|
const char *section,
|
|
unsigned section_line,
|
|
const char *lvalue,
|
|
int ltype,
|
|
const char *rvalue,
|
|
void *data,
|
|
void *userdata) {
|
|
|
|
uint8_t *ret = data;
|
|
unsigned protocol;
|
|
/* linux/fou.h defines the netlink field as one byte, so we need to reject protocols numbers that
|
|
* don't fit in one byte. */
|
|
int r;
|
|
|
|
assert(filename);
|
|
assert(section);
|
|
assert(lvalue);
|
|
assert(rvalue);
|
|
assert(data);
|
|
|
|
r = parse_ip_protocol(rvalue);
|
|
if (r >= 0)
|
|
protocol = r;
|
|
else {
|
|
r = safe_atou(rvalue, &protocol);
|
|
if (r < 0)
|
|
log_syntax(unit, LOG_WARNING, filename, line, r,
|
|
"Failed to parse IP protocol '%s' for FooOverUDP tunnel, "
|
|
"ignoring assignment: %m", rvalue);
|
|
return 0;
|
|
}
|
|
|
|
if (protocol > UINT8_MAX) {
|
|
log_syntax(unit, LOG_WARNING, filename, line, 0,
|
|
"IP protocol '%s' for FooOverUDP tunnel out of range, "
|
|
"ignoring assignment: %m", rvalue);
|
|
return 0;
|
|
}
|
|
|
|
*ret = protocol;
|
|
return 0;
|
|
}
|
|
|
|
int config_parse_fou_tunnel_address(
|
|
const char *unit,
|
|
const char *filename,
|
|
unsigned line,
|
|
const char *section,
|
|
unsigned section_line,
|
|
const char *lvalue,
|
|
int ltype,
|
|
const char *rvalue,
|
|
void *data,
|
|
void *userdata) {
|
|
|
|
union in_addr_union *addr = data;
|
|
FouTunnel *t = userdata;
|
|
int r, *f;
|
|
|
|
assert(filename);
|
|
assert(lvalue);
|
|
assert(rvalue);
|
|
assert(data);
|
|
|
|
if (streq(lvalue, "Local"))
|
|
f = &t->local_family;
|
|
else
|
|
f = &t->peer_family;
|
|
|
|
r = in_addr_from_string_auto(rvalue, f, addr);
|
|
if (r < 0)
|
|
log_syntax(unit, LOG_WARNING, filename, line, r,
|
|
"FooOverUDP tunnel '%s' address is invalid, ignoring assignment: %s",
|
|
lvalue, rvalue);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int netdev_fou_tunnel_verify(NetDev *netdev, const char *filename) {
|
|
FouTunnel *t;
|
|
|
|
assert(netdev);
|
|
assert(filename);
|
|
|
|
t = FOU(netdev);
|
|
|
|
assert(t);
|
|
|
|
switch (t->fou_encap_type) {
|
|
case NETDEV_FOO_OVER_UDP_ENCAP_DIRECT:
|
|
if (t->fou_protocol <= 0)
|
|
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
|
|
"FooOverUDP protocol not configured in %s. Rejecting configuration.",
|
|
filename);
|
|
break;
|
|
case NETDEV_FOO_OVER_UDP_ENCAP_GUE:
|
|
if (t->fou_protocol > 0)
|
|
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
|
|
"FooOverUDP GUE can't be set with protocol configured in %s. Rejecting configuration.",
|
|
filename);
|
|
break;
|
|
default:
|
|
assert_not_reached("Invalid fou encap type");
|
|
}
|
|
|
|
if (t->peer_family == AF_UNSPEC && t->peer_port > 0)
|
|
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
|
|
"FooOverUDP peer port is set but peer address not configured in %s. Rejecting configuration.",
|
|
filename);
|
|
else if (t->peer_family != AF_UNSPEC && t->peer_port == 0)
|
|
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
|
|
"FooOverUDP peer port not set but peer address is configured in %s. Rejecting configuration.",
|
|
filename);
|
|
return 0;
|
|
}
|
|
|
|
static void fou_tunnel_init(NetDev *netdev) {
|
|
FouTunnel *t;
|
|
|
|
assert(netdev);
|
|
|
|
t = FOU(netdev);
|
|
|
|
assert(t);
|
|
|
|
t->fou_encap_type = NETDEV_FOO_OVER_UDP_ENCAP_DIRECT;
|
|
}
|
|
|
|
const NetDevVTable foutnl_vtable = {
|
|
.object_size = sizeof(FouTunnel),
|
|
.init = fou_tunnel_init,
|
|
.sections = NETDEV_COMMON_SECTIONS "FooOverUDP\0",
|
|
.create = netdev_fou_tunnel_create,
|
|
.create_type = NETDEV_CREATE_INDEPENDENT,
|
|
.config_verify = netdev_fou_tunnel_verify,
|
|
};
|