relay role implementation

This commit is contained in:
Yegor Alexeyev
2021-03-15 21:19:52 +01:00
committed by Zbigniew Jędrzejewski-Szmek
parent d5bfddf037
commit c95df5879e
17 changed files with 222 additions and 21 deletions

View File

@@ -2294,6 +2294,17 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>RelayTarget=</varname></term>
<listitem>
<para>Takes an IPv4 address, which must be in the format
described in
<citerefentry project='man-pages'><refentrytitle>inet_pton</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
Turns this DHCP server into a DHCP relay agent. See <ulink url="https://tools.ietf.org/html/rfc1542">RFC 1542</ulink>.
The address is the address of DHCP server or another relay agent to forward DHCP messages to and from.</para>
Check also BindToInterface= option. Turning it off is required for relaying messages outside.
</listitem>
</varlistentry>
<varlistentry>
<term><varname>PoolOffset=</varname></term>
<term><varname>PoolSize=</varname></term>

View File

@@ -39,6 +39,7 @@ typedef struct DHCPLease {
} DHCPLease;
struct sd_dhcp_server {
struct in_addr relay_target;
unsigned n_ref;
sd_event *event;

View File

@@ -114,6 +114,12 @@ int sd_dhcp_server_is_running(sd_dhcp_server *server) {
return !!server->receive_message;
}
int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server) {
assert_return(server, -EINVAL);
return in4_addr_is_set(&server->relay_target);
}
void client_id_hash_func(const DHCPClientId *id, struct siphash *state) {
assert(id);
assert(id->length);
@@ -343,10 +349,27 @@ static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
return 0;
}
static bool requested_broadcast(DHCPRequest *req) {
assert(req);
static bool requested_broadcast(DHCPMessage *message) {
assert(message);
return message->flags & htobe16(0x8000);
}
return req->message->flags & htobe16(0x8000);
static int dhcp_server_send(sd_dhcp_server *server, be32_t destination, uint16_t destination_port,
DHCPPacket *packet, size_t optoffset, bool l2_broadcast) {
if (destination != INADDR_ANY)
return dhcp_server_send_udp(server, destination,
destination_port, &packet->dhcp,
sizeof(DHCPMessage) + optoffset);
else if (l2_broadcast)
return dhcp_server_send_udp(server, INADDR_BROADCAST,
destination_port, &packet->dhcp,
sizeof(DHCPMessage) + optoffset);
else
/* we cannot send UDP packet to specific MAC address when the
address is not yet configured, so must fall back to raw
packets */
return dhcp_server_send_unicast_raw(server, packet,
sizeof(DHCPPacket) + optoffset);
}
int dhcp_server_send_packet(sd_dhcp_server *server,
@@ -404,20 +427,8 @@ int dhcp_server_send_packet(sd_dhcp_server *server,
} else if (req->message->ciaddr && type != DHCP_NAK)
destination = req->message->ciaddr;
if (destination != INADDR_ANY)
return dhcp_server_send_udp(server, destination,
destination_port, &packet->dhcp,
sizeof(DHCPMessage) + optoffset);
else if (requested_broadcast(req) || type == DHCP_NAK)
return dhcp_server_send_udp(server, INADDR_BROADCAST,
destination_port, &packet->dhcp,
sizeof(DHCPMessage) + optoffset);
else
/* we cannot send UDP packet to specific MAC address when the
address is not yet configured, so must fall back to raw
packets */
return dhcp_server_send_unicast_raw(server, packet,
sizeof(DHCPPacket) + optoffset);
bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK;
return dhcp_server_send(server, destination, destination_port, packet, optoffset, l2_broadcast);
}
static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
@@ -701,6 +712,47 @@ static int get_pool_offset(sd_dhcp_server *server, be32_t requested_ip) {
return be32toh(requested_ip & ~server->netmask) - server->pool_offset;
}
static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length) {
_cleanup_free_ DHCPPacket *packet = NULL;
assert(server);
assert(message);
assert(sd_dhcp_server_is_in_relay_mode(server));
if (message->op == BOOTREQUEST) {
log_dhcp_server(server, "(relay agent) BOOTREQUEST (0x%x)", be32toh(message->xid));
if (message->hops >= 16)
return -ETIME;
message->hops++;
/* https://tools.ietf.org/html/rfc1542#section-4.1.1 */
if (message->giaddr == 0)
message->giaddr = server->address;
return dhcp_server_send_udp(server, server->relay_target.s_addr, DHCP_PORT_SERVER, message, sizeof(DHCPMessage) + opt_length);
} else if (message->op == BOOTREPLY) {
log_dhcp_server(server, "(relay agent) BOOTREPLY (0x%x)", be32toh(message->xid));
if (message->giaddr != server->address) {
return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG),
"(relay agent) BOOTREPLY giaddr mismatch, discarding");
}
int message_type = dhcp_option_parse(message, sizeof(DHCPMessage) + opt_length, NULL, NULL, NULL);
if (message_type < 0)
return message_type;
packet = malloc0(sizeof(DHCPPacket) + opt_length);
if (!packet)
return -ENOMEM;
memcpy(&packet->dhcp, message, sizeof(DHCPMessage) + opt_length);
bool l2_broadcast = requested_broadcast(message) || message_type == DHCP_NAK;
const be32_t destination = message_type == DHCP_NAK ? INADDR_ANY : message->ciaddr;
return dhcp_server_send(server, destination, DHCP_PORT_CLIENT, packet, opt_length, l2_broadcast);
}
return -EBADMSG;
}
#define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30)
int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
@@ -999,10 +1051,15 @@ static int server_receive_message(sd_event_source *s, int fd,
}
}
r = dhcp_server_handle_message(server, message, (size_t) len);
if (r < 0)
log_dhcp_server_errno(server, r, "Couldn't process incoming message: %m");
if (sd_dhcp_server_is_in_relay_mode(server)) {
r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage));
if (r < 0)
log_dhcp_server_errno(server, r, "Couldn't relay message: %m");
} else {
r = dhcp_server_handle_message(server, message, (size_t) len);
if (r < 0)
log_dhcp_server_errno(server, r, "Couldn't process incoming message: %m");
}
return 0;
}
@@ -1238,3 +1295,14 @@ int sd_dhcp_server_set_callback(sd_dhcp_server *server, sd_dhcp_server_callback_
return 0;
}
int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr* address) {
assert_return(server, -EINVAL);
assert_return(!sd_dhcp_server_is_running(server), -EBUSY);
if (memcmp(address, &server->relay_target, sizeof(struct in_addr)) == 0)
return 0;
server->relay_target = *address;
return 1;
}

View File

@@ -31,6 +31,9 @@ static int property_get_leases(
if (!s)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Link %s has no DHCP server.", l->ifname);
if (sd_dhcp_server_is_in_relay_mode(s))
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Link %s has DHCP relay agent active.", l->ifname);
r = sd_bus_message_open_container(reply, 'a', "(uayayayayt)");
if (r < 0)
return r;

View File

@@ -352,6 +352,10 @@ int dhcp4_server_configure(Link *link) {
if (r < 0)
return log_link_error_errno(link, r, "Failed to set router emission for DHCP server: %m");
r = sd_dhcp_server_set_relay_target(link->dhcp_server, &link->network->dhcp_server_relay_target);
if (r < 0)
return log_link_error_errno(link, r, "Failed to set relay target for DHCP server: %m");
if (link->network->dhcp_server_emit_timezone) {
_cleanup_free_ char *buffer = NULL;
const char *tz;
@@ -398,6 +402,32 @@ int dhcp4_server_configure(Link *link) {
return 0;
}
int config_parse_dhcp_server_relay_target(
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) {
Network *network = userdata;
union in_addr_union a;
int r;
r = in_addr_from_string(AF_INET, rvalue, &a);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to parse %s= address '%s', ignoring: %m", lvalue, rvalue);
return 0;
}
network->dhcp_server_relay_target = a.in;
return r;
}
int config_parse_dhcp_server_emit(
const char *unit,
const char *filename,

View File

@@ -9,4 +9,5 @@ typedef struct Link Link;
int dhcp4_server_configure(Link *link);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_target);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit);

View File

@@ -256,6 +256,7 @@ IPv6AcceptRA.PrefixAllowList, config_parse_ndisc_address_filter,
IPv6AcceptRA.PrefixDenyList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_deny_listed_prefix)
IPv6AcceptRA.RouteAllowList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_allow_listed_route_prefix)
IPv6AcceptRA.RouteDenyList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_deny_listed_route_prefix)
DHCPServer.RelayTarget, config_parse_dhcp_server_relay_target, 0, 0
DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec)
DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec)
DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_DNS].emit)

View File

@@ -186,6 +186,7 @@ struct Network {
/* DHCP Server Support */
bool dhcp_server;
bool dhcp_server_bind_to_interface;
struct in_addr dhcp_server_relay_target;
NetworkDHCPServerEmitAddress dhcp_server_emit[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
bool dhcp_server_emit_router;
bool dhcp_server_emit_timezone;

View File

@@ -83,6 +83,8 @@ int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint32_t t);
int sd_dhcp_server_forcerenew(sd_dhcp_server *server);
int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server);
int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr* address);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref);
_SD_END_DECLARATIONS;

View File

@@ -352,6 +352,7 @@ DNS=
SendOption=
SendVendorOption=
BindToInterface=
RelayTarget=
[NextHop]
Id=
Gateway=

View File

@@ -0,0 +1,9 @@
[Match]
Name=client-peer
[Network]
Address=192.168.6.2/24
DHCPServer=yes
IPForward=ipv4
[DHCPServer]
RelayTarget=192.168.5.1
BindToInterface=no

View File

@@ -0,0 +1,5 @@
[Match]
Name=client
[Network]
DHCP=yes
IPForward=ipv4

View File

@@ -0,0 +1,5 @@
[Match]
Name=server-peer
[Network]
Address=192.168.5.2/24
IPForward=ipv4

View File

@@ -0,0 +1,10 @@
[Match]
Name=server
[Network]
Address=192.168.5.1/24
IPForward=ipv4
DHCPServer=yes
[DHCPServer]
BindToInterface=no
PoolOffset=150
PoolSize=1

View File

@@ -0,0 +1,8 @@
[NetDev]
Name=client
Kind=veth
MACAddress=12:34:56:78:9a:bc
[Peer]
Name=client-peer
MACAddress=12:34:56:78:9a:bd

View File

@@ -0,0 +1,8 @@
[NetDev]
Name=server
Kind=veth
MACAddress=12:34:56:78:9b:bc
[Peer]
Name=server-peer
MACAddress=12:34:56:78:9b:bd

View File

@@ -3689,6 +3689,43 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
self.assertRegex(output, '192.168.5.*')
self.assertRegex(output, 'Europe/Berlin')
class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities):
links = [
'client',
'server',
'client-peer',
'server-peer',
]
units = [
'agent-veth-client.netdev',
'agent-veth-server.netdev',
'agent-client.network',
'agent-server.network',
'agent-client-peer.network',
'agent-server-peer.network',
]
def setUp(self):
remove_links(self.links)
stop_networkd(show_logs=False)
def tearDown(self):
remove_links(self.links)
remove_unit_from_networkd_path(self.units)
stop_networkd(show_logs=True)
def test_relay_agent(self):
copy_unit_to_networkd_unit_path(*self.units)
start_networkd()
#Test is disabled until BindToInterface DHCP server configuration option is supported
self.wait_online(['client:routable'])
output = check_output(*networkctl_cmd, '-n', '0', 'status', 'client', env=env)
print(output)
self.assertRegex(output, 'Address: 192.168.5.150 \(DHCP4 via 192.168.5.1\)')
class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
links = [
'veth99',