Skip to content

Add support for IPv6 scoped addresses (RFC4007) #15263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5b253ee
implement IPv6 scoped addresses (RFC4007)
foxxx0 Oct 14, 2024
78a1101
remove totally unnecessary artifact
foxxx0 Apr 14, 2025
06d9a06
adjust RFC4007 implementation based on review feedback
foxxx0 Apr 14, 2025
280e074
remove trailing dot from exception messages
foxxx0 Apr 15, 2025
cd27591
add specs for invalid v4 addrs with zone identifier
foxxx0 Apr 15, 2025
e39b72e
clarify RFC4007 comment and fix #inspect example
foxxx0 Apr 15, 2025
8f84cd3
wrap references to methods in backticks
foxxx0 Apr 15, 2025
867b13e
optimize rfc4007 parsing approach, unify exceptions
foxxx0 Apr 15, 2025
fe90d21
rework rfc4007 parsing to subslice
foxxx0 Apr 15, 2025
67a91a7
revert public parse_v6_fields? return type
foxxx0 Apr 16, 2025
85a5d12
streamline rfc4007 zone subslice building
foxxx0 Apr 16, 2025
ff85550
use hexadecimal representation for expected values
foxxx0 May 16, 2025
070257e
remove unneeded type declaration for IF_NAMESIZE constant
foxxx0 May 16, 2025
83244d8
remove erroneous WASI c/net/if dependency
foxxx0 May 16, 2025
6ab3d90
polish `Socket::Address.#link_local_interface` comment
foxxx0 May 16, 2025
addd235
handle `LibC.if_indextoname` errno
foxxx0 May 16, 2025
a396d5a
raise NotImplementedError on wasi for `#link_local_interface`
foxxx0 May 18, 2025
12a944d
fix errno handling for `Socket::Address.#link_local_interface`
foxxx0 May 18, 2025
28af243
use `LibC.has_method?` to guard against missing implementations
foxxx0 May 19, 2025
a16ed4b
use normal Int32 for `zone_id`
foxxx0 May 19, 2025
72c8b56
restrict zone_id parameter to Int32
foxxx0 May 19, 2025
2227909
handle windows errno appropriately
foxxx0 May 19, 2025
1f45a62
rework `if_indextoname` error handling
foxxx0 May 19, 2025
6d8f516
only look at errno for supported platforms
foxxx0 May 19, 2025
5e379df
adjust specs to match previous commit
foxxx0 May 19, 2025
9109226
only skip errno inspection on `flag?(:win32)`
foxxx0 May 19, 2025
21b5b49
fix formatting
foxxx0 May 19, 2025
22675f9
raise exception from errno on non-windows platforms
foxxx0 May 19, 2025
a6f880f
expect proper darwin/bsd errno message for ENXIO
foxxx0 May 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions spec/std/socket/address_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,87 @@ describe Socket::IPAddress do
Socket::IPAddress.new("::ffff:0:0", 443).address.should eq "::ffff:0.0.0.0"
end

describe "#zone_id" do
# loopback interface "lo" is supposed to *always* be the first interface and
# enumerated with index 1
loopback_iface = {% if flag?(:windows) %}
"loopback_0"
{% elsif flag?(:darwin) || flag?(:bsd) || flag?(:solaris) %}
"lo0"
{% else %}
"lo"
{% end %}

it "parses link-local IPv6 with interface scope" do
address = Socket::IPAddress.new("fe80::3333:4444%3", 8081)
address.address.should eq "fe80::3333:4444"
address.zone_id.should eq 3
address.inspect.should eq "Socket::IPAddress([fe80::3333:4444%3]:8081)"
end

it "looks up loopback interface index by name" do
address = Socket::IPAddress.new("fe80::1111%#{loopback_iface}", 0)
address.address.should eq "fe80::1111"
address.zone_id.should eq 1
end

it "looks up loopback interface name by index" do
# loopback interface "lo" is supposed to *always* be the first interface and
# enumerated with index 1
address = Socket::IPAddress.new("fe80::1111%#{loopback_iface}", 0)
address.link_local_interface.should eq loopback_iface
end

it "fails interface name lookup for non-existent interfaces" do
exc_suff = {% if flag?(:windows) %}
""
{% elsif flag?(:darwin) || flag?(:bsd) %}
": Device not configured"
{% else %}
": No such device or address"
{% end %}
expect_raises(Socket::Error, "Failed to look up interface name for index 333#{exc_suff}") do
Socket::IPAddress.new("fe80::d00d:1%333", 0).link_local_interface
end
end

it "interface name lookup returns nil in unsupported cases" do
Socket::IPAddress.new("fd03::3333", 0).link_local_interface.should be_nil
Socket::IPAddress.new("192.168.10.10", 0).link_local_interface.should be_nil
Socket::IPAddress.new("169.254.0.3", 0).link_local_interface.should be_nil
Socket::IPAddress.new("fe80::4545", 0).link_local_interface.should be_nil
end

it "fails link-local zone identifier on non-LL v6 addrs" do
expect_raises(Socket::Error, "Zoned/scoped IPv6 addresses are only allowed for link-local (supplied 'fd00::abcd%5' is not within fe80::/10)") do
Socket::IPAddress.new("fd00::abcd%5", 443)
end
end

it "fails link-local zone identifier on v4 addrs" do
expect_raises(Socket::Error, "Invalid IP address: 169.254.11.11%eth0") do
Socket::IPAddress.new("169.254.11.11%eth0", 0)
end

expect_raises(Socket::Error, "Invalid IP address: 192.168.11.11%3") do
Socket::IPAddress.new("192.168.11.11%3", 0)
end
end

it "fails on invalid link-local zone identifier" do
expect_raises(Socket::Error, "Invalid IPv6 link-local zone index '0' in address 'fe80::c0ff:ee%0'") do
Socket::IPAddress.new("fe80::c0ff:ee%0", port: 0)
end
end

it "fails on non-existent link-local zone interface" do
# looking up an interface index obviously requires for said interface device to exist
expect_raises(Socket::Error, "IPv6 link-local zone interface 'zzzzzzzzzzzzzzz' not found (in address 'fe80::0f0f:abcd%zzzzzzzzzzzzzzz')") do
Socket::IPAddress.new("fe80::0f0f:abcd%zzzzzzzzzzzzzzz", port: 0)
end
end
end

describe ".parse" do
it "parses IPv4" do
address = Socket::IPAddress.parse "ip://192.168.0.1:8081"
Expand Down Expand Up @@ -222,6 +303,8 @@ describe Socket::IPAddress do
it { Socket::IPAddress.parse_v6_fields?("0::ffff:c0a8:5e4").should eq UInt16.static_array(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x5e4) }
it { Socket::IPAddress.parse_v6_fields?("::0::ffff:c0a8:5e4").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("c0a8").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("fe80::a:b%eth0").should eq UInt16.static_array(0xfe80, 0, 0, 0, 0, 0, 0xa, 0xb) }
it { Socket::IPAddress.parse_v6_fields?("fe80:0:0:0:ffff:c0a8:5e4%lo").should eq UInt16.static_array(0xfe80, 0, 0, 0, 0xffff, 0xc0a8, 0x5e4, 0) }
end

describe ".v4" do
Expand Down Expand Up @@ -263,6 +346,7 @@ describe Socket::IPAddress do
Socket::IPAddress.v6(0xfe80, 0, 0, 0, 0x4860, 0x4860, 0x4860, 0x1234, port: 55001).should eq Socket::IPAddress.new("fe80::4860:4860:4860:1234", 55001)
Socket::IPAddress.v6(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xfffe, port: 65535).should eq Socket::IPAddress.new("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", 65535)
Socket::IPAddress.v6(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x0001, port: 0).should eq Socket::IPAddress.new("::ffff:192.168.0.1", 0)
Socket::IPAddress.v6(0xfe80, 0, 0, 0, 0x5971, 0x5971, 0x5971, 0xabcd, port: 44444, zone_id: 3).should eq Socket::IPAddress.new("fe80::5971:5971:5971:abcd%3", 44444)
end

it "raises on out of bound field" do
Expand Down
9 changes: 9 additions & 0 deletions src/lib_c/aarch64-android/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/aarch64-darwin/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/aarch64-linux-gnu/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/aarch64-linux-musl/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/arm-linux-gnueabihf/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/i386-linux-gnu/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/i386-linux-musl/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/x86_64-darwin/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/x86_64-dragonfly/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/x86_64-freebsd/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/x86_64-linux-gnu/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/x86_64-linux-musl/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/x86_64-netbsd/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/x86_64-openbsd/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
9 changes: 9 additions & 0 deletions src/lib_c/x86_64-solaris/c/net/if.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "../netinet/in"
require "../stdint"

lib LibC
IF_NAMESIZE = 16

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
12 changes: 12 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/netioapi.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
require "./in6addr"
require "./inaddr"
require "./stdint"

@[Link("iphlpapi")]
lib LibC
NDIS_IF_MAX_STRING_SIZE = 256
IF_NAMESIZE = LibC::NDIS_IF_MAX_STRING_SIZE + 1 # need one more byte for terminating '\0'

fun if_nametoindex(ifname : Char*) : UInt
fun if_indextoname(ifindex : UInt, ifname : LibC::Char*) : LibC::Char*
end
Loading
Loading