rfc3046 implementation

This commit is contained in:
Yegor Alexeyev 2021-04-21 06:51:07 +03:00 committed by Yu Watanabe
parent ebf940e1e9
commit 11c38d3e51
16 changed files with 321 additions and 47 deletions

View file

@ -2325,17 +2325,6 @@ 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>
@ -2461,9 +2450,40 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
</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>

View file

@ -42,6 +42,8 @@ int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port,
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);

View file

@ -14,12 +14,33 @@
#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--;
@ -93,34 +114,93 @@ static int option_append(uint8_t options[], size_t size, size_t *offset,
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, &current_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, &current_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;
options[*offset] = code;
options[*offset + 1] = optlen;
memcpy_safe(&options[*offset + 2], optval, optlen);
*offset += 2 + optlen;
break;
return dhcp_option_append_tlv(options, size, offset, code, optlen, optval);
}
return 0;
}
static int option_length(uint8_t *options, size_t length, size_t offset) {
assert(options);
assert(offset < length);
if (IN_SET(options[offset], SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END))
return 1;
if (length < offset + 2)
return -ENOBUFS;
/* 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;
}
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,
uint8_t overload,
uint8_t code, size_t optlen, const void *optval) {

View file

@ -39,7 +39,6 @@ typedef struct DHCPLease {
} DHCPLease;
struct sd_dhcp_server {
struct in_addr relay_target;
unsigned n_ref;
sd_event *event;
@ -76,6 +75,11 @@ struct sd_dhcp_server {
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 {
@ -88,6 +92,7 @@ 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,

View file

@ -164,6 +164,9 @@ static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) {
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);
@ -382,6 +385,7 @@ int dhcp_server_send_packet(sd_dhcp_server *server,
assert(server);
assert(req);
assert(req->max_optlen);
assert(req->message);
assert(optoffset <= req->max_optlen);
assert(packet);
@ -391,6 +395,15 @@ int dhcp_server_send_packet(sd_dhcp_server *server,
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)
@ -649,6 +662,10 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us
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;
}
@ -712,8 +729,30 @@ 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) {
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);
@ -729,13 +768,19 @@ static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *messag
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)
@ -746,6 +791,10 @@ static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *messag
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);
@ -1000,6 +1049,15 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
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;
@ -1013,20 +1071,25 @@ static int server_receive_message(sd_event_source *s, int fd,
.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))
@ -1052,7 +1115,7 @@ static int server_receive_message(sd_event_source *s, int fd,
}
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 {
@ -1296,7 +1359,7 @@ 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) {
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);
@ -1306,3 +1369,31 @@ int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr
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;
}

View file

@ -270,6 +270,14 @@ static void test_options(struct option_desc *desc) {
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,
@ -366,5 +374,12 @@ int main(int argc, char *argv[]) {
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;
}

View file

@ -252,6 +252,7 @@ int dhcp4_server_configure(Link *link) {
sd_dhcp_option *p;
Link *uplink = NULL;
Address *address;
bool bind_to_interface;
int r;
assert(link);
@ -344,10 +345,6 @@ int dhcp4_server_configure(Link *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");
@ -356,6 +353,15 @@ int dhcp4_server_configure(Link *link) {
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;
@ -402,6 +408,40 @@ int dhcp4_server_configure(Link *link) {
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,

View file

@ -9,5 +9,6 @@ typedef struct Link Link;
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);

View file

@ -262,6 +262,8 @@ IPv6AcceptRA.PrefixDenyList, config_parse_ndisc_address_filter,
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)

View file

@ -548,6 +548,9 @@ static Network *network_free(Network *network) {
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);

View file

@ -190,6 +190,9 @@ struct Network {
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;

View file

@ -90,6 +90,7 @@ enum {
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,
@ -105,6 +106,12 @@ enum {
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);

View file

@ -85,6 +85,8 @@ 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);
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;

View file

@ -362,6 +362,8 @@ SendOption=
SendVendorOption=
BindToInterface=
RelayTarget=
RelayAgentCircuitId=
RelayAgentRemoteId=
[NextHop]
Id=
Gateway=

View file

@ -7,3 +7,5 @@ IPForward=ipv4
[DHCPServer]
RelayTarget=192.168.5.1
BindToInterface=no
RelayAgentCircuitId=string:sample_circuit_id
RelayAgentRemoteId=string:sample_remote_id

View file

@ -3719,7 +3719,6 @@ class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities):
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)