Skip to content

Commit 47aa5eb

Browse files
committed
fix: properly handle IPv6 addresses in HTTP Host headers
This commit fixes IPv6 address handling in HTTP client Host headers by adding bracket notation when required and improving URL parsing validation. Changes: - Add automatic bracket wrapping for unbracketed IPv6 addresses in Host headers for both standard and non-standard ports - Add IPv6 bracketing for HTTPS default port (443) to ensure RFC compliance even when port is omitted (e.g., Host: [::1]) - Fix off-by-one error in IPv6 bracket stripping (was removing one extra character) - Fix incorrect length calculation in flb_utils_copy_host_sds for bracketed IPv6 extraction (changed from absolute position to relative length to properly account for pos_init offset) - Strip IPv6 zone IDs (e.g., %eth0) from Host headers per RFC 3986 which prohibits zone IDs in URIs (e.g., fe80::1%eth0 becomes [fe80::1]:8080 in Host header) - Perform zone ID stripping before inet_pton() validation to ensure proper IPv6 address detection for link-local addresses - Add URI path prepending for URLs with query/fragment but no path (e.g., http://example.com?query=1 becomes /?query=1) per RFC 7230 - Constrain IPv6 bracket validation to host portion only, preventing false negatives when brackets appear in URL paths or query strings - Update validate_ipv6_brackets() to recognize '?' and '#' as host delimiters in addition to '/' - Refactor URL parsing logic to eliminate duplication - Use memchr with length limit for consistent and safe bracket detection in both IPv6 and non-IPv6 cases - Improve error handling in URL parsing with proper cleanup on failure - Update TLS flag checking to use flb_stream_get_flag_status() for more reliable detection Tests: - Add test for IPv6 with HTTPS on default port 443 - Add test cases for IPv6 addresses with zone IDs (verifying zone ID stripping behavior) - Add test cases for brackets in URL paths and query strings - Add test cases for malformed bracket scenarios Signed-off-by: Shelby Hagman <shelbyzh@amazon.com>
1 parent 4f8c50b commit 47aa5eb

File tree

5 files changed

+489
-41
lines changed

5 files changed

+489
-41
lines changed

src/flb_http_client.c

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,13 @@
3333
#define _GNU_SOURCE
3434
#include <string.h>
3535

36+
#ifdef FLB_SYSTEM_WINDOWS
37+
#include <winsock2.h>
38+
#include <ws2tcpip.h>
39+
#endif
40+
3641
#include <fluent-bit/flb_info.h>
42+
#include <fluent-bit/flb_compat.h>
3743
#include <fluent-bit/flb_kv.h>
3844
#include <fluent-bit/flb_log.h>
3945
#include <fluent-bit/flb_mem.h>
@@ -617,11 +623,55 @@ static int add_host_and_content_length(struct flb_http_client *c)
617623
out_port = c->port;
618624
}
619625

620-
if (c->flags & FLB_IO_TLS && out_port == 443) {
621-
tmp = flb_sds_copy(host, out_host, strlen(out_host));
626+
/* Check if out_host is an unbracketed IPv6 address */
627+
struct in6_addr addr;
628+
char *zone_id;
629+
char addr_buf[INET6_ADDRSTRLEN];
630+
int is_ipv6 = 0;
631+
int is_https_default_port;
632+
const char *host_for_header;
633+
634+
if (out_host && out_host[0] != '[') {
635+
/* Strip zone ID if present (e.g., fe80::1%eth0 -> fe80::1) */
636+
zone_id = strchr(out_host, '%');
637+
if (zone_id) {
638+
len = zone_id - out_host;
639+
if (len < INET6_ADDRSTRLEN) {
640+
memcpy(addr_buf, out_host, len);
641+
addr_buf[len] = '\0';
642+
is_ipv6 = (inet_pton(AF_INET6, addr_buf, &addr) == 1);
643+
}
644+
}
645+
else {
646+
is_ipv6 = (inet_pton(AF_INET6, out_host, &addr) == 1);
647+
}
648+
}
649+
650+
/* Use stripped address (without zone ID) for Host header if zone ID was present */
651+
host_for_header = (is_ipv6 && zone_id) ? addr_buf : out_host;
652+
653+
/* Check if connection uses TLS and port is 443 (HTTPS default) */
654+
is_https_default_port = flb_stream_get_flag_status(&u->base, FLB_IO_TLS) && out_port == 443;
655+
656+
if (is_https_default_port) {
657+
if (is_ipv6) {
658+
/* IPv6 address needs brackets for RFC compliance */
659+
tmp = flb_sds_printf(&host, "[%s]", host_for_header);
660+
}
661+
else {
662+
/* HTTPS on default port 443 - omit port from Host header */
663+
tmp = flb_sds_copy(host, out_host, strlen(out_host));
664+
}
622665
}
623666
else {
624-
tmp = flb_sds_printf(&host, "%s:%i", out_host, out_port);
667+
if (is_ipv6) {
668+
/* IPv6 address needs brackets when combined with port */
669+
tmp = flb_sds_printf(&host, "[%s]:%i", host_for_header, out_port);
670+
}
671+
else {
672+
/* IPv4 address, domain name, or already bracketed IPv6 */
673+
tmp = flb_sds_printf(&host, "%s:%i", out_host, out_port);
674+
}
625675
}
626676

627677
if (!tmp) {

src/flb_network.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#ifdef FLB_SYSTEM_WINDOWS
3131
#define poll WSAPoll
3232
#include <winsock2.h>
33+
#include <ws2tcpip.h>
3334
#else
3435
#include <sys/poll.h>
3536
#endif

0 commit comments

Comments
 (0)