<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>
</varlistentry>
<varlistentry>
<term><varname>BindToInterface=</varname></term>
- <listitem>Takes a boolean value. When <literal>yes</literal>, DHCP server socket will be bound
- to its network interface and all socket communication will be restricted to this interface.
- Defaults to <literal>yes</literal>.
+ <listitem>
+ <para>Takes a boolean value. When <literal>yes</literal>, DHCP server socket will be bound
+ to its network interface and all socket communication will be restricted to this interface.
+ Defaults to <literal>yes</literal>, except if <varname>RelayTarget=</varname> is used (see below),
+ in which case it defaults defaults to <literal>no</literal>.</para>
+ </listitem>
+ </varlistentry>
+ <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>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>RelayAgentCircuitId=</varname></term>
+ <listitem>
+ <para>Specifies value for Agent Circuit ID suboption of Relay Agent Information option.
+ Takes a string, which must be in the format <literal>string:<replaceable>value</replaceable></literal>,
+ where <literal><replaceable>value</replaceable></literal> should be replaced with the value of the suboption.
+ Defaults to unset (means no Agent Circuit ID suboption is generated).
+ Ignored if <varname>RelayTarget=</varname> is not specified.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>RelayAgentRemoteId=</varname></term>
+ <listitem>
+ <para>Specifies value for Agent Remote ID suboption of Relay Agent Information option.
+ Takes a string, which must be in the format <literal>string:<replaceable>value</replaceable></literal>,
+ where <literal><replaceable>value</replaceable></literal> should be replaced with the value of the suboption.
+ Defaults to unset (means no Agent Remote ID suboption is generated).
+ Ignored if <varname>RelayTarget=</varname> is not specified.</para>
</listitem>
</varlistentry>
int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, uint8_t overload,
uint8_t code, size_t optlen, const void *optval);
+int dhcp_option_find_option(uint8_t *options, size_t length, uint8_t wanted_code, size_t *ret_offset);
+int dhcp_option_remove_option(uint8_t *options, size_t buflen, uint8_t option_code);
typedef int (*dhcp_option_callback_t)(uint8_t code, uint8_t len,
const void *option, void *userdata);
#include "strv.h"
#include "utf8.h"
+/* Append type-length value structure to the options buffer */
+static int dhcp_option_append_tlv(uint8_t options[], size_t size, size_t *offset, uint8_t code, size_t optlen, const void *optval) {
+ assert(options);
+ assert(size > 0);
+ assert(offset);
+ assert(optlen <= UINT8_MAX);
+ assert(*offset < size);
+
+ if (*offset + 2 + optlen > size)
+ return -ENOBUFS;
+
+ options[*offset] = code;
+ options[*offset + 1] = optlen;
+
+ memcpy_safe(&options[*offset + 2], optval, optlen);
+ *offset += 2 + optlen;
+ return 0;
+}
+
static int option_append(uint8_t options[], size_t size, size_t *offset,
uint8_t code, size_t optlen, const void *optval) {
assert(options);
assert(size > 0);
assert(offset);
+ int r;
+
if (code != SD_DHCP_OPTION_END)
/* always make sure there is space for an END option */
size--;
options[*offset] = code;
options[*offset + 1] = l;
-
*offset += 2;
ORDERED_SET_FOREACH(p, s) {
- options[*offset] = p->option;
- options[*offset + 1] = p->length;
- memcpy(&options[*offset + 2], p->data, p->length);
- *offset += 2 + p->length;
+ r = dhcp_option_append_tlv(options, size, offset, p->option, p->length, p->data);
+ if (r < 0)
+ return r;
}
+ break;
+ }
+ case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: {
+ sd_dhcp_server *server = (sd_dhcp_server *) optval;
+ size_t current_offset = *offset + 2;
+ if (server->agent_circuit_id) {
+ r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_CIRCUIT_ID,
+ strlen(server->agent_circuit_id), server->agent_circuit_id);
+ if (r < 0)
+ return r;
+ }
+ if (server->agent_remote_id) {
+ r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_REMOTE_ID,
+ strlen(server->agent_remote_id), server->agent_remote_id);
+ if (r < 0)
+ return r;
+ }
+
+ options[*offset] = code;
+ options[*offset + 1] = current_offset - *offset - 2;
+ assert(current_offset - *offset - 2 <= UINT8_MAX);
+ *offset = current_offset;
break;
}
default:
- if (*offset + 2 + optlen > size)
- return -ENOBUFS;
+ return dhcp_option_append_tlv(options, size, offset, code, optlen, optval);
+ }
+ return 0;
+}
- options[*offset] = code;
- options[*offset + 1] = optlen;
+static int option_length(uint8_t *options, size_t length, size_t offset) {
+ assert(options);
+ assert(offset < length);
- memcpy_safe(&options[*offset + 2], optval, optlen);
- *offset += 2 + optlen;
+ if (IN_SET(options[offset], SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END))
+ return 1;
+ if (length < offset + 2)
+ return -ENOBUFS;
- break;
+ /* validating that buffer is long enough */
+ if (length < offset + 2 + options[offset + 1])
+ return -ENOBUFS;
+
+ return options[offset + 1] + 2;
+}
+
+int dhcp_option_find_option(uint8_t *options, size_t length, uint8_t code, size_t *ret_offset) {
+ int r;
+
+ assert(options);
+ assert(ret_offset);
+
+ for (size_t offset = 0; offset < length; offset += r) {
+ r = option_length(options, length, offset);
+ if (r < 0)
+ return r;
+
+ if (code == options[offset]) {
+ *ret_offset = offset;
+ return r;
+ }
}
+ return -ENOENT;
+}
- return 0;
+int dhcp_option_remove_option(uint8_t *options, size_t length, uint8_t option_code) {
+ int r;
+ size_t offset;
+
+ assert(options);
+
+ r = dhcp_option_find_option(options, length, option_code, &offset);
+ if (r < 0)
+ return r;
+
+ memmove(options + offset, options + offset + r, length - offset - r);
+ return length - r;
}
int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset,
} DHCPLease;
struct sd_dhcp_server {
- struct in_addr relay_target;
unsigned n_ref;
sd_event *event;
sd_dhcp_server_callback_t callback;
void *callback_userdata;
+
+ struct in_addr relay_target;
+
+ char *agent_circuit_id;
+ char *agent_remote_id;
};
typedef struct DHCPRequest {
be32_t server_id;
be32_t requested_ip;
uint32_t lifetime;
+ const uint8_t *agent_info_option;
} DHCPRequest;
int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
ordered_set_free(server->extra_options);
ordered_set_free(server->vendor_options);
+ free(server->agent_circuit_id);
+ free(server->agent_remote_id);
+
free(server->bound_leases);
free(server->ifname);
assert(server);
assert(req);
assert(req->max_optlen);
+ assert(req->message);
assert(optoffset <= req->max_optlen);
assert(packet);
if (r < 0)
return r;
+ if (req->agent_info_option) {
+ size_t opt_full_length = *(req->agent_info_option + 1) + 2;
+ /* there must be space left for SD_DHCP_OPTION_END */
+ if (optoffset + opt_full_length < req->max_optlen) {
+ memcpy(packet->dhcp.options + optoffset, req->agent_info_option, opt_full_length);
+ optoffset += opt_full_length;
+ }
+ }
+
r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
SD_DHCP_OPTION_END, 0, NULL);
if (r < 0)
if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket))
req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket);
+ break;
+ case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION:
+ req->agent_info_option = (uint8_t*)option - 2;
+
break;
}
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) {
+static int append_agent_information_option(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length, size_t size) {
+ int r;
+ size_t offset;
+
+ assert(server);
+ assert(message);
+
+ r = dhcp_option_find_option(message->options, opt_length, SD_DHCP_OPTION_END, &offset);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(message, size, &offset, 0, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, 0, server);
+ if (r < 0)
+ return r;
+
+ r = dhcp_option_append(message, size, &offset, 0, SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+ return offset;
+}
+
+static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length, size_t buflen) {
_cleanup_free_ DHCPPacket *packet = NULL;
+ int r;
assert(server);
assert(message);
if (message->giaddr == 0)
message->giaddr = server->address;
+ if (server->agent_circuit_id || server->agent_remote_id) {
+ r = append_agent_information_option(server, message, opt_length, buflen - sizeof(DHCPMessage));
+ if (r < 0)
+ return log_dhcp_server_errno(server, r, "could not append relay option: %m");
+ opt_length = r;
+ }
+
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) {
+ if (message->giaddr != server->address)
return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG),
- "(relay agent) BOOTREPLY giaddr mismatch, discarding");
- }
+ "(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 -ENOMEM;
memcpy(&packet->dhcp, message, sizeof(DHCPMessage) + opt_length);
+ r = dhcp_option_remove_option(packet->dhcp.options, opt_length, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION);
+ if (r > 0)
+ opt_length = r;
+
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 0;
}
+static size_t relay_agent_information_length(const char* agent_circuit_id, const char* agent_remote_id) {
+ size_t sum = 0;
+ if (agent_circuit_id)
+ sum += 2 + strlen(agent_circuit_id);
+ if (agent_remote_id)
+ sum += 2 + strlen(agent_remote_id);
+ return sum;
+}
+
static int server_receive_message(sd_event_source *s, int fd,
uint32_t revents, void *userdata) {
_cleanup_free_ DHCPMessage *message = NULL;
.msg_controllen = sizeof(control),
};
struct cmsghdr *cmsg;
- ssize_t buflen, len;
+ ssize_t datagram_size, len;
int r;
assert(server);
- buflen = next_datagram_size_fd(fd);
- if (buflen < 0)
- return buflen;
+ datagram_size = next_datagram_size_fd(fd);
+ if (datagram_size < 0)
+ return datagram_size;
+
+ size_t buflen = datagram_size;
+ if (sd_dhcp_server_is_in_relay_mode(server))
+ /* Preallocate the additional size for DHCP Relay Agent Information Option if neeeded */
+ buflen += relay_agent_information_length(server->agent_circuit_id, server->agent_remote_id) + 2;
message = malloc(buflen);
if (!message)
return -ENOMEM;
- iov = IOVEC_MAKE(message, buflen);
+ iov = IOVEC_MAKE(message, datagram_size);
len = recvmsg_safe(fd, &msg, 0);
if (IN_SET(len, -EAGAIN, -EINTR))
}
if (sd_dhcp_server_is_in_relay_mode(server)) {
- r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage));
+ r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage), buflen);
if (r < 0)
log_dhcp_server_errno(server, r, "Couldn't relay message: %m");
} else {
return 0;
}
-int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr* address) {
+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);
server->relay_target = *address;
return 1;
}
+
+int sd_dhcp_server_set_relay_agent_information(
+ sd_dhcp_server *server,
+ const char *agent_circuit_id,
+ const char *agent_remote_id) {
+ _cleanup_free_ char *circuit_id_dup = NULL, *remote_id_dup = NULL;
+
+ assert_return(server, -EINVAL);
+
+ if (relay_agent_information_length(agent_circuit_id, agent_remote_id) > UINT8_MAX)
+ return -ENOBUFS;
+
+ if (agent_circuit_id) {
+ circuit_id_dup = strdup(agent_circuit_id);
+ if (!circuit_id_dup)
+ return -ENOMEM;
+ }
+
+ if (agent_remote_id) {
+ remote_id_dup = strdup(agent_remote_id);
+ if (!remote_id_dup)
+ return -ENOMEM;
+ }
+
+ free_and_replace(server->agent_circuit_id, circuit_id_dup);
+ free_and_replace(server->agent_remote_id, remote_id_dup);
+ return 0;
+}
printf("DHCP type %s\n", dhcp_type(res));
}
+static void test_option_removal(struct option_desc *desc) {
+ _cleanup_free_ DHCPMessage *message = create_message(&desc->options[0], desc->len, NULL, 0, NULL, 0);
+
+ assert_se(dhcp_option_parse(message, sizeof(DHCPMessage) + desc->len, NULL, NULL, NULL) >= 0);
+ assert_se((desc->len = dhcp_option_remove_option(message->options, desc->len, SD_DHCP_OPTION_MESSAGE_TYPE)) >= 0);
+ assert_se(dhcp_option_parse(message, sizeof(DHCPMessage) + desc->len, NULL, NULL, NULL) < 0);
+}
+
static uint8_t options[64] = {
'A', 'B', 'C', 'D',
160, 2, 0x11, 0x12,
test_option_set();
+ for (i = 0; i < ELEMENTSOF(option_tests); i++) {
+ struct option_desc *desc = &option_tests[i];
+ if (!desc->success || desc->snamelen > 0 || desc->filelen > 0)
+ continue;
+ test_option_removal(desc);
+ }
+
return 0;
}
sd_dhcp_option *p;
Link *uplink = NULL;
Address *address;
+ bool bind_to_interface;
int r;
assert(link);
dhcp_lease_server_type_to_string(type));
}
- r = sd_dhcp_server_set_bind_to_interface(link->dhcp_server, link->network->dhcp_server_bind_to_interface);
- if (r < 0)
- return log_link_error_errno(link, r, "Failed to set interface binding for DHCP server: %m");
-
r = sd_dhcp_server_set_emit_router(link->dhcp_server, link->network->dhcp_server_emit_router);
if (r < 0)
return log_link_error_errno(link, r, "Failed to set router emission for DHCP server: %m");
if (r < 0)
return log_link_error_errno(link, r, "Failed to set relay target for DHCP server: %m");
+ bind_to_interface = sd_dhcp_server_is_in_relay_mode(link->dhcp_server) ? false : link->network->dhcp_server_bind_to_interface;
+ r = sd_dhcp_server_set_bind_to_interface(link->dhcp_server, bind_to_interface);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to set interface binding for DHCP server: %m");
+
+ r = sd_dhcp_server_set_relay_agent_information(link->dhcp_server, link->network->dhcp_server_relay_agent_circuit_id, link->network->dhcp_server_relay_agent_remote_id);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to set agent circuit/remote id for DHCP server: %m");
+
if (link->network->dhcp_server_emit_timezone) {
_cleanup_free_ char *buffer = NULL;
const char *tz;
return 0;
}
+int config_parse_dhcp_server_relay_agent_suboption(
+ 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) {
+
+ char **suboption_value = data;
+ char* p;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+
+ if (isempty(rvalue)) {
+ *suboption_value = mfree(*suboption_value);
+ return 0;
+ }
+
+ p = startswith(rvalue, "string:");
+ if (!p) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Failed to parse %s=%s'. Invalid format, ignoring.", lvalue, rvalue);
+ return 0;
+ }
+ return free_and_strdup(suboption_value, empty_to_null(p));
+}
+
int config_parse_dhcp_server_relay_target(
const char *unit,
const char *filename,
int dhcp4_server_configure(Link *link);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_agent_suboption);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_target);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit);
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.RelayAgentCircuitId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_circuit_id)
+DHCPServer.RelayAgentRemoteId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_remote_id)
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)
net_match_clear(&network->match);
condition_free_list(network->conditions);
+ free(network->dhcp_server_relay_agent_circuit_id);
+ free(network->dhcp_server_relay_agent_remote_id);
+
free(network->description);
free(network->dhcp_vendor_class_identifier);
free(network->dhcp_mudurl);
bool dhcp_server;
bool dhcp_server_bind_to_interface;
struct in_addr dhcp_server_relay_target;
+ char *dhcp_server_relay_agent_circuit_id;
+ char *dhcp_server_relay_agent_remote_id;
+
NetworkDHCPServerEmitAddress dhcp_server_emit[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
bool dhcp_server_emit_router;
bool dhcp_server_emit_timezone;
SD_DHCP_OPTION_POP3_SERVER = 70,
SD_DHCP_OPTION_USER_CLASS = 77,
SD_DHCP_OPTION_FQDN = 81,
+ SD_DHCP_OPTION_RELAY_AGENT_INFORMATION = 82,
SD_DHCP_OPTION_NEW_POSIX_TIMEZONE = 100,
SD_DHCP_OPTION_NEW_TZDB_TIMEZONE = 101,
SD_DHCP_OPTION_DOMAIN_SEARCH_LIST = 119,
SD_DHCP_OPTION_END = 255,
};
+/* Suboptions for SD_DHCP_OPTION_RELAY_AGENT_INFORMATION option */
+enum {
+ SD_DHCP_RELAY_AGENT_CIRCUIT_ID = 1,
+ SD_DHCP_RELAY_AGENT_REMOTE_ID = 2,
+};
+
typedef struct sd_dhcp_client sd_dhcp_client;
typedef int (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata);
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);
+int sd_dhcp_server_set_relay_agent_information(sd_dhcp_server *server, const char* circuit_id, const char* remote_id);
+
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref);
_SD_END_DECLARATIONS;
SendVendorOption=
BindToInterface=
RelayTarget=
+RelayAgentCircuitId=
+RelayAgentRemoteId=
[NextHop]
Id=
Gateway=
[DHCPServer]
RelayTarget=192.168.5.1
BindToInterface=no
+RelayAgentCircuitId=string:sample_circuit_id
+RelayAgentRemoteId=string:sample_remote_id
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)