-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add Socket::IPaddress.v4
, .v6
, .v4_mapped_v6
#13422
Changes from all commits
e03ab03
c6895b1
f0474ef
df4249e
a726b16
786b6d9
b24ae5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,7 +82,7 @@ class Socket | |
@addr : LibC::In6Addr | LibC::InAddr | ||
|
||
def initialize(@address : String, @port : Int32) | ||
raise Error.new("Invalid port number: #{port}") unless 0 <= port <= UInt16::MAX | ||
raise Error.new("Invalid port number: #{port}") unless IPAddress.valid_port?(port) | ||
|
||
if addr = IPAddress.address_v6?(address) | ||
@addr = addr | ||
|
@@ -144,16 +144,111 @@ class Socket | |
parse URI.parse(uri) | ||
end | ||
|
||
# Returns the IPv4 address with the given address *fields* and *port* | ||
# number. | ||
def self.v4(fields : UInt8[4], port : UInt16) : self | ||
addr_value = UInt32.zero | ||
fields.each_with_index do |field, i| | ||
addr_value = (addr_value << 8) | field | ||
end | ||
|
||
addr = LibC::SockaddrIn.new( | ||
sin_family: LibC::AF_INET, | ||
sin_port: endian_swap(port), | ||
sin_addr: LibC::InAddr.new(s_addr: endian_swap(addr_value)), | ||
) | ||
new(pointerof(addr), sizeof(typeof(addr))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @HertzDevil I just noticed this now during the review of #13463. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There isn't a tail call here (I believe you have to opt in explicitly in the LLVM IR), so In any case I plan to make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, if LLVM keeps this safe, then it's fine. |
||
end | ||
|
||
# Returns the IPv4 address `x0.x1.x2.x3:port`. | ||
# | ||
# Raises `Socket::Error` if any field or the port number is out of range. | ||
def self.v4(x0 : Int, x1 : Int, x2 : Int, x3 : Int, *, port : Int) : self | ||
fields = StaticArray[x0, x1, x2, x3].map { |field| to_v4_field(field) } | ||
port = valid_port?(port) ? port.to_u16! : raise Error.new("Invalid port number: #{port}") | ||
v4(fields, port) | ||
end | ||
|
||
private def self.to_v4_field(field) | ||
0 <= field <= 0xff ? field.to_u8! : raise Error.new("Invalid IPv4 field: #{field}") | ||
end | ||
|
||
# Returns the IPv6 address with the given address *fields* and *port* | ||
# number. | ||
def self.v6(fields : UInt16[8], port : UInt16) : self | ||
fields.map! { |field| endian_swap(field) } | ||
addr = LibC::SockaddrIn6.new( | ||
sin6_family: LibC::AF_INET6, | ||
sin6_port: endian_swap(port), | ||
sin6_addr: ipv6_from_addr16(fields), | ||
) | ||
new(pointerof(addr), sizeof(typeof(addr))) | ||
end | ||
|
||
# Returns the IPv6 address `[x0:x1:x2:x3:x4:x5:x6:x7]:port`. | ||
# | ||
# Raises `Socket::Error` if any field or the port number is out of range. | ||
def self.v6(x0 : Int, x1 : Int, x2 : Int, x3 : Int, x4 : Int, x5 : Int, x6 : Int, x7 : Int, *, port : Int) : self | ||
fields = StaticArray[x0, x1, x2, x3, x4, x5, x6, x7].map { |field| to_v6_field(field) } | ||
port = valid_port?(port) ? port.to_u16! : raise Error.new("Invalid port number: #{port}") | ||
v6(fields, port) | ||
end | ||
|
||
private def self.to_v6_field(field) | ||
0 <= field <= 0xffff ? field.to_u16! : raise Error.new("Invalid IPv6 field: #{field}") | ||
end | ||
|
||
# Returns the IPv4-mapped IPv6 address with the given IPv4 address *fields* | ||
# and *port* number. | ||
def self.v4_mapped_v6(fields : UInt8[4], port : UInt16) : self | ||
v6_fields = StaticArray[ | ||
0_u16, 0_u16, 0_u16, 0_u16, 0_u16, 0xffff_u16, | ||
fields[0].to_u16! << 8 | fields[1], | ||
fields[2].to_u16! << 8 | fields[3], | ||
] | ||
v6(v6_fields, port) | ||
end | ||
|
||
# Returns the IPv4-mapped IPv6 address `[::ffff:x0.x1.x2.x3]:port`. | ||
# | ||
# Raises `Socket::Error` if any field or the port number is out of range. | ||
def self.v4_mapped_v6(x0 : Int, x1 : Int, x2 : Int, x3 : Int, *, port : Int) : self | ||
v4_fields = StaticArray[x0, x1, x2, x3].map { |field| to_v4_field(field) } | ||
port = valid_port?(port) ? port.to_u16! : raise Error.new("Invalid port number: #{port}") | ||
v4_mapped_v6(v4_fields, port) | ||
end | ||
|
||
private def self.ipv6_from_addr16(bytes : UInt16[8]) | ||
addr = LibC::In6Addr.new | ||
{% if flag?(:darwin) || flag?(:bsd) %} | ||
addr.__u6_addr.__u6_addr16 = bytes | ||
{% elsif flag?(:linux) && flag?(:musl) %} | ||
addr.__in6_union.__s6_addr16 = bytes | ||
{% elsif flag?(:wasm32) %} | ||
bytes.each_with_index do |byte, i| | ||
addr.s6_addr[2 * i] = byte.to_u8! | ||
addr.s6_addr[2 * i + 1] = (byte >> 8).to_u8! | ||
end | ||
{% elsif flag?(:linux) %} | ||
addr.__in6_u.__u6_addr16 = bytes | ||
{% elsif flag?(:win32) %} | ||
addr.u.word = bytes | ||
{% else %} | ||
{% raise "Unsupported platform" %} | ||
{% end %} | ||
addr | ||
end | ||
|
||
protected def initialize(sockaddr : LibC::SockaddrIn6*, @size) | ||
@family = Family::INET6 | ||
@addr = sockaddr.value.sin6_addr | ||
@port = endian_swap(sockaddr.value.sin6_port).to_i | ||
@port = IPAddress.endian_swap(sockaddr.value.sin6_port).to_i | ||
end | ||
|
||
protected def initialize(sockaddr : LibC::SockaddrIn*, @size) | ||
@family = Family::INET | ||
@addr = sockaddr.value.sin_addr | ||
@port = endian_swap(sockaddr.value.sin_port).to_i | ||
@port = IPAddress.endian_swap(sockaddr.value.sin_port).to_i | ||
end | ||
|
||
# Returns `true` if *address* is a valid IPv4 or IPv6 address. | ||
|
@@ -313,20 +408,20 @@ class Socket | |
private def to_sockaddr_in6(addr) | ||
sockaddr = Pointer(LibC::SockaddrIn6).malloc | ||
sockaddr.value.sin6_family = family | ||
sockaddr.value.sin6_port = endian_swap(port.to_u16!) | ||
sockaddr.value.sin6_port = IPAddress.endian_swap(port.to_u16!) | ||
sockaddr.value.sin6_addr = addr | ||
sockaddr.as(LibC::Sockaddr*) | ||
end | ||
|
||
private def to_sockaddr_in(addr) | ||
sockaddr = Pointer(LibC::SockaddrIn).malloc | ||
sockaddr.value.sin_family = family | ||
sockaddr.value.sin_port = endian_swap(port.to_u16!) | ||
sockaddr.value.sin_port = IPAddress.endian_swap(port.to_u16!) | ||
sockaddr.value.sin_addr = addr | ||
sockaddr.as(LibC::Sockaddr*) | ||
end | ||
|
||
private def endian_swap(x : UInt16) : UInt16 | ||
protected def self.endian_swap(x : Int::Primitive) : Int::Primitive | ||
{% if IO::ByteFormat::NetworkEndian != IO::ByteFormat::SystemEndian %} | ||
x.byte_swap | ||
{% else %} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor detail, but this expectation is a bit confusing.
How about being more explicit about which result is expected?
I'm happy either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like the previous
Socket.ip?
spec, I don't have proof that Windows actually returns the non-canonical IP address on all OS versions, so it is better to err on the side of caution