From 8aaee2fa0c9089640768bc749335be58396c5725 Mon Sep 17 00:00:00 2001 From: Wleeaf <179125857+wleeaf@users.noreply.github.com> Date: Sat, 24 Oct 2026 23:12:28 +0000 Subject: [PATCH] Allow -, . and ~ in IPv6 zone IDs IPAddr.new and IPAddr#zone_id= only accepted \w characters after %, so zone IDs for interfaces such as eth0.100 (VLAN) or br-lan raised InvalidAddressError. RFC 6874 defines ZoneID as unreserved characters (ALPHA, DIGIT, -, ., _, ~), which is also what Resolv::IPv6 accepts (%[-0-9A-Za-z._~]+). Use the same character class in both places. --- lib/ipaddr.rb | 4 ++-- test/test_ipaddr.rb | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 898af26..1d6d595 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -617,7 +617,7 @@ def zone_id def zone_id=(zid) if @family == Socket::AF_INET6 case zid - when nil, /\A%(\w+)\z/ + when nil, /\A%[-0-9A-Za-z._~]+\z/ @zone_id = zid else raise InvalidAddressError, "invalid zone identifier for address" @@ -758,7 +758,7 @@ def initialize(addr = '::', family = Socket::AF_UNSPEC) family = Socket::AF_INET6 bracketed = true end - if prefix =~ /\A(.*)(%\w+)\z/ + if prefix =~ /\A(.*)(%[-0-9A-Za-z._~]+)\z/ prefix = $1 zone_id = $2 family = Socket::AF_INET6 diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index daa68e6..5727239 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -302,6 +302,9 @@ def test_to_s_with_zone_id assert_equal("::192.168.1.2%eth0", IPAddr.new("::192.168.1.2%eth0").to_s) assert_equal("fe80::1%0ab", IPAddr.new("fe80::1%0ab").to_s) assert_equal("fe80::1%0012", IPAddr.new("fe80::1%0012").to_s) + assert_equal("fe80::1%eth0.100", IPAddr.new("fe80::1%eth0.100").to_s) + assert_equal("fe80::1%br-lan", IPAddr.new("fe80::1%br-lan").to_s) + assert_equal("fe80::%a~b/64", IPAddr.new("fe80::1%a~b/64").cidr) end def test_to_bits @@ -389,6 +392,12 @@ def test_zone_id assert_equal('%ab0', a.zone_id) assert_equal("1:2:3:4:5:6:7:8%ab0", a.to_s) assert_raise(IPAddr::InvalidAddressError) { a.zone_id = '%' } + a.zone_id = '%eth0.100' + assert_equal("1:2:3:4:5:6:7:8%eth0.100", a.to_s) + a.zone_id = '%br-lan' + assert_equal('%br-lan', a.zone_id) + assert_raise(IPAddr::InvalidAddressError) { a.zone_id = '%a b' } + assert_raise(IPAddr::InvalidAddressError) { a.zone_id = '%a/b' } end def test_to_range