Skip to content
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

Parse IP addresses in Crystal instead of using LibC.inet_pton #13463

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
132 changes: 71 additions & 61 deletions spec/std/socket/address_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,69 @@ describe Socket::IPAddress do
end
end

# Tests from libc-test:
# https://repo.or.cz/libc-test.git/blob/2113a3ed8217775797dd9a82aa420c10ef1712d5:/src/functional/inet_pton.c
describe ".parse_v4_fields?" do
# dotted-decimal notation
it { Socket::IPAddress.parse_v4_fields?("0.0.0.0").should eq UInt8.static_array(0, 0, 0, 0) }
it { Socket::IPAddress.parse_v4_fields?("127.0.0.1").should eq UInt8.static_array(127, 0, 0, 1) }
it { Socket::IPAddress.parse_v4_fields?("10.0.128.31").should eq UInt8.static_array(10, 0, 128, 31) }
it { Socket::IPAddress.parse_v4_fields?("255.255.255.255").should eq UInt8.static_array(255, 255, 255, 255) }

# numbers-and-dots notation, but not dotted-decimal
it { Socket::IPAddress.parse_v4_fields?("1.2.03.4").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1.2.0x33.4").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1.2.0XAB.4").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1.2.0xabcd").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1.0xabcdef").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("00377.0x0ff.65534").should be_nil }

# invalid
it { Socket::IPAddress.parse_v4_fields?(".1.2.3").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1..2.3").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1.2.3.").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1.2.3.4.5").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1.2.3.a").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1.256.2.3").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1.2.4294967296.3").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1.2.-4294967295.3").should be_nil }
it { Socket::IPAddress.parse_v4_fields?("1.2. 3.4").should be_nil }
end

describe ".parse_v6_fields?" do
it { Socket::IPAddress.parse_v6_fields?(":").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("::").should eq UInt16.static_array(0, 0, 0, 0, 0, 0, 0, 0) }
it { Socket::IPAddress.parse_v6_fields?("::1").should eq UInt16.static_array(0, 0, 0, 0, 0, 0, 0, 1) }
it { Socket::IPAddress.parse_v6_fields?(":::").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("192.168.1.1").should be_nil }
it { Socket::IPAddress.parse_v6_fields?(":192.168.1.1").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("::192.168.1.1").should eq UInt16.static_array(0, 0, 0, 0, 0, 0, 0xc0a8, 0x0101) }
it { Socket::IPAddress.parse_v6_fields?("0:0:0:0:0:0:192.168.1.1").should eq UInt16.static_array(0, 0, 0, 0, 0, 0, 0xc0a8, 0x0101) }
it { Socket::IPAddress.parse_v6_fields?("0:0::0:0:0:192.168.1.1").should eq UInt16.static_array(0, 0, 0, 0, 0, 0, 0xc0a8, 0x0101) }
it { Socket::IPAddress.parse_v6_fields?("::012.34.56.78").should be_nil }
it { Socket::IPAddress.parse_v6_fields?(":ffff:192.168.1.1").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("::ffff:192.168.1.1").should eq UInt16.static_array(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x0101) }
it { Socket::IPAddress.parse_v6_fields?(".192.168.1.1").should be_nil }
it { Socket::IPAddress.parse_v6_fields?(":.192.168.1.1").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("a:0b:00c:000d:E:F::").should eq UInt16.static_array(0xa, 0x0b, 0x00c, 0x000d, 0xE, 0xF, 0, 0) }
it { Socket::IPAddress.parse_v6_fields?("a:0b:00c:000d:0000e:f::").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("1:2:3:4:5:6::").should eq UInt16.static_array(1, 2, 3, 4, 5, 6, 0, 0) }
it { Socket::IPAddress.parse_v6_fields?("1:2:3:4:5:6:7::").should eq UInt16.static_array(1, 2, 3, 4, 5, 6, 7, 0) }
it { Socket::IPAddress.parse_v6_fields?("1:2:3:4:5:6:7:8::").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("1:2:3:4:5:6:7::9").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("::1:2:3:4:5:6").should eq UInt16.static_array(0, 0, 1, 2, 3, 4, 5, 6) }
it { Socket::IPAddress.parse_v6_fields?("::1:2:3:4:5:6:7").should eq UInt16.static_array(0, 1, 2, 3, 4, 5, 6, 7) }
it { Socket::IPAddress.parse_v6_fields?("::1:2:3:4:5:6:7:8").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("a:b::c:d:e:f").should eq UInt16.static_array(0xa, 0xb, 0, 0, 0xc, 0xd, 0xe, 0xf) }
it { Socket::IPAddress.parse_v6_fields?("ffff:c0a8:5e4").should be_nil }
it { Socket::IPAddress.parse_v6_fields?(":ffff:c0a8:5e4").should be_nil }
it { Socket::IPAddress.parse_v6_fields?("0:0:0:0:0:ffff:c0a8:5e4").should eq UInt16.static_array(0, 0, 0, 0, 0, 0xffff, 0xc0a8, 0x5e4) }
it { Socket::IPAddress.parse_v6_fields?("0:0:0:0:ffff:c0a8:5e4").should be_nil }
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 }
end

describe ".v4" do
it "constructs an IPv4 address" do
Socket::IPAddress.v4(0, 0, 0, 0, port: 0).should eq Socket::IPAddress.new("0.0.0.0", 0)
Expand Down Expand Up @@ -397,68 +460,15 @@ end
{% end %}

describe Socket do
# Tests from libc-test:
# http://repo.or.cz/libc-test.git/blob/master:/src/functional/inet_pton.c
# Most of the specs are moved to `.parse_v4_fields?` and `.parse_v6_fields?`,
# which are implemented in pure Crystal; the remaining ones here are test
# cases that were once known to break on certain platforms when `Socket.ip?`
# was still using the system `inet_pton`
it ".ip?" do
# dotted-decimal notation
Socket.ip?("0.0.0.0").should be_true
Socket.ip?("127.0.0.1").should be_true
Socket.ip?("10.0.128.31").should be_true
Socket.ip?("255.255.255.255").should be_true

# numbers-and-dots notation, but not dotted-decimal
# Socket.ip?("1.2.03.4").should be_false # fails on darwin
Socket.ip?("1.2.0x33.4").should be_false
Socket.ip?("1.2.0XAB.4").should be_false
Socket.ip?("1.2.0xabcd").should be_false
Socket.ip?("1.0xabcdef").should be_false
Socket.ip?("00377.0x0ff.65534").should be_false

# invalid
Socket.ip?(".1.2.3").should be_false
Socket.ip?("1..2.3").should be_false
Socket.ip?("1.2.3.").should be_false
Socket.ip?("1.2.3.4.5").should be_false
Socket.ip?("1.2.3.a").should be_false
Socket.ip?("1.256.2.3").should be_false
Socket.ip?("1.2.4294967296.3").should be_false
Socket.ip?("1.2.-4294967295.3").should be_false
Socket.ip?("1.2. 3.4").should be_false

# ipv6
Socket.ip?(":").should be_false
Socket.ip?("::").should be_true
Socket.ip?("::1").should be_true
Socket.ip?(":::").should be_false
Socket.ip?(":192.168.1.1").should be_false
Socket.ip?("::192.168.1.1").should be_true
Socket.ip?("0:0:0:0:0:0:192.168.1.1").should be_true
Socket.ip?("0:0::0:0:0:192.168.1.1").should be_true
# Socket.ip?("::012.34.56.78").should be_false # fails on darwin
Socket.ip?(":ffff:192.168.1.1").should be_false
Socket.ip?("::ffff:192.168.1.1").should be_true
Socket.ip?(".192.168.1.1").should be_false
Socket.ip?(":.192.168.1.1").should be_false
Socket.ip?("a:0b:00c:000d:E:F::").should be_true
# Socket.ip?("a:0b:00c:000d:0000e:f::").should be_false # fails on GNU libc
Socket.ip?("1:2:3:4:5:6::").should be_true
Socket.ip?("1:2:3:4:5:6:7::").should be_true
Socket.ip?("1:2:3:4:5:6:7:8::").should be_false
Socket.ip?("1:2:3:4:5:6:7::9").should be_false
Socket.ip?("::1:2:3:4:5:6").should be_true
# FIXME: On older Windows versions, this returned `false`. It was apparently fixed in Windows Server 2022.
{% unless flag?(:win32) %}
Socket.ip?("::1:2:3:4:5:6:7").should be_true
{% end %}
Socket.ip?("::1:2:3:4:5:6:7:8").should be_false
Socket.ip?("a:b::c:d:e:f").should be_true
Socket.ip?("ffff:c0a8:5e4").should be_false
Socket.ip?(":ffff:c0a8:5e4").should be_false
Socket.ip?("0:0:0:0:0:ffff:c0a8:5e4").should be_true
Socket.ip?("0:0:0:0:ffff:c0a8:5e4").should be_false
Socket.ip?("0::ffff:c0a8:5e4").should be_true
Socket.ip?("::0::ffff:c0a8:5e4").should be_false
Socket.ip?("c0a8").should be_false
Socket.ip?("1.2.03.4").should be_false
Socket.ip?("::012.34.56.78").should be_false
Socket.ip?("a:0b:00c:000d:0000e:f::").should be_false
Socket.ip?("::1:2:3:4:5:6:7").should be_true
end

it "==" do
Expand Down
18 changes: 9 additions & 9 deletions src/openssl/ssl/hostname_validation.cr
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,19 @@ module OpenSSL::SSL::HostnameValidation
pattern = String.new(dns_name, dns_name_len)
return Result::MatchFound if matches_hostname?(pattern, hostname)
when LibCrypto::GEN_IPADD
data = LibCrypto.asn1_string_data(current_name.value)
len = LibCrypto.asn1_string_length(current_name.value)
data = Slice.new(LibCrypto.asn1_string_data(current_name.value), LibCrypto.asn1_string_length(current_name.value))

case len
case data.size
when 4
addr = uninitialized LibC::InAddr
if LibC.inet_pton(LibC::AF_INET, hostname, pointerof(addr).as(Void*)) > 0
return Result::MatchFound if addr == data.as(LibC::InAddr*).value
if v4_fields = ::Socket::IPAddress.parse_v4_fields?(hostname)
return Result::MatchFound if v4_fields.to_slice == data
end
when 16
addr6 = uninitialized LibC::In6Addr
if LibC.inet_pton(LibC::AF_INET6, hostname, pointerof(addr6).as(Void*)) > 0
return Result::MatchFound if addr6.unsafe_as(StaticArray(UInt32, 4)) == data.as(StaticArray(UInt32, 4)*).value
if v6_fields = ::Socket::IPAddress.parse_v6_fields?(hostname)
{% if IO::ByteFormat::NetworkEndian != IO::ByteFormat::SystemEndian %}
v6_fields.map! &.byte_swap
{% end %}
return Result::MatchFound if v6_fields.to_slice.to_unsafe_bytes == data
end
else
# not a length we expect
Expand Down
Loading