Both DHCPv4 and DHCPv6 has the capability to pass the hostname to
clients, the DHCPv4 uses option 12 (hostname) while the DHCPv6 uses option 39
(client fqdn), for some virt deployments like kubevirt is expected to
have the VirtualMachine name as the guest hostname.
This change add the following arguments:
- -H --hostname NAME to configure the hostname DHCPv4 option(12)
- --fqdn NAME to configure client fqdn option for both DHCPv4(81) and
DHCPv6(39)
Signed-off-by: Enrique Llorente <ellorent(a)redhat.com>
---
conf.c | 23 ++++++++++++++++++++---
dhcp.c | 15 ++++++++++++++-
dhcpv6.c | 44 +++++++++++++++++++++++++++++++++++++++++++-
passt.h | 4 ++++
test/lib/setup | 10 +++++-----
test/passt.mbuto | 6 ++++--
test/passt/dhcp | 17 ++++++++++++++++-
util.c | 21 +++++++++++++++++++++
util.h | 2 ++
9 files changed, 129 insertions(+), 13 deletions(-)
diff --git a/conf.c b/conf.c
index 86566db..e9cf491 100644
--- a/conf.c
+++ b/conf.c
@@ -849,7 +849,9 @@ static void usage(const char *name, FILE *f, int status)
" --freebind Bind to any address for forwarding\n"
" --no-map-gw Don't map gateway address to host\n"
" -4, --ipv4-only Enable IPv4 operation only\n"
- " -6, --ipv6-only Enable IPv6 operation only\n");
+ " -6, --ipv6-only Enable IPv6 operation only\n"
+ " -H, --hostname NAME Hostname to configure client with\n"
+ " --fqdn NAME The FQDN to configure client with\n");
if (strstr(name, "pasta"))
goto pasta_opts;
@@ -1266,6 +1268,7 @@ void conf(struct ctx *c, int argc, char **argv)
{"outbound", required_argument, NULL, 'o' },
{"dns", required_argument, NULL, 'D' },
{"search", required_argument, NULL, 'S' },
+ {"hostname", required_argument, NULL, 'H' },
{"no-tcp", no_argument, &c->no_tcp, 1 },
{"no-udp", no_argument, &c->no_udp, 1 },
{"no-icmp", no_argument, &c->no_icmp, 1 },
@@ -1305,6 +1308,7 @@ void conf(struct ctx *c, int argc, char **argv)
{"map-guest-addr", required_argument, NULL, 22 },
{"host-lo-to-ns-lo", no_argument, NULL, 23 },
{"dns-host", required_argument, NULL, 24 },
+ {"fqdn", required_argument, NULL, 25 },
{ 0 },
};
const char *logname = (c->mode == MODE_PASTA) ? "pasta" : "passt";
@@ -1327,9 +1331,9 @@ void conf(struct ctx *c, int argc, char **argv)
if (c->mode == MODE_PASTA) {
c->no_dhcp_dns = c->no_dhcp_dns_search = 1;
fwd_default = FWD_AUTO;
- optstring = "+dqfel:hF:I:p:P:m:a:n:M:g:i:o:D:S:46t:u:T:U:";
+ optstring = "+dqfel:hF:I:p:P:m:a:n:M:g:i:o:D:S:H:46t:u:T:U:";
} else {
- optstring = "+dqfel:hs:F:p:P:m:a:n:M:g:i:o:D:S:461t:u:";
+ optstring = "+dqfel:hs:F:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:";
}
c->tcp.fwd_in.mode = c->tcp.fwd_out.mode = FWD_UNSET;
@@ -1682,6 +1686,19 @@ void conf(struct ctx *c, int argc, char **argv)
c->one_off = true;
break;
+ case 'H':
+ ret = snprintf(c->hostname.n, sizeof(c->hostname.n),
+ "%s", optarg);
+ if (ret <= 0 || ret >= (int)sizeof(c->hostname.n))
+ die("Invalid hostname: %s", optarg);
+ break;
+ case 25:
+ ret = snprintf(c->fqdn.n, sizeof(c->fqdn.n),
+ "%s", optarg);
+ if (ret <= 0 || ret >= (int)sizeof(c->fqdn.n))
+ die("Invalid fqdn: %s", optarg);
+ break;
+
case 't':
case 'u':
case 'T':
diff --git a/dhcp.c b/dhcp.c
index a06f143..e05bfc6 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -275,7 +275,7 @@ static void opt_set_dns_search(const struct ctx *c, size_t max_len)
*/
int dhcp(const struct ctx *c, const struct pool *p)
{
- size_t mlen, dlen, offset = 0, opt_len, opt_off = 0;
+ size_t mlen, dlen, offset = 0, opt_len, opt_off = 0, hostname_len, fqdn_len = 0;
char macstr[ETH_ADDRSTRLEN];
const struct ethhdr *eh;
const struct iphdr *iph;
@@ -375,6 +375,19 @@ int dhcp(const struct ctx *c, const struct pool *p)
opts[6].slen += sizeof(uint32_t);
}
+ hostname_len = strlen(c->hostname.n);
+ if ( hostname_len > 0 ) {
+ opts[12].slen = hostname_len;
+ memcpy(opts[12].s, &c->hostname.n, hostname_len);
+ }
+
+ fqdn_len = strlen(c->fqdn.n);
+ if ( fqdn_len > 0 ) {
+ opts[81].slen = fqdn_len+3;
+ memcpy(opts[81].s+3, &c->fqdn.n, fqdn_len);
+ }
+
+
if (!c->no_dhcp_dns_search)
opt_set_dns_search(c, sizeof(m->o));
diff --git a/dhcpv6.c b/dhcpv6.c
index 0523bba..88c0978 100644
--- a/dhcpv6.c
+++ b/dhcpv6.c
@@ -48,6 +48,7 @@ struct opt_hdr {
# define STATUS_NOTONLINK htons_constant(4)
# define OPT_DNS_SERVERS htons_constant(23)
# define OPT_DNS_SEARCH htons_constant(24)
+# define OPT_CLIENT_FQDN htons_constant(39)
#define STR_NOTONLINK "Prefix not appropriate for link."
uint16_t l;
@@ -163,6 +164,18 @@ struct opt_dns_search {
char list[MAXDNSRCH * NS_MAXDNAME];
} __attribute__((packed));
+/**
+ * struct opt_client_fqdn - Client FQDN option (RFC 4704)
+ * @hdr: Option header
+ * @flags: Flags as stated at RFC 4704 (always zero for us)
+ * @fqdn: Client fqdn
+ */
+struct opt_client_fqdn{
+ struct opt_hdr hdr;
+ uint8_t flags;
+ char domain_name[253 /*RFC 1035*/ ];
+} __attribute__((packed));
+
/**
* struct msg_hdr - DHCPv6 client/server message header
* @type: DHCP message type
@@ -193,6 +206,7 @@ struct msg_hdr {
* @client_id: Client Identifier, variable length
* @dns_servers: DNS Recursive Name Server, here just for storage size
* @dns_search: Domain Search List, here just for storage size
+ * @client_fqdn: Client FQDN, variable length
*/
static struct resp_t {
struct msg_hdr hdr;
@@ -203,10 +217,10 @@ static struct resp_t {
struct opt_client_id client_id;
struct opt_dns_servers dns_servers;
struct opt_dns_search dns_search;
+ struct opt_client_fqdn client_fqdn;
} __attribute__((__packed__)) resp = {
{ 0 },
SERVER_ID,
-
{ { OPT_IA_NA, OPT_SIZE_CONV(sizeof(struct opt_ia_na) +
sizeof(struct opt_ia_addr) -
sizeof(struct opt_hdr)) },
@@ -228,6 +242,10 @@ static struct resp_t {
{ { OPT_DNS_SEARCH, 0, },
{ 0 },
},
+
+ { { OPT_CLIENT_FQDN, 0, },
+ 0, { 0 },
+ },
};
static const struct opt_status_code sc_not_on_link = {
@@ -411,6 +429,29 @@ search:
return offset;
}
+/**
+ * dhcpv6_client_fqdn_fill() - Fill in client FQDN option
+ * @c: Execution context
+ * @buf: Response message buffer where options will be appended
+ * @offset: Offset in message buffer for new options
+ *
+ * Return: updated length of response message buffer.
+ */
+static size_t dhcpv6_client_fqdn_fill(const struct ctx *c, char *buf, int offset)
+{
+ uint16_t fqdn_len = strlen(c->fqdn.n);
+ if (fqdn_len > 0) {
+ struct opt_client_fqdn *o = (struct opt_client_fqdn *)(buf + offset);
+ size_t encoded_fqdn_len = encode_domain_name(c->fqdn.n, fqdn_len, o->domain_name);
+ size_t opt_len = encoded_fqdn_len + 1;
+ o->hdr.t = OPT_CLIENT_FQDN;
+ o->hdr.l = htons(opt_len);
+ o->flags = 0x00;
+ offset += sizeof(struct opt_hdr) + opt_len;
+ }
+ return offset;
+}
+
/**
* dhcpv6() - Check if this is a DHCPv6 message, reply as needed
* @c: Execution context
@@ -544,6 +585,7 @@ int dhcpv6(struct ctx *c, const struct pool *p,
n = offsetof(struct resp_t, client_id) +
sizeof(struct opt_hdr) + ntohs(client_id->l);
n = dhcpv6_dns_fill(c, (char *)&resp, n);
+ n = dhcpv6_client_fqdn_fill(c, (char*)&resp, n);
resp.hdr.xid = mh->xid;
diff --git a/passt.h b/passt.h
index 72c7f72..566d21d 100644
--- a/passt.h
+++ b/passt.h
@@ -205,6 +205,8 @@ struct ip6_ctx {
* @ifi4: Index of template interface for IPv4, 0 if IPv4 disabled
* @ip: IPv4 configuration
* @dns_search: DNS search list
+ * @hostname: Client hostname
+ * @fqdn: Client FQDN
* @ifi6: Index of template interface for IPv6, 0 if IPv6 disabled
* @ip6: IPv6 configuration
* @pasta_ifn: Name of namespace interface for pasta
@@ -262,6 +264,8 @@ struct ctx {
struct ip4_ctx ip4;
struct fqdn dns_search[MAXDNSRCH];
+ struct fqdn hostname;
+ struct fqdn fqdn;
unsigned int ifi6;
struct ip6_ctx ip6;
diff --git a/test/lib/setup b/test/lib/setup
index 5338393..a3fe578 100755
--- a/test/lib/setup
+++ b/test/lib/setup
@@ -49,7 +49,7 @@ setup_passt() {
context_run passt "make clean"
context_run passt "make valgrind"
- context_run_bg passt "valgrind --max-stackframe=$((4 * 1024 * 1024)) --trace-children=yes --vgdb=no --error-exitcode=1 --suppressions=test/valgrind.supp ./passt ${__opts} -s ${STATESETUP}/passt.socket -f -t 10001 -u 10001 -P ${STATESETUP}/passt.pid"
+ context_run_bg passt "valgrind --max-stackframe=$((4 * 1024 * 1024)) --trace-children=yes --vgdb=no --error-exitcode=1 --suppressions=test/valgrind.supp ./passt ${__opts} -s ${STATESETUP}/passt.socket -f -t 10001 -u 10001 -H hostname1 --fqdn fqdn1.passt.top -P ${STATESETUP}/passt.pid"
# pidfile isn't created until passt is listening
wait_for [ -f "${STATESETUP}/passt.pid" ]
@@ -146,11 +146,11 @@ setup_passt_in_ns() {
if [ ${VALGRIND} -eq 1 ]; then
context_run passt "make clean"
context_run passt "make valgrind"
- context_run_bg passt "valgrind --max-stackframe=$((4 * 1024 * 1024)) --trace-children=yes --vgdb=no --error-exitcode=1 --suppressions=test/valgrind.supp ./passt -f ${__opts} -s ${STATESETUP}/passt.socket -t 10001,10011,10021,10031 -u 10001,10011,10021,10031 -P ${STATESETUP}/passt.pid --map-host-loopback ${__map_ns4} --map-host-loopback ${__map_ns6}"
+ context_run_bg passt "valgrind --max-stackframe=$((4 * 1024 * 1024)) --trace-children=yes --vgdb=no --error-exitcode=1 --suppressions=test/valgrind.supp ./passt -f ${__opts} -s ${STATESETUP}/passt.socket -H hostname1 --fqdn fqdn1.passt.top -t 10001,10011,10021,10031 -u 10001,10011,10021,10031 -P ${STATESETUP}/passt.pid --map-host-loopback ${__map_ns4} --map-host-loopback ${__map_ns6}"
else
context_run passt "make clean"
context_run passt "make"
- context_run_bg passt "./passt -f ${__opts} -s ${STATESETUP}/passt.socket -t 10001,10011,10021,10031 -u 10001,10011,10021,10031 -P ${STATESETUP}/passt.pid --map-host-loopback ${__map_ns4} --map-host-loopback ${__map_ns6}"
+ context_run_bg passt "./passt -f ${__opts} -s ${STATESETUP}/passt.socket -H hostname1 --fqdn fqdn1.passt.top -t 10001,10011,10021,10031 -u 10001,10011,10021,10031 -P ${STATESETUP}/passt.pid --map-host-loopback ${__map_ns4} --map-host-loopback ${__map_ns6}"
fi
wait_for [ -f "${STATESETUP}/passt.pid" ]
@@ -215,7 +215,7 @@ setup_two_guests() {
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
- context_run_bg passt_1 "./passt -s ${STATESETUP}/passt_1.socket -P ${STATESETUP}/passt_1.pid -f ${__opts} -t 10001 -u 10001"
+ context_run_bg passt_1 "./passt -s ${STATESETUP}/passt_1.socket -P ${STATESETUP}/passt_1.pid -f ${__opts} --fqdn fqdn1.passt.top -H hostname1 -t 10001 -u 10001"
wait_for [ -f "${STATESETUP}/passt_1.pid" ]
__opts=
@@ -223,7 +223,7 @@ setup_two_guests() {
[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
- context_run_bg passt_2 "./passt -s ${STATESETUP}/passt_2.socket -P ${STATESETUP}/passt_2.pid -f ${__opts} -t 10004 -u 10004"
+ context_run_bg passt_2 "./passt -s ${STATESETUP}/passt_2.socket -P ${STATESETUP}/passt_2.pid -f ${__opts} --hostname hostname2 --fqdn fqdn2 -t 10004 -u 10004"
wait_for [ -f "${STATESETUP}/passt_2.pid" ]
GUEST_1_CID=94557
diff --git a/test/passt.mbuto b/test/passt.mbuto
index 138d365..1e07693 100755
--- a/test/passt.mbuto
+++ b/test/passt.mbuto
@@ -13,7 +13,7 @@
PROGS="${PROGS:-ash,dash,bash ip mount ls insmod mkdir ln cat chmod lsmod
modprobe find grep mknod mv rm umount jq iperf3 dhclient hostname
sed tr chown sipcalc cut socat dd strace ping tail killall sleep sysctl
- nproc tcp_rr tcp_crr udp_rr which tee seq bc sshd ssh-keygen cmp}"
+ nproc tcp_rr tcp_crr udp_rr which tee seq bc sshd ssh-keygen cmp env}"
# OpenSSH 9.8 introduced split binaries, with sshd being the daemon, and
# sshd-session the per-session program. We need the latter as well, and the path
@@ -41,6 +41,7 @@ FIXUP="${FIXUP}"'
#!/bin/sh
LOG=/var/log/dhclient-script.log
echo \${reason} \${interface} >> \$LOG
+env >> \$LOG
set >> \$LOG
[ -n "\${new_interface_mtu}" ] && ip link set dev \${interface} mtu \${new_interface_mtu}
@@ -54,7 +55,8 @@ set >> \$LOG
[ -n "\${new_ip6_address}" ] && ip addr add \${new_ip6_address}/\${new_ip6_prefixlen} dev \${interface}
[ -n "\${new_dhcp6_name_servers}" ] && for d in \${new_dhcp6_name_servers}; do echo "nameserver \${d}%\${interface}" >> /etc/resolv.conf; done
[ -n "\${new_dhcp6_domain_search}" ] && (printf "search"; for d in \${new_dhcp6_domain_search}; do printf " %s" "\${d}"; done; printf "\n") >> /etc/resolv.conf
-[ -n "\${new_host_name}" ] && hostname "\${new_host_name}"
+[ -n "\${new_host_name}" ] && echo "\${new_host_name}" > /tmp/new_host_name
+[ -n "\${new_fqdn_fqdn}" ] && echo "\${new_fqdn_fqdn}" > /tmp/new_fqdn_fqdn
exit 0
EOF
chmod 755 /sbin/dhclient-script
diff --git a/test/passt/dhcp b/test/passt/dhcp
index 9925ab9..f947fd1 100644
--- a/test/passt/dhcp
+++ b/test/passt/dhcp
@@ -11,7 +11,7 @@
# Copyright (c) 2021 Red Hat GmbH
# Author: Stefano Brivio <sbrivio(a)redhat.com>
-gtools ip jq dhclient sed tr
+gtools ip jq dhclient sed tr hostname
htools ip jq sed tr head
test Interface name
@@ -22,6 +22,7 @@ check [ -n "__IFNAME__" ]
test DHCP: address
guest /sbin/dhclient -4 __IFNAME__
+guest cat /var/log/dhclient-script.log
gout ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
hout HOST_ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME__").addr_info[0].local'
check [ "__ADDR__" = "__HOST_ADDR__" ]
@@ -47,10 +48,20 @@ gout SEARCH sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^searc
hout HOST_SEARCH sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^search \(.*\)/\1/p' | tr ' \n' ',' | sed 's/,$//;s/$/\n/'
check [ "__SEARCH__" = "__HOST_SEARCH__" ]
+test DHCP: Hostname
+gout NEW_HOST_NAME cat /tmp/new_host_name
+check [ "__NEW_HOST_NAME__" = "hostname1" ]
+
+test DHCP: Client FQDN
+gout NEW_FQDN_FQDN cat /tmp/new_fqdn_fqdn
+check [ "__NEW_FQDN_FQDN__" = "fqdn1.passt.top" ]
+
test DHCPv6: address
+guest rm /tmp/new_fqdn_fqdn
guest /sbin/dhclient -6 __IFNAME__
# Wait for DAD to complete
guest while ip -j -6 addr show tentative | jq -e '.[].addr_info'; do sleep 0.1; done
+guest cat /var/log/dhclient-script.log
gout ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.prefixlen == 128).local] | .[0]'
hout HOST_ADDR6 ip -j -6 addr show|jq -rM '[.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global" and .deprecated != true).local] | .[0]'
check [ "__ADDR6__" = "__HOST_ADDR6__" ]
@@ -70,3 +81,7 @@ test DHCPv6: search list
gout SEARCH6 sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^search \(.*\)/\1/p' | tr ' \n' ',' | sed 's/,$//;s/$/\n/'
hout HOST_SEARCH6 sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^search \(.*\)/\1/p' | tr ' \n' ',' | sed 's/,$//;s/$/\n/'
check [ "__SEARCH6__" = "__HOST_SEARCH6__" ]
+
+test DHCPv6: Hostname
+gout NEW_FQDN_FQDN cat /tmp/new_fqdn_fqdn
+check [ "__NEW_FQDN_FQDN__" = "fqdn1.passt.top" ]
diff --git a/util.c b/util.c
index 55cae3f..b38aec7 100644
--- a/util.c
+++ b/util.c
@@ -837,3 +837,24 @@ void raw_random(void *buf, size_t buflen)
if (random_read < buflen)
die("Unexpected EOF on random data source");
}
+/**
+ * encode_domain_name() - Encoding domain names according to RFC 3315 sec 8
+ * @domain_name: Input domain name to encode
+ * @len: Domain name length
+ * @buf: Buffer to fill in with encoded domain name
+ *
+ * Returns: encoded domain name length
+ */
+
+size_t encode_domain_name(const char* domain_name, size_t len, char* buf){
+ buf[0] = strcspn(domain_name, ".");
+ char* p = buf + 1;
+ for (size_t i = 0; i < len; i++) {
+ if (domain_name[i] == '.') {
+ p[i] = strcspn(domain_name + i + 1, ".");
+ } else {
+ p[i] = domain_name[i];
+ }
+ }
+ return len + 2;
+}
diff --git a/util.h b/util.h
index 90428c4..8001ebb 100644
--- a/util.h
+++ b/util.h
@@ -305,4 +305,6 @@ static inline int wrap_accept4(int sockfd, struct sockaddr *addr,
#define accept4(s, addr, addrlen, flags) \
wrap_accept4((s), (addr), (addrlen), (flags))
+size_t encode_domain_name(const char* domain_name, size_t len, char* buf);
+
#endif /* UTIL_H */
--
2.47.0