From nobody Fri Sep 11 05:04:24 2026 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4hh2X93s2Dz6rbbL for ; Fri, 11 Sep 2026 05:04:25 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR2" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4hh2X85HYnz4CBC for ; Fri, 11 Sep 2026 05:04:24 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1789103064; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=xL7AMcJiXIFoQNBAdBf6+6Aku8WxZQEpCmQATG1sqGU=; b=LcReZ7mWGrT8EodYZGUCQGklWdeJcAJ6xfpSq4Ttgw+0B0BzH9uKe5nAi4XDy1j8KdZGyh sT1OAVa8m4mMAPRNlcql6kbO5NsfWWpA5d8je1ufFVITCGOaBNZ6VeenK60AcLgBCCe1vv If2aAmmwGw3YGXgqr2hs3mwrpA38R01BQFR1m6KgZoe1qVrI4b1p7DOWlqLH8d/HMkFwxo sYZOEPxIl6undAQ7Ulco3exwiN4DbAUPhjMecvWzig54hJrX5uCY7sanWTmcG9B1xyE1Xf Sh965NCfufLiimv7pWF9cni91k0dnOueS3/s3ROF5NsY/GwOHUWeO8e/PCPy7A== ARC-Seal: i=1; a=rsa-sha256; d=freebsd.org; s=dkim; cv=none; t=1789103064; b=WQ1HMZXM5ssbzb8VuypjhK+tvlwVspm54qzvHCgvVRT+JBHr3i5HahNVHd0yGOvP4FB8TD KwRe1gl8VDKTW/H2NxCFVNt1u2ZRZpdgY9ETtrgIN0sKD7pxGssuvNisRdClJf4iCwRnQT 5nNGh7lwUmEXVZMbG9xKSJdAXlkX3eY/9wmDOdOR4xQzGdI4G6csLcM2OQLZ1jvtp/pxiU 5RZkvlwWjSxOdlUVhUTz4ppoX1ti9cNu+lSih4eLMnUsaMVn47CJF1Xop9SjZGer/DhmxC cWZ6kCgpol5JSZ3cZwMGOfc5HDtC9xDbAxmnr0V39Uvg1gsOpcVUG4/FJzVtcA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1789103064; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=xL7AMcJiXIFoQNBAdBf6+6Aku8WxZQEpCmQATG1sqGU=; b=TGf4ya83w9rGZ6ZX9jGc7b12sX1Tc/xItDZy3xPi9Viyu6KR12ORyAxX1dKF6HAy9iNhXK CWIJVZXhk/vPlqct3hcb1pB+Rgvbw/7IcHl8x+fT7c5XUgKpUIJkStrM20t0WiMqIaMkc8 QfpxfKFkSqkE1xgNimzeNBv4gWa7lFsF0iofVqwWJDvZBXD3qsgfr3Fj+3ypl+T4j37b+s p/d4FYxWM1UtZOnp99Qcij0R3LUBiHziOUt7sbIgBIIVTH7vyh/EXsqnx652/mFgyROFiO O11T185x1FFxcdOWI3SRcFsvz5q93iCF2Qer/lCn8U8n/yeAb1mcoTndDYZgEA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4hh2X84MMlz10TR for ; Fri, 11 Sep 2026 05:04:24 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3c205 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Fri, 11 Sep 2026 05:04:24 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Warner Losh Subject: git: 6788e42d53c6 - main - stand/efihttp: Support arbitrary http[N]://host[:port]/path devspecs List-Id: Commit messages for the main branch of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: imp X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 6788e42d53c60904943a1db11bdb27b8f9d2bb4c Auto-Submitted: auto-generated Date: Fri, 11 Sep 2026 05:04:24 +0000 Message-Id: <6aa38bd8.3c205.4b4c6b01@gitrepo.freebsd.org> The branch main has been updated by imp: URL: https://cgit.FreeBSD.org/src/commit/?id=6788e42d53c60904943a1db11bdb27b8f9d2bb4c commit 6788e42d53c60904943a1db11bdb27b8f9d2bb4c Author: Warner Losh AuthorDate: 2026-09-11 04:13:46 +0000 Commit: Warner Losh CommitDate: 2026-09-11 04:15:28 +0000 stand/efihttp: Support arbitrary http[N]://host[:port]/path devspecs Until now, the "http" device only worked when firmware itself booted loader.efi via EFI HTTP Boot: efihttp_dev_init() required a URI node on the loader's own boot device path, and every fetch went to that same server. Add a second form, using the parse_uri() helper from libsa/dev.c, that works anywhere: http[N]://host[:port]/path (N is a network interface unit, defaulting to 0). This lets a devspec like this be used as rootdev, in loader.conf, or interactively, independent of how loader.efi itself was booted. Network configuration is shared, loader-wide state (the myip/netmask/ gateip/nameip globals from net.h): if something has already configured them (e.g. tftp/nfs via net_open()), the http device just reuses those values, applying them to EFI_IP4_CONFIG2_PROTOCOL as a static address. If nothing has, it drives IP4Config2's own DHCP directly instead of calling netdev's net_open(): net_open() opens the NIC's SNP EFI_OPEN_PROTOCOL_EXCLUSIVE, which would disconnect the Mnp/Ip4/.../ HttpDxe driver chain this code depends on. That chain is also commonly disconnected already by whatever chained us here (e.g. iPXE excludes and exclusively opens SNP for its own raw I/O), so reconnect it explicitly with ConnectController() before giving up. The legacy EFI HTTP Boot path is untouched. Sponsored by: Netflix Differential Revision: https://reviews.freebsd.org/D59423 --- stand/efi/libefi/efihttp.c | 344 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 342 insertions(+), 2 deletions(-) diff --git a/stand/efi/libefi/efihttp.c b/stand/efi/libefi/efihttp.c index b18607410f3b..f725ba0709d8 100644 --- a/stand/efi/libefi/efihttp.c +++ b/stand/efi/libefi/efihttp.c @@ -49,11 +49,16 @@ static EFI_GUID ip4config2_guid = EFI_IP4_CONFIG2_PROTOCOL_GUID; static bool efihttp_init_done = false; +struct http_devdesc; + static int efihttp_dev_init(void); static int efihttp_dev_strategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf, size_t *rsize); static int efihttp_dev_open(struct open_file *f, ...); +static int efihttp_dev_open_legacy(struct open_file *f); +static int efihttp_dev_open_url(struct open_file *f, struct http_devdesc *hd); static int efihttp_dev_close(struct open_file *f); +static int efihttp_parsedev(struct devdesc **, const char *, const char **); static int efihttp_fs_open(const char *path, struct open_file *f); static int efihttp_fs_close(struct open_file *f); @@ -79,6 +84,16 @@ struct file_efihttp { bool is_dir; }; +/* + * host == NULL for the legacy "httpN:" EFI HTTP Boot form; non-NULL for + * the "httpN://host/path" URL form. + */ +struct http_devdesc { + struct devdesc dd; + char *host; + int port; +}; + struct devsw efihttp_dev = { .dv_name = "http", .dv_type = DEVT_NET, @@ -89,6 +104,7 @@ struct devsw efihttp_dev = { .dv_ioctl = noioctl, .dv_print = NULL, .dv_cleanup = nullsys, + .dv_parsedev = efihttp_parsedev, }; struct fs_ops efihttp_fsops = { @@ -175,6 +191,135 @@ setup_ipv4_config2(EFI_HANDLE handle, MAC_ADDR_DEVICE_PATH *mac, return (0); } +/* + * Like setup_ipv4_config2()'s static branch, but sourced from the + * loader-wide myip/netmask/gateip/nameip globals instead of boot-path + * device nodes. + */ +static int +setup_ipv4_config2_static(EFI_HANDLE handle, struct in_addr ip, n_long mask, + struct in_addr gw, struct in_addr dns) +{ + EFI_IP4_CONFIG2_PROTOCOL *ip4config2; + EFI_IP4_CONFIG2_MANUAL_ADDRESS manual; + EFI_IPv4_ADDRESS addr; + EFI_STATUS status; + + status = BS->OpenProtocol(handle, &ip4config2_guid, + (void **)&ip4config2, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(status)) + return (efi_status_to_errno(status)); + + status = ip4config2->SetData(ip4config2, + Ip4Config2DataTypePolicy, sizeof(EFI_IP4_CONFIG2_POLICY), + &(EFI_IP4_CONFIG2_POLICY) { Ip4Config2PolicyStatic }); + if (EFI_ERROR(status)) + return (efi_status_to_errno(status)); + + memset(&manual, 0, sizeof(manual)); + memcpy(manual.Address.Addr, &ip, sizeof(manual.Address.Addr)); + memcpy(manual.SubnetMask.Addr, &mask, sizeof(manual.SubnetMask.Addr)); + status = ip4config2->SetData(ip4config2, + Ip4Config2DataTypeManualAddress, sizeof(manual), &manual); + if (EFI_ERROR(status)) + return (efi_status_to_errno(status)); + + if (gw.s_addr != 0) { + memcpy(addr.Addr, &gw, sizeof(addr.Addr)); + status = ip4config2->SetData(ip4config2, + Ip4Config2DataTypeGateway, sizeof(addr), &addr); + if (EFI_ERROR(status)) + return (efi_status_to_errno(status)); + } + + if (dns.s_addr != 0) { + memcpy(addr.Addr, &dns, sizeof(addr.Addr)); + status = ip4config2->SetData(ip4config2, + Ip4Config2DataTypeDnsServer, sizeof(addr), &addr); + if (EFI_ERROR(status)) + return (efi_status_to_errno(status)); + } + + return (0); +} + +/* + * DHCPs via IP4Config2 directly, not netdev's net_open(): net_open() + * opens the NIC's SNP EFI_OPEN_PROTOCOL_EXCLUSIVE (efinet_probe()), + * which disconnects the MNP/IP4/HttpDxe chain this handle needs. + */ +static int +efi_ip4_dhcp(EFI_HANDLE handle, struct in_addr *ip, n_long *mask, + struct in_addr *gw, struct in_addr *dns) +{ + EFI_IP4_CONFIG2_PROTOCOL *ip4config2; + EFI_IP4_CONFIG2_INTERFACE_INFO *info; + EFI_IPv4_ADDRESS dnsaddr; + EFI_STATUS status; + UINTN sz, i, nroutes; + int polltime; + + status = BS->OpenProtocol(handle, &ip4config2_guid, + (void **)&ip4config2, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(status)) + return (efi_status_to_errno(status)); + + status = ip4config2->SetData(ip4config2, + Ip4Config2DataTypePolicy, sizeof(EFI_IP4_CONFIG2_POLICY), + &(EFI_IP4_CONFIG2_POLICY) { Ip4Config2PolicyDhcp }); + if (EFI_ERROR(status)) + return (efi_status_to_errno(status)); + + info = NULL; + polltime = 0; + for (;;) { + free(info); + info = NULL; + sz = 0; + status = ip4config2->GetData(ip4config2, + Ip4Config2DataTypeInterfaceInfo, &sz, NULL); + if (status == EFI_BUFFER_TOO_SMALL) { + info = malloc(sz); + if (info == NULL) + return (ENOMEM); + status = ip4config2->GetData(ip4config2, + Ip4Config2DataTypeInterfaceInfo, &sz, info); + } + if (!EFI_ERROR(status) && + *(uint32_t *)info->StationAddress.Addr != 0) + break; + if (polltime >= EFIHTTP_POLL_TIMEOUT) { + free(info); + return (ENXIO); + } + delay(100 * 1000); + polltime += 100; + } + + memcpy(ip, info->StationAddress.Addr, sizeof(*ip)); + memcpy(mask, info->SubnetMask.Addr, sizeof(*mask)); + gw->s_addr = 0; + nroutes = info->RouteTableSize / sizeof(EFI_IP4_ROUTE_TABLE); + for (i = 0; i < nroutes; i++) { + if (*(uint32_t *)info->RouteTable[i].SubnetAddress.Addr == 0 && + *(uint32_t *)info->RouteTable[i].SubnetMask.Addr == 0) { + memcpy(gw, info->RouteTable[i].GatewayAddress.Addr, + sizeof(*gw)); + break; + } + } + free(info); + + dns->s_addr = 0; + sz = sizeof(dnsaddr); + status = ip4config2->GetData(ip4config2, Ip4Config2DataTypeDnsServer, + &sz, &dnsaddr); + if (!EFI_ERROR(status) && sz >= sizeof(dnsaddr)) + memcpy(dns, &dnsaddr, sizeof(*dns)); + + return (0); +} + static int efihttp_dev_init(void) { @@ -223,6 +368,21 @@ efihttp_dev_strategy(void *devdata __unused, int rw __unused, static int efihttp_dev_open(struct open_file *f, ...) +{ + struct http_devdesc *hd; + + hd = (struct http_devdesc *)f->f_devdata; + if (hd->host == NULL) + return (efihttp_dev_open_legacy(f)); + return (efihttp_dev_open_url(f, hd)); +} + +/* + * EFI HTTP Boot path: handles http_devdesc with host == NULL, usable only + * when the boot image's own device path has a URI node. + */ +static int +efihttp_dev_open_legacy(struct open_file *f) { EFI_HTTP_CONFIG_DATA config; EFI_HTTPv4_ACCESS_POINT config_access; @@ -358,6 +518,133 @@ end: return (err); } +/* + * EFI HTTP URL form: handles http_devdesc with host != NULL, using the + * shared myip/netmask/gateip/nameip globals for network config -- reused + * if already set, otherwise populated via efi_ip4_dhcp(). No network at + * all is an error; nothing here retries. + */ +static int +efihttp_dev_open_url(struct open_file *f, struct http_devdesc *hd) +{ + EFI_HTTP_CONFIG_DATA config; + EFI_HTTPv4_ACCESS_POINT config_access; + EFI_DEVICE_PATH *devpath, *trimmed; + EFI_HANDLE nic, handle; + EFI_SERVICE_BINDING_PROTOCOL *sb; + struct devdesc *dev; + struct open_efihttp *oh; + EFI_STATUS status; + struct in_addr ip, gw, dns; + n_long mask; + int err; + + nic = efi_find_handle(&efinet_dev, hd->dd.d_unit); + if (nic == NULL) + return (ENXIO); + devpath = efi_lookup_devpath(nic); + if (devpath == NULL) + return (ENXIO); + trimmed = devpath; + status = BS->LocateDevicePath(&httpsb_guid, &trimmed, &handle); + if (EFI_ERROR(status)) { + /* + * Whatever chained us here (e.g. iPXE) commonly excludes and + * then exclusively opens the NIC's SNP for its own raw I/O + * -- the same thing our own efinet_probe() does -- which + * disconnects the Mnp/Ip4/.../HttpDxe chain. Nothing + * reconnects it automatically; ask explicitly. + */ + BS->ConnectController(nic, NULL, NULL, TRUE); + trimmed = devpath; + status = BS->LocateDevicePath(&httpsb_guid, &trimmed, &handle); + } + if (EFI_ERROR(status)) + return (efi_status_to_errno(status)); + + if (myip.s_addr != 0) { + err = setup_ipv4_config2_static(handle, myip, netmask, + gateip, nameip); + } else { + err = efi_ip4_dhcp(handle, &ip, &mask, &gw, &dns); + if (err == 0) { + myip = ip; + netmask = mask; + gateip = gw; + nameip = dns; + setenv("boot.netif.ip", inet_ntoa(myip), 1); + setenv("boot.netif.netmask", intoa(netmask), 1); + setenv("boot.netif.gateway", inet_ntoa(gateip), 1); + } + } + if (err != 0) + return (err); + + oh = calloc(1, sizeof(struct open_efihttp)); + if (oh == NULL) + return (ENOMEM); + oh->dev_handle = handle; + dev = (struct devdesc *)f->f_devdata; + dev->d_opendata = oh; + + status = BS->OpenProtocol(handle, &httpsb_guid, (void **)&sb, IH, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(status)) { + err = efi_status_to_errno(status); + goto end; + } + + status = sb->CreateChild(sb, &oh->http_handle); + if (EFI_ERROR(status)) { + err = efi_status_to_errno(status); + goto end; + } + + status = BS->OpenProtocol(oh->http_handle, &http_guid, + (void **)&oh->http, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(status)) { + sb->DestroyChild(sb, oh->http_handle); + err = efi_status_to_errno(status); + goto end; + } + + config.HttpVersion = HttpVersion11; + config.TimeOutMillisec = 0; + config.LocalAddressIsIPv6 = FALSE; + config.AccessPoint.IPv4Node = &config_access; + config_access.UseDefaultAddress = TRUE; + config_access.LocalPort = 0; + status = oh->http->Configure(oh->http, &config); + if (EFI_ERROR(status)) { + sb->DestroyChild(sb, oh->http_handle); + err = efi_status_to_errno(status); + goto end; + } + + /* + * No trailing slash: every path here is already absolute, and a + * double slash makes EDK2's HttpDxe reject the request outright + * (EFI_HTTP_ERROR, not a clean 404). + */ + if (hd->port != 0) + asprintf(&oh->uri_base, "http://%s:%d", hd->host, hd->port); + else + asprintf(&oh->uri_base, "http://%s", hd->host); + if (oh->uri_base == NULL) { + sb->DestroyChild(sb, oh->http_handle); + err = ENOMEM; + goto end; + } + + err = 0; +end: + if (err != 0) { + free(dev->d_opendata); + dev->d_opendata = NULL; + } + return (err); +} + static int efihttp_dev_close(struct open_file *f) { @@ -379,6 +666,56 @@ efihttp_dev_close(struct open_file *f) return (0); } +/* + * Recognizes both "httpN:[/path]" (legacy EFI HTTP Boot) and + * "httpN://host[:port][/path]" (see efihttp_dev_open_url()). Falls back + * to default_parsedev() for the legacy form so existing "http0:" + * currdev strings keep parsing the same way. + */ +static int +efihttp_parsedev(struct devdesc **idev, const char *devspec, + const char **path) +{ + struct http_devdesc *dev; + struct devdesc *ldev; + const char *np, *p; + char *host; + int unit, port, err; + + np = devspec + strlen(efihttp_dev.dv_name); + err = parse_uri(np, &unit, &host, &port, &p); + if (err == EINVAL) { + err = default_parsedev(&ldev, np, path); + if (err != 0) + return (err); + dev = malloc(sizeof(*dev)); + if (dev == NULL) { + free(ldev); + return (ENOMEM); + } + dev->dd = *ldev; + free(ldev); + dev->host = NULL; + dev->port = 0; + } else if (err != 0) { + return (err); + } else { + dev = malloc(sizeof(*dev)); + if (dev == NULL) { + free(host); + return (ENOMEM); + } + dev->dd.d_unit = unit; + dev->host = host; + dev->port = port; + if (path != NULL) + *path = p; + } + dev->dd.d_dev = &efihttp_dev; + *idev = &dev->dd; + return (0); +} + static int _efihttp_fs_open(const char *path, struct open_file *f) { @@ -562,8 +899,11 @@ efihttp_fs_open(const char *path, struct open_file *f) char *path_slash; int err; - if (!efihttp_init_done) - return (ENXIO); + /* + * efihttp_init_done is irrelevant here: efihttp_dev_open_legacy() + * already gates on it before a legacy-form open can reach this + * point, and the URL form never touches it at all. + */ if (f->f_dev != &efihttp_dev) return (EINVAL); /*