Skip to content

Commit

Permalink
Fix Crystal 1.12.1 Deprecated Warning & Improve TCPSocket.new.
Browse files Browse the repository at this point in the history
  • Loading branch information
636f7374 committed May 28, 2024
1 parent a37cf22 commit 87a75b0
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 51 deletions.
2 changes: 1 addition & 1 deletion license
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The Clear BSD License

Copyright (c) 2019 - 2022 636f7374
Copyright (c) 2019 - 2024 636f7374
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
6 changes: 3 additions & 3 deletions serialized/timeout.cr
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ module DNS::Serialized
def unwrap : DNS::TimeOut
timeout = DNS::TimeOut.new

timeout.read = read
timeout.write = write
timeout.connect = connect
timeout.read = read.seconds
timeout.write = write.seconds
timeout.connect = connect.seconds

timeout
end
Expand Down
2 changes: 1 addition & 1 deletion shard.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: dns
version: 1.0.4
version: 1.0.5

authors:
- 636f7374
Expand Down
113 changes: 74 additions & 39 deletions src/dns/extra/socket/tcp_socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -12,66 +12,101 @@ class TCPSocket < IPSocket

def self.new(host : String, port : Int32, dns_resolver : DNS::Resolver, connect_timeout : Int | Time::Span | Nil = nil, caller : Symbol? = nil, answer_safety_first : Bool? = nil, addrinfo_overridable : Bool? = nil) : TCPSocket
delegator, fetch_type, ip_addresses = dns_resolver.getaddrinfo host: host, port: port, caller: caller, answer_safety_first: answer_safety_first, addrinfo_overridable: addrinfo_overridable
raise Exception.new String.build { |io| io << "TCPSocket.new: Unfortunately, DNS::Resolver.getaddrinfo! The host: (" << host << ") & fetchType: (" << fetch_type << ")" << " IPAddress result is empty!" } if ip_addresses.empty?
raise Exception.new String.build { |io| io << "TCPSocket.new: DNS::Resolver.getaddrinfo! The host: (" << host << ") & fetchType: (" << fetch_type << ")" << " IPAddress result is empty!" } if ip_addresses.empty?

connect_timeout_time_span = case _connect_timeout = connect_timeout
in Time::Span
_connect_timeout
in Int
_connect_timeout.seconds
in Nil
10_i32.seconds
end
_connect_timeout = case __connect_timeout = connect_timeout
in Time::Span
__connect_timeout
in Int
__connect_timeout.seconds
in Nil
10_i32.seconds
end

ipv4_connection_failure_counter = Atomic(Int32).new 0_i32
ipv6_connection_failure_counter = Atomic(Int32).new 0_i32
ipv4_failure_counter = Atomic(UInt8).new value: 0_u8
ipv6_failure_counter = Atomic(UInt8).new value: 0_u8

ip_addresses.each_with_index do |ip_address, index|
ip_address = Socket::IPAddress.new address: ip_address.address, port: port if ip_address.port.zero?

case ip_address.family
when .inet?
next if ipv4_connection_failure_counter.get == dns_resolver.maximum_ipv4_attempts caller: caller, delegator: delegator
when .inet6?
next if ipv6_connection_failure_counter.get == dns_resolver.maximum_ipv6_attempts caller: caller, delegator: delegator
failure_counter_callback = ->(family : Socket::Family, method : Symbol) do
case method
when :get
family.inet? ? ipv4_failure_counter.get : ipv6_failure_counter.get
when :add
family.inet? ? ipv4_failure_counter.add(value: 1_u8) : ipv6_failure_counter.add(value: 1_u8)
else
UInt8::MAX
end
end

loop do
begin
socket = attempt_create_socket! dns_resolver: dns_resolver, caller: caller, delegator: delegator, fetch_type: fetch_type, ip_address: ip_address, connect_timeout: connect_timeout_time_span
rescue ex
dns_resolver.__create_socket_exception_call ip_address: ip_address, exception: ex
socket = attempt_create_socket dns_resolver: dns_resolver, caller: caller, delegator: delegator, fetch_type: fetch_type, ip_addresses: ip_addresses, port: port, connect_timeout: _connect_timeout, failure_counter: failure_counter_callback
rescue exception
end

case ip_address.family
# E.g. (Some Domains have only one IP address.)
# It is possible that the connection failed due to connect_timeout.
# E.g. (one IP address and 5 seconds connect_timeout) or (Four IP addresses and 10 seconds connect_timeout).
# Maybe support customization via Options in the future (to be implemented).

if exception && (1_i32 == ip_addresses.size)
first_family = (ip_addresses.first?.try &.family || Socket::Family::INET)

case first_family
when .inet?
ipv4_connection_failure_counter.add value: 1_i32
maximum_ipv4_attempts = dns_resolver.maximum_ipv4_attempts caller: caller, delegator: delegator

unless maximum_ipv4_attempts.zero?
next if ipv4_failure_counter.get < maximum_ipv4_attempts
end
when .inet6?
ipv6_connection_failure_counter.add value: 1_i32
maximum_ipv6_attempts = dns_resolver.maximum_ipv6_attempts caller: caller, delegator: delegator

unless maximum_ipv6_attempts
next if ipv6_failure_counter.get < maximum_ipv6_attempts
end
end
end

raise ex if index.zero? && (1_i32 == ip_addresses.size)
next unless index == ip_addresses.size
return socket if socket
_exception = exception = Exception.new(message: String.build { |io| io << "TCPSocket.new: Tries to connect address: (" << host << ':' << port << ") & fetchType: (" << fetch_type << ") & ipCounts: (" << ip_addresses.size << "), But still failed to connect!" })
exception = _exception if exception.try &.message == "TCPSocket.attempt_create_socket: connect failed!"
raise exception || _exception
end
end

private def self.attempt_create_socket(dns_resolver : DNS::Resolver, caller : Symbol?, delegator : Symbol, fetch_type : DNS::FetchType, ip_addresses : Array(Socket::IPAddress), port : Int32, connect_timeout : Time::Span, failure_counter : Proc(Socket::Family, Symbol, UInt8)) : TCPSocket
exception = nil

ip_addresses.each_with_index do |ip_address, index|
case ip_address.family
when .inet?
next if failure_counter.call(Socket::Family::INET, :get) == dns_resolver.maximum_ipv4_attempts(caller: caller, delegator: delegator)
when .inet6?
next if failure_counter.call(Socket::Family::INET6, :get) == dns_resolver.maximum_ipv6_attempts(caller: caller, delegator: delegator)
end

ip_address = Socket::IPAddress.new address: ip_address.address, port: port if ip_address.port.zero?

raise ex
begin
socket = new ip_address: ip_address, connect_timeout: connect_timeout
return socket unless socket.closed?
rescue exception
dns_resolver.__create_socket_exception_call ip_address: ip_address, exception: exception
end

if socket.closed?
if socket.try &.closed? || exception
case ip_address.family
when .inet?
ipv4_connection_failure_counter.add value: 1_i32
failure_counter.call(Socket::Family::INET, :add)
when .inet6?
ipv6_connection_failure_counter.add value: 1_i32
failure_counter.call(Socket::Family::INET6, :add)
end

next
end

return socket
raise exception if (1_i32 == ip_addresses.size) && exception
next unless index == ip_addresses.size
end

raise Exception.new String.build { |io| io << "TCPSocket.new: Tries to connect the DNS::Resolver.getaddrinfo! address: (" << host << ':' << port << ") & fetchType: (" << fetch_type << ") & count: (" << ip_addresses.size << ") IP addresses, But still failed to connect!" }
end

private def self.attempt_create_socket!(dns_resolver : DNS::Resolver, caller : Symbol?, delegator : Symbol, fetch_type : DNS::FetchType, ip_address : Socket::IPAddress, connect_timeout : Time::Span) : TCPSocket
new ip_address: ip_address, connect_timeout: connect_timeout
raise exception || Exception.new(message: "TCPSocket.attempt_create_socket: connect failed!")
end
end
11 changes: 4 additions & 7 deletions src/dns/timeout.cr
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
struct DNS::TimeOut
property read : Int32
property write : Int32
property connect : Int32
property read : Time::Span
property write : Time::Span
property connect : Time::Span

def initialize
@read = 2_i32
@write = 2_i32
@connect = 2_i32
def initialize(@read : Time::Span = 30_i32.seconds, @write : Time::Span = 30_i32.seconds, @connect : Time::Span = 10_i32.seconds)
end
end

0 comments on commit 87a75b0

Please sign in to comment.