public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: Enrique Llorente Pastora <ellorent@redhat.com>
To: Stefano Brivio <sbrivio@redhat.com>
Cc: passt-dev@passt.top
Subject: Re: [PATCH v9] dhcp, dhcpv6: Add hostname and client fqdn ops
Date: Wed, 22 Jan 2025 14:20:22 +0100	[thread overview]
Message-ID: <CAHVoYmJaD6dk9az2HTVWcCp2atW-rhDY9xXn9rW7PX_-8bF_mQ@mail.gmail.com> (raw)
In-Reply-To: <20250122132932.21f30b7c@elisabeth>

On Wed, Jan 22, 2025 at 1:29 PM Stefano Brivio <sbrivio@redhat.com> wrote:
>
> On Wed, 22 Jan 2025 13:09:07 +0100
> Enrique Llorente Pastora <ellorent@redhat.com> wrote:
>
> > On Thu, Jan 16, 2025 at 1:31 AM Stefano Brivio <sbrivio@redhat.com> wrote:
> > >
> > > Thanks for sticking with this. The general approach and almost the
> > > whole patch looks good to me now, but there are still a couple of
> > > issues with the options:
> > >
> > > On Wed, 15 Jan 2025 09:18:44 +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           | 60 ++++++++++++++++++++++++++-----
> > > >  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           | 22 ++++++++++++
> > > >  util.h           |  6 ++++
> > > >  11 files changed, 226 insertions(+), 38 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..ba66f47 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 */ ];
> > > >  } __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: false if m has space to write the option, true otherwise
> > > >   */
> > > > -static void fill_one(struct msg *m, int o, int *offset)
> > > > +static bool fill_one(struct msg *m, int o, int *offset)
> > > >  {
> > > > +     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 true;
> > > > +
> > > >       m->o[*offset] = o;
> > > > -     m->o[*offset + 1] = opts[o].slen;
> > > > -     memcpy(&m->o[*offset + 2], opts[o].s, opts[o].slen);
> > > > +     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 false;
> > > >  }
> > > >
> > > >  /**
> > > > @@ -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);
> > > > +             if (fill_one(m, 53, &offset))
> > > > +                      debug("DHCP: skipping option 55");
> > >
> > > We're skipping option 53 in this case, not 55.
> > >
> > > >
> > > >       for (i = 0; i < opts[55].clen; i++) {
> > > >               o = opts[55].c[i];
> > > >               if (opts[o].slen != -1)
> > > > -                     fill_one(m, o, &offset);
> > > > +                     if (fill_one(m, o, &offset))
> > > > +                             debug("DHCP: skipping option %i", o);
> > > >       }
> > > >
> > > >       for (o = 0; o < 255; o++) {
> > > >               if (opts[o].slen != -1 && !opts[o].sent)
> > > > -                     fill_one(m, o, &offset);
> > > > +                     if (fill_one(m, o, &offset))
> > > > +                             debug("DHCP: skipping option %i", o);
> > > >       }
> > > >
> > > >       m->o[offset++] = 255;
> > > > @@ -398,6 +419,29 @@ 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);
> > > > +     }
> > >
> > > So I tried (note the comma):
> > >
> > > $ ./passt -d -f --hostname thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.and_get_to_253_with_this_one, -p fqdn.pcap
> > >
> > > $ ./passt -d -f --hostname , -p fqdn.pcap
> > >
> > > $ ./passt -d -f --hostname "" -p fqdn.pcap
> > >
> > > the hostname and the DHCP reply are fine, but we get an empty FQDN
> > > option in the DHCPv6 reply (only).
> > >
> > > Note that RFC 2132 (3.14) says, about option 12:
> > >
> > >   See RFC 1035 for character set restrictions.
> > >
> > > but I'm not sure if we should validate the user input any further. I
> > > wouldn't. One might legitimately use passt to test the robustness of a
> > > DHCP client.
> > >
> >
> > I understand that we are talking about two different things:
> > - The fact that adding "," to option 12 is not ok
>
> No, I think it's actually fine, I just wanted to make you aware of that.
>
> > - And DHCPv6 is broken if we don't pass --fqdn because it adds a "."
> > field that breaks it.
>
> Right, yes.
>
> > I will fix the DHCPv6 FQDN client option, not sure about validation
> > input, but it's true that more or less
> > we are going to send virtual machine names at hostnames and by
> > kubernetes standard those should be a fqdn,
> > so they are implicitly validated.
>
> Well, this is completely independent of what Kubernetes might do with
> it one day. But still, I would *not* validate it because passt is also
> quite useful (and used) to test this kind of stuff.
>
> > > > +
> > > > +     opt_len = strlen(c->fqdn);
> > > > +     if (opt_len > 0) {
> > > > +             opt_len += 3 /* flags */
> > > > +                     + 2; /* Length byte for first label, and terminator */
> > > > +
> > > > +             if (sizeof(opts[81].s) < opt_len)
> > > > +                     debug("DHCP: client FQDN option doesn't fit, skipping");
> > >
> > > For this one, I think you actually need to maintain the check in the
> > > caller of encode_domain_name() (that is, here), because of the three
> > > bytes for the flags.
> >
> > Right, I am not "skipping", I will fix that.
> >
> > > Otherwise, with this:
> > >
> > >   $ ./passt -d -f --fqdn
> > >   thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.and_get_to_253_with_this_one, -p fqdn.pcap
> > >
> > > this message is printed, but we end up with an option 81 with length
> > > 2, and the FQDN ends up in the padding (try and see capture).
> > >
> > > With this:
> > >
> > >   $ ./passt -d -f --fqdn
> > >  thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.and_get_to_252_with_this_one -p fqdn.pcap
> > >
> > > length becomes 1, and the FQDN is still in the padding.
> > >
> > > With this:
> > >
> > >   $ ./passt -d -f --fqdn
> > >  thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.make_that_251_with_this_one -p fqdn.pcap
> > >
> > > length becomes 0, end option is missing, and we have an option 4 (time
> > > server) instead.
> >
> > After the fix I see just proper padding with 0 at the end, is this fine ?
>
> Probably. We just shouldn't have unnecessary padding.
>
> By the way, this change should depend on another change *not* reusing
> the original message as buffer for the reply, as discussed.
>

Totally forgot about that, should I make a patchset or different changes ?

> > > With this:
> > >
> > >   $ ./passt -d -f --fqdn
> > >  thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.thirtytwocharactersforeachlabel.then_its_250_with_this_one -p fqdn.pcap
> > >
> > > things are almost fine. We get one byte of padding, which is probably
> > > not needed, but I didn't look into it.
> >
> > And at this case I see proper option 81 configured with length 255
> > (250 + 3 /* flags */ + 2 /* first length and terminator */)
> >
> > So with the fix now it looks good, I am super worried that we don't
> > have this at the tests, but we will go back to it when the proper
> > testing framework lands.
>
> I'm not, in the sense that this isn't really security relevant *unless
> we leak host memory into the guest*. The rest is a functional issue,
> and it's not easy to break this "by mistake".
>
> Sure, ideally we should have tests for those, but... they don't break
> by themselves. This code is pretty much isolated and stand-alone.
>

Fair enough.

> --
> Stefano
>


-- 
Quique Llorente

CNV networking Senior Software Engineer

Red Hat EMEA

ellorent@redhat.com

@RedHat   Red Hat  Red Hat


  reply	other threads:[~2025-01-22 13:20 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-01-15  8:18 [PATCH v9] dhcp, dhcpv6: Add hostname and client fqdn ops Enrique Llorente
2025-01-16  0:30 ` Stefano Brivio
2025-01-22 12:09   ` Enrique Llorente Pastora
2025-01-22 12:29     ` Stefano Brivio
2025-01-22 13:20       ` Enrique Llorente Pastora [this message]
2025-01-22 13:29         ` Stefano Brivio
2025-01-22 13:48           ` Enrique Llorente Pastora

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=CAHVoYmJaD6dk9az2HTVWcCp2atW-rhDY9xXn9rW7PX_-8bF_mQ@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).