From: Enrique Llorente Pastora <ellorent@redhat.com>
To: Stefano Brivio <sbrivio@redhat.com>
Cc: passt-dev@passt.top
Subject: Re: [PATCH v8] dhcp, dhcpv6: Add hostname and client fqdn ops
Date: Tue, 14 Jan 2025 15:01:33 +0100 [thread overview]
Message-ID: <CAHVoYmJPxGHkt=WqU7H59pW0LPN=AjMm7Ov7Vs7poMSeLAfHaA@mail.gmail.com> (raw)
In-Reply-To: <20250111005249.34dfb995@elisabeth>
On Sat, Jan 11, 2025 at 12:53 AM Stefano Brivio <sbrivio@redhat.com> wrote:
>
> On Fri, 10 Jan 2025 11:26:26 +0100
> Enrique Llorente <ellorent@redhat.com> wrote:
>
> > 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@redhat.com>
> > ---
> > conf.c | 20 +++++++++--
> > dhcp.c | 63 ++++++++++++++++++++++++++------
> > dhcpv6.c | 93 ++++++++++++++++++++++++++++++++++++++++--------
> > passt.1 | 10 ++++++
> > passt.h | 5 +++
> > pasta.c | 17 ++++++---
> > test/lib/setup | 10 +++---
> > test/passt.mbuto | 6 ++--
> > test/passt/dhcp | 15 +++++++-
> > util.c | 24 +++++++++++++
> > util.h | 6 ++++
> > 11 files changed, 229 insertions(+), 40 deletions(-)
> >
> > diff --git a/conf.c b/conf.c
> > index df2b016..0cbd625 100644
> > --- a/conf.c
> > +++ b/conf.c
> > @@ -854,7 +854,9 @@ static void usage(const char *name, FILE *f, int status)
> > FPRINTF(f, " default: use addresses from /etc/resolv.conf\n");
> > FPRINTF(f,
> > " -S, --search LIST Space-separated list, search domains\n"
> > - " a single, empty option disables the DNS search list\n");
> > + " a single, empty option disables the DNS search list\n"
> > + " -H, --hostname NAME Hostname to configure client with\n"
> > + " --fqdn NAME FQDN to configure client with\n");
> > if (strstr(name, "pasta"))
> > FPRINTF(f, " default: don't use any search list\n");
> > else
> > @@ -1313,6 +1315,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 },
> > @@ -1357,6 +1360,7 @@ void conf(struct ctx *c, int argc, char **argv)
> > /* vhost-user backend program convention */
> > {"print-capabilities", no_argument, NULL, 26 },
> > {"socket-path", required_argument, NULL, 's' },
> > + {"fqdn", required_argument, NULL, 27 },
> > { 0 },
> > };
> > const char *logname = (c->mode == MODE_PASTA) ? "pasta" : "passt";
> > @@ -1379,9 +1383,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;
> > @@ -1558,6 +1562,11 @@ void conf(struct ctx *c, int argc, char **argv)
> > case 26:
> > vu_print_capabilities();
> > break;
> > + case 27:
> > + if (snprintf_check(c->fqdn, PASST_MAXDNAME,
> > + "%s", optarg))
> > + die("Invalid FQDN: %s", optarg);
> > + break;
> > case 'd':
> > c->debug = 1;
> > c->quiet = 0;
> > @@ -1727,6 +1736,11 @@ void conf(struct ctx *c, int argc, char **argv)
> >
> > die("Cannot use DNS search domain %s", optarg);
> > break;
> > + case 'H':
> > + if (snprintf_check(c->hostname, PASST_MAXDNAME,
> > + "%s", optarg))
> > + die("Invalid hostname: %s", optarg);
> > + break;
> > case '4':
> > v4_only = true;
> > v6_only = false;
> > diff --git a/dhcp.c b/dhcp.c
> > index d8515aa..8491075 100644
> > --- a/dhcp.c
> > +++ b/dhcp.c
> > @@ -63,6 +63,11 @@ static struct opt opts[255];
> >
> > #define OPT_MIN 60 /* RFC 951 */
> >
> > +/* Total option size (excluding end option) is 576 (RFC 2131), minus
> > + * offset of options (268), minus end option and its length (2).
> > + */
> > +#define OPT_MAX 306
> > +
> > /**
> > * dhcp_init() - Initialise DHCP options
> > */
> > @@ -122,7 +127,7 @@ struct msg {
> > uint8_t sname[64];
> > uint8_t file[128];
> > uint32_t magic;
> > - uint8_t o[308];
> > + uint8_t o[OPT_MAX + 2/* End option and its length */];
>
> Same as my comment to v6, same as my comment to v7:
>
> "Spaces around /* ... */ wouldn't be bad for readability."
>
> > } __attribute__((__packed__));
> >
> > /**
> > @@ -130,15 +135,28 @@ struct msg {
> > * @m: Message to fill
> > * @o: Option number
> > * @offset: Current offset within options field, updated on insertion
> > + *
> > + * Return: offset for the next option field or -1 if option doesn't fit
> > */
> > -static void fill_one(struct msg *m, int o, int *offset)
> > +static ssize_t fill_one(struct msg *m, int o, int offset)
> > {
> > - m->o[*offset] = o;
> > - m->o[*offset + 1] = opts[o].slen;
> > - memcpy(&m->o[*offset + 2], opts[o].s, opts[o].slen);
> > + size_t slen = opts[o].slen;
> > +
> > + /* If we don't have space to write the option, then just skip */
> > + if (offset + 1 /* length of option */ + slen > OPT_MAX)
> > + return -1;
> > +
> > + m->o[offset] = o;
> > + m->o[offset + 1] = slen;
> > +
> > + /* Move to option */
> > + offset += 2;
> > +
> > + memcpy(&m->o[offset], opts[o].s, slen);
> >
> > opts[o].sent = 1;
> > - *offset += 2 + opts[o].slen;
> > + offset += slen;
> > + return offset;
> > }
> >
> > /**
> > @@ -162,17 +180,20 @@ static int fill(struct msg *m)
> > * Put it there explicitly, unless requested via option 55.
> > */
> > if (opts[55].clen > 0 && !memchr(opts[55].c, 53, opts[55].clen))
> > - fill_one(m, 53, &offset);
> > + offset = fill_one(m, 53, offset);
>
> Now suppose that somebody adds a "long" option before this block, or...
>
> >
> > for (i = 0; i < opts[55].clen; i++) {
> > o = opts[55].c[i];
> > if (opts[o].slen != -1)
> > - fill_one(m, o, &offset);
> > + offset = fill_one(m, o, offset);
> > }
>
> before this one, and fill_one() returns -1. Then...
>
> > for (o = 0; o < 255; o++) {
> > - if (opts[o].slen != -1 && !opts[o].sent)
> > - fill_one(m, o, &offset);
> > + if (opts[o].slen != -1 && !opts[o].sent) {
> > + offset = fill_one(m, o, offset);
>
> you use offset as -1 here, and boom. If not, see directly below).
>
> > + if (offset == -1)
>
> Or maybe the domain name is actually too long, and offset is -1 here...
>
> > + debug("DHCP: skipping option %i", o);
> > + }
> > }
> >
> > m->o[offset++] = 255;
>
> ...and boom. This sets the last byte of "magic" (depending on the
> architecture) to 0xff, making the whole message invalid.
>
well, I was expecting the only problematic options being at > 55
(didn't check the standard)
but I agree that we have to be consistent on handling the fill_in result.
> Two solutions:
>
> - the obvious one, which adds a couple of lines but it doesn't look
> that bad: check if fill_one() returns -1 before setting offset
>
> - the short but maybe hacky one (I would personally go with this, but
> it's a matter of taste): make fill_one() return bool, true means
> failure, and keep passing offset as a pointer, which is *not* set on
> failure. Then you get, simply:
>
> if (fill_one(m, o, &offset))
> debug("DHCP: skipping option %i", o);
>
I will go with the second one since it's more passt idiomatic and will
add the check at all the
three blocks.
> > @@ -398,6 +419,28 @@ int dhcp(const struct ctx *c, const struct pool *p)
> > if (!opts[6].slen)
> > opts[6].slen = -1;
> >
> > + opt_len = strlen(c->hostname);
> > + if (opt_len > 0) {
> > + opts[12].slen = opt_len;
> > + memcpy(opts[12].s, &c->hostname, opt_len);
> > + }
> > +
> > + opt_len = strlen(c->fqdn);
> > + if (opt_len > 0) {
> > + opt_len += 3/* flags */ + 2/* extra for encoded fqdn */;
>
> Same as several comments on several revisions:
>
> 3 /* flags */ + 2 /* ... */
>
> It's FQDN, not fqdn.
>
> > +
> > + if (sizeof(opts[81].s) < opt_len)
> > + debug("DHCP: client FQDN option do not fit, skipping");
>
> Same as several comments on several revisions: "doesn't"
>
> > +
> > + opts[81].s[0] = 0x4; /* flags (E) */
> > + opts[81].s[1] = 0xff; /* RCODE1 */
> > + opts[81].s[2] = 0xff; /* RCODE2 */
> > +
> > + encode_domain_name((char *)opts[81].s + 3, c->fqdn);
> > +
> > + opts[81].slen = opt_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..29e8b02 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;
> > @@ -58,6 +59,9 @@ struct opt_hdr {
> > sizeof(struct opt_hdr))
> > #define OPT_VSIZE(x) (sizeof(struct opt_##x) - \
> > sizeof(struct opt_hdr))
> > +#define OPT_MAX_SIZE IPV6_MIN_MTU - (sizeof(struct ipv6hdr) + \
> > + sizeof(struct udphdr) + \
> > + sizeof(struct msg_hdr))
> >
> > /**
> > * struct opt_client_id - DHCPv6 Client Identifier option
> > @@ -163,6 +167,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 described by RFC 4704
> > + * @domain_name: Client FQDN
> > + */
> > +struct opt_client_fqdn {
> > + struct opt_hdr hdr;
> > + uint8_t flags;
> > + char domain_name[PASST_MAXDNAME];
> > +} __attribute__((packed));
> > +
> > /**
> > * struct msg_hdr - DHCPv6 client/server message header
> > * @type: DHCP message type
> > @@ -193,6 +209,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,6 +220,7 @@ 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,
> > @@ -228,6 +246,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 = {
> > @@ -346,7 +368,6 @@ static size_t dhcpv6_dns_fill(const struct ctx *c, char *buf, int offset)
> > {
> > struct opt_dns_servers *srv = NULL;
> > struct opt_dns_search *srch = NULL;
> > - char *p = NULL;
> > int i;
> >
> > if (c->no_dhcp_dns)
> > @@ -383,34 +404,75 @@ search:
> > if (!name_len)
> > continue;
> >
> > + name_len += 2/* encoded domain name extra bytes */;
>
> Same as several comments on several revisions: leave spaces between
> comments and code. There's no need to add this before the ;, it could
> simply be:
>
> name_len += 2; /* Length byte for first label, and terminator */
>
> > + if (name_len > NS_MAXDNAME) {
>
> With this, if I do:
>
> $ ./passt -d -f -S thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.and_get_to_253_with_this_one,a.b -p search.pcap
>
> I get (try and see capture):
>
> - a single domain name that's 258 characters long (terminator is
> missing)
>
> - a 3-byte FQDN option (there should be none)
>
Right, we should add the first byte to the check
if (name_len > NS_MAXDNAME + 1) {
> > + debug("DHCP: DNS search name '%s' too big, skipping",
>
> I'd say it's too long, rather than big.
>
> > + c->dns_search[i].n);
> > + continue;
> > + }
> > +
> > if (!srch) {
> > srch = (struct opt_dns_search *)(buf + offset);
> > offset += sizeof(struct opt_hdr);
> > srch->hdr.t = OPT_DNS_SEARCH;
> > srch->hdr.l = 0;
> > - p = srch->list;
> > }
> >
> > - *p = '.';
> > - p = stpncpy(p + 1, c->dns_search[i].n, name_len);
> > - p++;
> > - srch->hdr.l += name_len + 2;
> > - offset += name_len + 2;
> > + encode_domain_name(buf + offset, c->dns_search[i].n);
> > +
> > + srch->hdr.l += name_len;
> > + offset += name_len;
> > +
> > }
> >
> > - if (srch) {
> > - for (i = 0; i < srch->hdr.l; i++) {
> > - if (srch->list[i] == '.') {
> > - srch->list[i] = strcspn(srch->list + i + 1,
> > - ".");
> > - }
> > - }
> > + if (srch)
> > srch->hdr.l = htons(srch->hdr.l);
> > - }
> >
> > 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 pool *p, const struct ctx *c,
> > + char *buf, int offset)
> > +
> > +{
> > + struct opt_client_fqdn const *req_opt;
> > + struct opt_client_fqdn *o;
> > + size_t opt_len;
> > +
> > + opt_len = strlen(c->fqdn) + 2/* encoded domain name extra bytes */;
>
> Same as above.
>
> > + if (opt_len > MIN(PASST_MAXDNAME,
>
> This doesn't quite work:
>
> $ ./passt -d -f --fqdn thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.and_get_to_253_with_this_one -p fqdn.pcap
>
> [...]
>
> 9.0202: DHCP: client FQDN option do not fit, skipping
>
> and the check for the domain search list would have the same problem:
> now you add 2 to opt_len (length of first label, and terminator), but
> it's only the terminator accounted for in PASST_MAXDNAME (which is
> correct: that represents the storage size).
>
> However, you can actually send 255 bytes (including those two extra
> bytes).
>
> That is, the failure should be on strlen(c->fqdn) + 1 > PASST_MAXDNAME,
> or, equivalently, opt_len > PASST_MAXDNAME + 1.
>
> By the way, don't you already check that in conf()? I guess the check
> here could simply be on the remaining space in the options area? I'm
> not sure, I didn't really think this through.
>
Right, --fqdn config value is already checked at starting time, no
need for redundant checks,
we just need to check the limits with the packet max size:
opt_len = strlen(c->fqdn) + 2; /* Length byte for first label,
and terminator */
if (opt_len > OPT_MAX_SIZE - (offset +
sizeof(struct opt_hdr) +
1 /* flags */ )) {
debug("DHCPv6: client FQDN option doesn't fit, skipping");
return offset;
}
Also fix the search domain check to take into account the first label
length byte.
name_len += 2; /* Length byte for first label, and terminator */
if (name_len > NS_MAXDNAME +
1 /* Length byte for first label */) {
debug("DHCP: DNS search name '%s' too long, skipping",
c->dns_search[i].n);
continue;
}
> > + OPT_MAX_SIZE - (offset +
> > + sizeof(struct opt_hdr) +
> > + 1/* flags */))){
>
> Same as several comments on several revisions: leave spaces around
> comments. Missing whitespace between ) and {.
>
> > + debug("DHCPv6: client FQDN option doesn't fit, skipping");
> > + return offset;
> > + }
> > +
> > + o = (struct opt_client_fqdn *)(buf + offset);
> > + encode_domain_name(o->domain_name, c->fqdn);
> > + req_opt = (struct opt_client_fqdn *)dhcpv6_opt(p, &(size_t){ 0 },
> > + OPT_CLIENT_FQDN);
> > + if (req_opt && req_opt->flags & 0x01 /* S flag */)
> > + o->flags = 0x02 /* O flag */;
> > + else
> > + o->flags = 0x00;
> > +
> > + opt_len++;
> > +
> > + o->hdr.t = OPT_CLIENT_FQDN;
> > + o->hdr.l = htons(opt_len);
> > +
> > + return offset + sizeof(struct opt_hdr) + opt_len;
> > +}
> > +
> > /**
> > * dhcpv6() - Check if this is a DHCPv6 message, reply as needed
> > * @c: Execution context
> > @@ -544,6 +606,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(p, c, (char *)&resp, n);
> >
> > resp.hdr.xid = mh->xid;
> >
> > diff --git a/passt.1 b/passt.1
> > index d9cd33e..7051fc4 100644
> > --- a/passt.1
> > +++ b/passt.1
> > @@ -401,6 +401,16 @@ Enable IPv6-only operation. IPv4 traffic will be ignored.
> > By default, IPv4 operation is enabled as long as at least an IPv4 route and an
> > interface address are configured on a given host interface.
> >
> > +.TP
> > +.BR \-H ", " \-\-hostname " " \fIname
> > +Hostname to configure the client with.
> > +Send \fIname\fR as DHCP option 12 (hostname).
> > +
> > +.TP
> > +.BR \-\-fqdn " " \fIname
> > +FQDN to configure the client with.
> > +Send \fIname\fR as Client FQDN: DHCP option 81 and DHCPv6 option 39.
> > +
> > .SS \fBpasst\fR-only options
> >
> > .TP
> > diff --git a/passt.h b/passt.h
> > index 0dd4efa..f3151f0 100644
> > --- a/passt.h
> > +++ b/passt.h
> > @@ -209,6 +209,8 @@ struct ip6_ctx {
> > * @ifi4: Template interface for IPv4, -1: none, 0: IPv4 disabled
> > * @ip: IPv4 configuration
> > * @dns_search: DNS search list
> > + * @hostname: Guest hostname
> > + * @fqdn: Guest FQDN
> > * @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled
> > * @ip6: IPv6 configuration
> > * @pasta_ifn: Name of namespace interface for pasta
> > @@ -269,6 +271,9 @@ struct ctx {
> >
> > struct fqdn dns_search[MAXDNSRCH];
> >
> > + char hostname[PASST_MAXDNAME];
> > + char fqdn[PASST_MAXDNAME];
> > +
> > int ifi6;
> > struct ip6_ctx ip6;
> >
> > diff --git a/pasta.c b/pasta.c
> > index ff41c95..922aa10 100644
> > --- a/pasta.c
> > +++ b/pasta.c
> > @@ -169,10 +169,12 @@ void pasta_open_ns(struct ctx *c, const char *netns)
> > * struct pasta_spawn_cmd_arg - Argument for pasta_spawn_cmd()
> > * @exe: Executable to run
> > * @argv: Command and arguments to run
> > + * @ctx: Context to read config from
> > */
> > struct pasta_spawn_cmd_arg {
> > const char *exe;
> > char *const *argv;
> > + struct ctx *c;
> > };
> >
> > /**
> > @@ -186,6 +188,7 @@ static int pasta_spawn_cmd(void *arg)
> > {
> > char hostname[HOST_NAME_MAX + 1] = HOSTNAME_PREFIX;
> > const struct pasta_spawn_cmd_arg *a;
> > + size_t conf_hostname_len;
> > sigset_t set;
> >
> > /* We run in a detached PID and mount namespace: mount /proc over */
> > @@ -195,9 +198,15 @@ static int pasta_spawn_cmd(void *arg)
> > if (write_file("/proc/sys/net/ipv4/ping_group_range", "0 0"))
> > warn("Cannot set ping_group_range, ICMP requests might fail");
> >
> > - if (!gethostname(hostname + sizeof(HOSTNAME_PREFIX) - 1,
> > - HOST_NAME_MAX + 1 - sizeof(HOSTNAME_PREFIX)) ||
> > - errno == ENAMETOOLONG) {
> > + a = (const struct pasta_spawn_cmd_arg *)arg;
> > +
> > + conf_hostname_len = strlen(a->c->hostname);
> > + if (conf_hostname_len > 0) {
> > + if (sethostname(a->c->hostname, conf_hostname_len))
> > + warn("Unable to set configured hostname");
> > + } else if (!gethostname(hostname + sizeof(HOSTNAME_PREFIX) - 1,
> > + HOST_NAME_MAX + 1 - sizeof(HOSTNAME_PREFIX)) ||
> > + errno == ENAMETOOLONG) {
> > hostname[HOST_NAME_MAX] = '\0';
> > if (sethostname(hostname, strlen(hostname)))
> > warn("Unable to set pasta-prefixed hostname");
> > @@ -208,7 +217,6 @@ static int pasta_spawn_cmd(void *arg)
> > sigaddset(&set, SIGUSR1);
> > sigwaitinfo(&set, NULL);
> >
> > - a = (const struct pasta_spawn_cmd_arg *)arg;
> > execvp(a->exe, a->argv);
> >
> > die_perror("Failed to start command or shell");
> > @@ -230,6 +238,7 @@ void pasta_start_ns(struct ctx *c, uid_t uid, gid_t gid,
> > struct pasta_spawn_cmd_arg arg = {
> > .exe = argv[0],
> > .argv = argv,
> > + .c = c,
> > };
> > char uidmap[BUFSIZ], gidmap[BUFSIZ];
> > char *sh_argv[] = { NULL, NULL };
> > diff --git a/test/lib/setup b/test/lib/setup
> > index 580825f..ee67152 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.test -P ${STATESETUP}/passt.pid"
> >
> > # pidfile isn't created until passt is listening
> > wait_for [ -f "${STATESETUP}/passt.pid" ]
> > @@ -160,11 +160,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.test -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.test -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" ]
> >
> > @@ -243,7 +243,7 @@ setup_two_guests() {
> > [ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
> > [ ${VHOST_USER} -eq 1 ] && __opts="${__opts} --vhost-user"
> >
> > - 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.test -H hostname1 -t 10001 -u 10001"
> > wait_for [ -f "${STATESETUP}/passt_1.pid" ]
> >
> > __opts=
> > @@ -252,7 +252,7 @@ setup_two_guests() {
> > [ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
> > [ ${VHOST_USER} -eq 1 ] && __opts="${__opts} --vhost-user"
> >
> > - 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" ]
> >
> > __vmem="$((${MEM_KIB} / 1024 / 4))"
> > 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..145f1ba 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,16 @@ 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.test" ]
> > +
> > 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
> > @@ -70,3 +79,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.test" ]
> > diff --git a/util.c b/util.c
> > index 11973c4..40d95bf 100644
> > --- a/util.c
> > +++ b/util.c
> > @@ -837,3 +837,27 @@ void raw_random(void *buf, size_t buflen)
> > if (random_read < buflen)
> > die("Unexpected EOF on random data source");
> > }
> > +/**
> > + * encode_domain_name() - Encode domain name according to RFC 1035, section 3.1
> > + * @buf: Buffer to fill in with encoded domain name
> > + * @domain_name: Input domain name string with terminator
> > + *
> > + * The buffer's 'buf' size has to be >= strlen(domain_name) + 2
> > + */
> > +void encode_domain_name(char *buf, const char *domain_name)
> > +{
> > + size_t i;
> > + char *p;
> > +
> > + buf[0] = strcspn(domain_name, ".");
> > + p = buf + 1;
> > + for (i = 0; ; i++) {
>
> That's equivalent to having domain_name[i] as loop condition I suppose,
> which would look a bit clearer.
>
> > + if (domain_name[i] == '.')
>
The only difference is that if I do:
for (i = 0; domain_name[9]; i++)
then we will have to add the terminator after the loop like we were
doing before, that's not that bad.
But the end result is easier to follow than what I have now:
for (i = 0; domain_name[i]; i++) {
if (domain_name[i] == '.')
p[i] = strcspn(domain_name + i + 1, ".");
else
p[i] = domain_name[i];
}
p[i] = 0L;
> I don't think you'll actually need the extra condition in the else
> block below, but for consistency (same as kernel coding style), if one
> conditional block has curly brackets, we'll use them for all the others
> too, even if they are a single line.
>
I am going to check doimain_name as the for loop so no need for that.
> > + p[i] = strcspn(domain_name + i + 1, ".");
> > + else {
> > + p[i] = domain_name[i];
> > + if (p[i] == 0L)
> > + break;
> > + }
> > + }
> > +}
> > diff --git a/util.h b/util.h
> > index 3fa1d12..0744276 100644
> > --- a/util.h
> > +++ b/util.h
> > @@ -40,6 +40,9 @@
> > #ifndef IP_MAX_MTU
> > #define IP_MAX_MTU USHRT_MAX
> > #endif
> > +#ifndef IPV6_MIN_MTU
> > +#define IPV6_MIN_MTU 1280
> > +#endif
> >
> > #ifndef MIN
> > #define MIN(x, y) (((x) < (y)) ? (x) : (y))
> > @@ -346,4 +349,7 @@ static inline int wrap_accept4(int sockfd, struct sockaddr *addr,
> > #define accept4(s, addr, addrlen, flags) \
> > wrap_accept4((s), (addr), (addrlen), (flags))
> >
> > +#define PASST_MAXDNAME 254 /* 253 (RFC 1035) + 1 (the terminator) */
> > +void encode_domain_name(char *buf, const char *domain_name);
> > +
> > #endif /* UTIL_H */
>
> The rest looks good to me, but I didn't try really hard to break it.
>
> --
> Stefano
>
--
Quique Llorente
CNV networking Senior Software Engineer
Red Hat EMEA
ellorent@redhat.com
@RedHat Red Hat Red Hat
next prev parent reply other threads:[~2025-01-14 14:01 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-01-10 10:26 [PATCH v8] dhcp, dhcpv6: Add hostname and client fqdn ops Enrique Llorente
2025-01-10 23:52 ` Stefano Brivio
2025-01-14 14:01 ` Enrique Llorente Pastora [this message]
2025-01-14 19:32 ` Stefano Brivio
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CAHVoYmJPxGHkt=WqU7H59pW0LPN=AjMm7Ov7Vs7poMSeLAfHaA@mail.gmail.com' \
--to=ellorent@redhat.com \
--cc=passt-dev@passt.top \
--cc=sbrivio@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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).