From 71aa8fa9c3922ec3fbd927dbb5ebe8b5403f3092 Mon Sep 17 00:00:00 2001 From: Allen Fair Date: Sat, 23 Nov 2019 10:30:21 -0500 Subject: [PATCH 1/2] Adds network connectivity recovery logic We can't tell (in Ruby?) that the network is down, so if a dns lookup returned a SocketError, it can mean either not found or network unavailable. Until I find a better way, I test the network on that exception by looking up "example.com". If it works, then assume it was a not-found condition. For unavailable, a dummy record is returned. Also, moved the a-record lookup into the same exchanger object, with the same network test. Exchanger objects are cached. When I pull it out of the cache, I check to see if the network-down occurred, and if so, throw it away and create a new one so it can work properly when the network becomes available. --- lib/email_address/exchanger.rb | 53 ++++++++++++++++++++++++++++------ lib/email_address/host.rb | 9 +++--- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/lib/email_address/exchanger.rb b/lib/email_address/exchanger.rb index 613500f..7e7aa8f 100644 --- a/lib/email_address/exchanger.rb +++ b/lib/email_address/exchanger.rb @@ -12,19 +12,22 @@ def self.cached(host, config={}) @host_cache ||= {} @cache_size ||= ENV['EMAIL_ADDRESS_CACHE_SIZE'].to_i || 100 if @host_cache.has_key?(host) - o = @host_cache.delete(host) - @host_cache[host] = o # LRU cache, move to end - elsif @host_cache.size >= @cache_size - @host_cache.delete(@host_cache.keys.first) - @host_cache[host] = new(host, config) + exchanger = @host_cache.delete(host) + exchanger = new(host, config) if exchanger.network_was_down? + @host_cache[host] = exchanger # LRU cache, move to end else - @host_cache[host] = new(host, config) + if @host_cache.size >= @cache_size + @host_cache.delete(@host_cache.keys.first) + end + exchanger = @host_cache[host] = new(host, config) end + exchanger end def initialize(host, config={}) @host = host @config = config + @network_down_at = nil end def each(&block) @@ -44,13 +47,40 @@ def provider @provider = :default end + # Returns: [official_hostname, alias_hostnames, address_family, *address_list] + def a_record + @_a_record = [@host, [], 2, ""] if @config[:dns_lookup] == :off + @_a_record ||= Socket.gethostbyname(@host) + rescue SocketError # not found, but could also mean network not work + if network_down? + @_a_record = [@host, [], 2, ""] + else + @_a_record ||= [] + end + end + + def network_down? + return false if @config[:dns_lookup] == :off + + Socket.gethostbyname('example.com') # Should always exist + @network_down_at = nil + false + rescue SocketError # DNS Failed, so network is down + @network_down_at = Time.new + true + end + + def network_was_down? + @network_down_at ? true : false + end + # Returns: [["mta7.am0.yahoodns.net", "66.94.237.139", 1], ["mta5.am0.yahoodns.net", "67.195.168.230", 1], ["mta6.am0.yahoodns.net", "98.139.54.60", 1]] # If not found, returns [] # Returns a dummy record when dns_lookup is turned off since it may exists, though # may not find provider by MX name or IP. I'm not sure about the "0.0.0.0" ip, it should # be good in this context, but in "listen" context it means "all bound IP's" def mxers - return [["example.com", "0.0.0.0", 1]] if @config[:dns_lookup] == :off + return [[@host, "0.0.0.0", 1]] if @config[:dns_lookup] == :off @mxers ||= Resolv::DNS.open do |dns| dns.timeouts = @config[:dns_timeout] if @config[:dns_timeout] @@ -59,6 +89,7 @@ def mxers rescue Resolv::ResolvTimeout [] end + return [[@host, "0.0.0.0", 1]] if ress.empty? && network_down? records = ress.map do |r| begin @@ -67,8 +98,12 @@ def mxers else nil end - rescue SocketError # not found, but could also mean network not work or it could mean one record doesn't resolve an address - nil + rescue SocketError + if network_down? + [@host, "0.0.0.0", 1] + else # Not Found + nil + end end end records.compact diff --git a/lib/email_address/host.rb b/lib/email_address/host.rb index 1c7f52e..1fe08ad 100644 --- a/lib/email_address/host.rb +++ b/lib/email_address/host.rb @@ -332,12 +332,11 @@ def dns_enabled? [:mx, :a].include?(EmailAddress::Config.setting(:host_validation)) end - # Returns: [official_hostname, alias_hostnames, address_family, *address_list] + # Returns: [official_hostname, [alias'], address_family, *address_list] def dns_a_record - @_dns_a_record = "0.0.0.0" if @config[:dns_lookup] == :off - @_dns_a_record ||= Socket.gethostbyname(self.dns_name) - rescue SocketError # not found, but could also mean network not work - @_dns_a_record ||= [] + return [self.dns_name, [], 2, ""] if @config[:dns_lookup] == :off + + @_dns_a_record = exchangers.a_record end # Returns an array of EmailAddress::Exchanger hosts configured in DNS. From e96457c854d75aa6403633ed8784310f91cb28a8 Mon Sep 17 00:00:00 2001 From: Allen Fair Date: Sat, 23 Nov 2019 15:17:57 -0500 Subject: [PATCH 2/2] Adds option to bypass the network offline check --- lib/email_address/exchanger.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/email_address/exchanger.rb b/lib/email_address/exchanger.rb index 7e7aa8f..660fdb3 100644 --- a/lib/email_address/exchanger.rb +++ b/lib/email_address/exchanger.rb @@ -61,6 +61,7 @@ def a_record def network_down? return false if @config[:dns_lookup] == :off + return false if @config[:dns_unavailable] == :ignore Socket.gethostbyname('example.com') # Should always exist @network_down_at = nil