public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
* [PATCH] dhcp, dhcpv6: Add hostname and client fqdn ops
@ 2024-11-13 17:12 Enrique Llorente
  0 siblings, 0 replies; only message in thread
From: Enrique Llorente @ 2024-11-13 17:12 UTC (permalink / raw)
  To: passt-dev; +Cc: Enrique Llorente

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 -H --hostname to configure the DHCPv4 and DHCPv6
options to will send hostname to clients.

Signed-off-by: Enrique Llorente <ellorent@redhat.com>

wip

Signed-off-by: Enrique Llorente <ellorent@redhat.com>
---
 conf.c           | 13 ++++++++---
 dhcp.c           |  6 +++++
 dhcpv6.c         | 59 +++++++++++++++++++++++++++++++++++++++---------
 passt.h          |  2 ++
 test/lib/setup   | 10 ++++----
 test/passt.mbuto |  3 +++
 test/passt/dhcp  | 11 ++++++++-
 test/pasta/dhcp  |  2 +-
 8 files changed, 85 insertions(+), 21 deletions(-)

diff --git a/conf.c b/conf.c
index 14411b4..be35996 100644
--- a/conf.c
+++ b/conf.c
@@ -847,7 +847,8 @@ 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 ADDR	Hostname to configure client with\n");
 
 	if (strstr(name, "pasta"))
 		goto pasta_opts;
@@ -1303,6 +1304,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 },
+		{"hostname",	required_argument,	NULL,		'H' },
 		{ 0 },
 	};
 	const char *logname = (c->mode == MODE_PASTA) ? "pasta" : "passt";
@@ -1325,9 +1327,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:46t:u:T:U:H:";
 	} 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:461t:u:H:";
 	}
 
 	c->tcp.fwd_in.mode = c->tcp.fwd_out.mode = FWD_UNSET;
@@ -1680,6 +1682,11 @@ 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 't':
 		case 'u':
 		case 'T':
diff --git a/dhcp.c b/dhcp.c
index a06f143..13255d9 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -374,6 +374,12 @@ int dhcp(const struct ctx *c, const struct pool *p)
 		((struct in_addr *)opts[6].s)[i] = c->ip4.dns[i];
 		opts[6].slen += sizeof(uint32_t);
 	}
+    
+    size_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);
+    }
 
 	if (!c->no_dhcp_dns_search)
 		opt_set_dns_search(c, sizeof(m->o));
diff --git a/dhcpv6.c b/dhcpv6.c
index 14a5c7e..143287e 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,17 @@ struct opt_dns_search {
 	char list[MAXDNSRCH * NS_MAXDNAME];
 } __attribute__((packed));
 
+/**
+ * struct opt_client_fqdn - Client FQDN option (RFC 4704)
+ * @hdr:		Option header
+ * @hostname:	Client fqdn
+ */
+struct opt_client_fqdn{
+	struct opt_hdr hdr;
+    uint8_t flags;
+	uint8_t hostname[NS_MAXDNAME];
+} __attribute__((packed));
+
 /**
  * struct msg_hdr - DHCPv6 client/server message header
  * @type:		DHCP message type
@@ -191,6 +203,7 @@ struct msg_hdr {
  * @ia_na:		Non-temporary Address option
  * @ia_addr:		Address for IA_NA
  * @client_id:		Client Identifier, variable length
+ * @client_fqdn:	Client FQDN, variable length
  * @dns_servers:	DNS Recursive Name Server, here just for storage size
  * @dns_search:		Domain Search List, here just for storage size
  */
@@ -203,31 +216,30 @@ 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)) },
 	  1, (uint32_t)~0U, (uint32_t)~0U
 	},
-
 	{ { OPT_IAAADR,		OPT_SIZE(ia_addr) },
 	  IN6ADDR_ANY_INIT, (uint32_t)~0U, (uint32_t)~0U
 	},
-
 	{ { OPT_CLIENTID,	0, },
 	  { 0 }
 	},
-
 	{ { OPT_DNS_SERVERS,	0, },
 	  { IN6ADDR_ANY_INIT }
 	},
-
 	{ { OPT_DNS_SEARCH,	0, },
 	  { 0 },
 	},
+	{ { OPT_CLIENT_FQDN, 0, },
+	  (uint8_t)~0U, { 0 },
+	},
 };
 
 static const struct opt_status_code sc_not_on_link = {
@@ -416,12 +428,34 @@ 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 hostname_len = strlen(c->hostname.n);
+    if (hostname_len > 0)  {
+        struct opt_client_fqdn *o = (struct opt_client_fqdn*)(buf + offset);
+        o->hdr.t = OPT_CLIENT_FQDN;
+        o->hdr.l = htons(hostname_len+2);
+        o->flags = 0x00; 
+        *o->hostname = hostname_len;
+        memcpy(o->hostname+1, c->hostname.n, hostname_len);
+        offset += sizeof(struct opt_hdr) + hostname_len+2;
+    }
+    return offset;
+}
+	
 /**
  * dhcpv6() - Check if this is a DHCPv6 message, reply as needed
  * @c:		Execution context
  * @p:		Packet pool, single packet starting from UDP header
- * @saddr:	Source IPv6 address of original message
- * @daddr:	Destination IPv6 address of original message
+ * @saddr:	Source IPv6 address of original message * @daddr:	Destination IPv6 address of original message
  *
  * Return: 0 if it's not a DHCPv6 message, 1 if handled, -1 on failure
  */
@@ -542,15 +576,18 @@ int dhcpv6(struct ctx *c, const struct pool *p,
 	}
 	if (ia)
 		resp.ia_na.iaid = ((struct opt_ia_na *)ia)->iaid;
-
+    
 	memcpy(&resp.client_id, client_id,
 	       ntohs(client_id->l) + sizeof(struct opt_hdr));
 
-	n = offsetof(struct resp_t, client_id) +
+    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_dns_fill(c, (char *)&resp, n);
 
-	resp.hdr.xid = mh->xid;
+    n = dhcpv6_client_fqdn_fill(c, (char*)&resp, n);   
+   
+    resp.hdr.xid = mh->xid;
 
 	tap_udp6_send(c, src, 547, tap_ip6_daddr(c, src), 546,
 		      mh->xid, &resp, n);
diff --git a/passt.h b/passt.h
index 72c7f72..b25b13a 100644
--- a/passt.h
+++ b/passt.h
@@ -205,6 +205,7 @@ 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	
  * @ifi6:		Index of template interface for IPv6, 0 if IPv6 disabled
  * @ip6:		IPv6 configuration
  * @pasta_ifn:		Name of namespace interface for pasta
@@ -262,6 +263,7 @@ struct ctx {
 	struct ip4_ctx ip4;
 
 	struct fqdn dns_search[MAXDNSRCH];
+	struct fqdn hostname;
 
 	unsigned int ifi6;
 	struct ip6_ctx ip6;
diff --git a/test/lib/setup b/test/lib/setup
index 5338393..9c7aac9 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 passt1 -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 passt1 -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 passt1 -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} -H passt1 -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 passt2 -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..b959891 100755
--- a/test/passt.mbuto
+++ b/test/passt.mbuto
@@ -43,6 +43,8 @@ LOG=/var/log/dhclient-script.log
 echo \${reason} \${interface} >> \$LOG
 set >> \$LOG
 
+env
+
 [ -n "\${new_interface_mtu}" ]       && ip link set dev \${interface} mtu \${new_interface_mtu}
 
 [ -n "\${new_ip_address}" ]          && ip addr add \${new_ip_address}/\${new_subnet_mask} dev \${interface}
@@ -55,6 +57,7 @@ set >> \$LOG
 [ -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_fqdn_hostname}" ]       && hostname "\${new_fqdn_hostname}"
 exit 0
 EOF
 	chmod 755 /sbin/dhclient-script
diff --git a/test/passt/dhcp b/test/passt/dhcp
index 9925ab9..36535f2 100644
--- a/test/passt/dhcp
+++ b/test/passt/dhcp
@@ -11,7 +11,7 @@
 # Copyright (c) 2021 Red Hat GmbH
 # Author: Stefano Brivio <sbrivio@redhat.com>
 
-gtools	ip jq dhclient sed tr
+gtools	ip jq dhclient sed tr hostname
 htools	ip jq sed tr head
 
 test	Interface name
@@ -47,7 +47,12 @@ 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	HOSTNAME hostname
+check	[ "__HOSTNAME__" = "passt1" ]
+
 test	DHCPv6: address
+guest	hostname none
 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
@@ -70,3 +75,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	HOSTNAME hostname
+check	[ "__HOSTNAME__" = "passt1" ]
diff --git a/test/pasta/dhcp b/test/pasta/dhcp
index d4f3ad5..1f47325 100644
--- a/test/pasta/dhcp
+++ b/test/pasta/dhcp
@@ -19,7 +19,7 @@ nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifnam
 check	[ -n "__IFNAME__" ]
 
 test	DHCP: address
-ns	/sbin/dhclient -4 --no-pid __IFNAME__
+ns	/sbin/dhclient -4 -sf test/dhclient-script --no-pid __IFNAME__
 nsout	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 == "__IFNAME__").addr_info[0].local'
 check	[ __ADDR__ = __HOST_ADDR__ ]
-- 
2.47.0


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2024-11-13 17:12 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-11-13 17:12 [PATCH] dhcp, dhcpv6: Add hostname and client fqdn ops Enrique Llorente

Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).