resolv 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38de4f14ddb183350d35593550caa34f9694644fb9d2fc79bd275d92d17ee338
4
- data.tar.gz: 00d4977e89fb7cd8ec4a772c481d63e49fd4441b90c7ec5be42a79294ab3da19
3
+ metadata.gz: '017129811e9d2a2c49065d8f95e96f73acba713f6e1e2ee1cd65abb448bea1ce'
4
+ data.tar.gz: 9fe320e19d45e74055b648326efc8bc975b9cbc5aad2a4cd47e1b3660fb81f5e
5
5
  SHA512:
6
- metadata.gz: 175ca63aa1f82ebabc499bbcd8eedf17c33ff220c439187a1c5778ca2e74926d15651a720c0bb5f615a698747252d8f9cd82c5db5fb25d28a4d1e63cb696acf1
7
- data.tar.gz: 8789721febc5b7a96a8bcfdc51dd503afefa648a6d7057e6951b77d1fb514c613e35524ef9680e4ef6d4ad49e310a0fa2d1e92a972eae0e8f23969519c7db1d9
6
+ metadata.gz: 9632f2e80d4ce1d581f3271b70913e2acef6af77a0289539d0954b191382234d6d53fa190f79bd27468724e917b4ba68fb27e2c8e247ce89f0cabccec1c956a5
7
+ data.tar.gz: 59623cf40d63ab667ce5a39301e2eff5186d8c98306cec5c41fd720aad5b74bca7fcfce877ea44b54de297809d41c4822d7bdd5603964c4e772544998a6a5ef7
@@ -0,0 +1,3 @@
1
+ # AUTOGENERATED DEPENDENCIES START
2
+ resolv.o: resolv.c
3
+ # AUTOGENERATED DEPENDENCIES END
@@ -6,8 +6,9 @@
6
6
 
7
7
  require 'win32/resolv.so'
8
8
 
9
+ # Generic namespace for Windows platform-specific features.
9
10
  module Win32
10
- module Resolv
11
+ module Resolv # :nodoc:
11
12
  # Error at Win32 API
12
13
  class Error < StandardError
13
14
  # +code+ Win32 Error code
@@ -53,6 +53,7 @@ wchar_to_utf8(const WCHAR *w, int n)
53
53
  return str;
54
54
  }
55
55
 
56
+ /* :nodoc: */
56
57
  static VALUE
57
58
  get_dns_server_list(VALUE self)
58
59
  {
@@ -81,7 +82,6 @@ get_dns_server_list(VALUE self)
81
82
  return nameservers;
82
83
  }
83
84
 
84
-
85
85
  static const WCHAR TCPIP_Params[] = L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters";
86
86
 
87
87
  static void
@@ -116,6 +116,7 @@ reg_open_key(VALUE klass, HKEY hkey, const WCHAR *wname)
116
116
  return rb_ensure(rb_yield, k, hkey_close, k);
117
117
  }
118
118
 
119
+ /* :nodoc: */
119
120
  static VALUE
120
121
  tcpip_params_open(VALUE klass)
121
122
  {
data/lib/resolv.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'socket'
4
4
  require 'timeout'
5
- require 'io/wait'
5
+ require 'io/wait' if RUBY_VERSION < '3.2'
6
6
  require 'securerandom'
7
7
  require 'rbconfig'
8
8
 
@@ -35,7 +35,7 @@ require 'rbconfig'
35
35
  class Resolv
36
36
 
37
37
  # The version string
38
- VERSION = "0.7.2"
38
+ VERSION = "0.8.0"
39
39
 
40
40
  ##
41
41
  # Looks up the first IP address for +name+.
@@ -562,7 +562,19 @@ class Resolv
562
562
  next if !sender
563
563
  senders[[candidate, requester, nameserver, port]] = sender
564
564
  end
565
- reply, reply_name = requester.request(sender, tout)
565
+ begin
566
+ reply, reply_name = requester.request(sender, tout)
567
+ rescue ResolvTimeout
568
+ # Giving up part way through a frame loses stream sync, and a peer
569
+ # seen going away leaves the socket dead. Either way the requester
570
+ # says so, and the next attempt has to open a fresh connection. A
571
+ # timeout with the stream still on a frame boundary keeps it.
572
+ unless requester.reusable?
573
+ requesters.delete([nameserver, port])
574
+ requester.close
575
+ end
576
+ raise
577
+ end
566
578
  case reply.rcode
567
579
  when RCode::NoError
568
580
  if reply.tc == 1 and not Requester::TCP === requester
@@ -698,13 +710,27 @@ class Resolv
698
710
  @socks = nil
699
711
  end
700
712
 
713
+ # Whether another request may be sent over the same transport. Only a
714
+ # stream transport can end up in a state that rules this out.
715
+ def reusable?
716
+ true
717
+ end
718
+
719
+ # A request could not be written. Only a stream transport can be left
720
+ # unusable by that; see #reusable?.
721
+ def send_failed
722
+ end
723
+
701
724
  def request(sender, tout)
702
725
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
703
726
  timelimit = start + tout
704
727
  begin
705
728
  sender.send
706
729
  rescue Errno::EHOSTUNREACH, # multi-homed IPv6 may generate this
707
- Errno::ENETUNREACH
730
+ Errno::ENETUNREACH,
731
+ Errno::EPIPE, # a peer that went away between requests
732
+ Errno::ECONNRESET # the same, as Windows reports it
733
+ send_failed
708
734
  raise ResolvTimeout
709
735
  end
710
736
  while true
@@ -724,7 +750,7 @@ class Resolv
724
750
  raise ResolvTimeout
725
751
  end
726
752
  begin
727
- reply, from = recv_reply(select_result[0])
753
+ reply, from = recv_reply(select_result[0], timelimit)
728
754
  rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
729
755
  Errno::ECONNRESET, # Windows
730
756
  EOFError
@@ -801,7 +827,7 @@ class Resolv
801
827
  self
802
828
  end
803
829
 
804
- def recv_reply(readable_socks)
830
+ def recv_reply(readable_socks, timelimit = nil)
805
831
  lazy_initialize
806
832
  reply, from = readable_socks[0].recvfrom(UDPSize)
807
833
  return reply, [from[3],from[1]]
@@ -860,20 +886,37 @@ class Resolv
860
886
  @mutex.synchronize {
861
887
  next if @initialized
862
888
  @initialized = true
863
- is_ipv6 = @host.index(':')
864
- sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET)
865
- @socks = [sock]
866
- sock.do_not_reverse_lookup = true
867
- DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0")
868
- sock.connect(@host, @port)
889
+ connect_socket
869
890
  }
870
891
  self
871
892
  end
872
893
 
873
- def recv_reply(readable_socks)
894
+ # The socket to talk to the nameserver over, opening one if there is
895
+ # none yet. #recv_reply may have replaced it since a sender was
896
+ # created, so senders ask for it per request instead of holding on to
897
+ # one.
898
+ def sock
899
+ lazy_initialize
900
+ @socks[0]
901
+ end
902
+
903
+ def recv_reply(readable_socks, timelimit = nil)
874
904
  lazy_initialize
875
905
  reply = readable_socks[0].recv(UDPSize)
876
906
  return reply, nil
907
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
908
+ # The kernel reports these from an ICMP message, and a second one for
909
+ # the same pair of endpoints is not always passed on: macOS 26.1 and
910
+ # later deliver every other one. A retry over this socket would then
911
+ # wait out its whole timeout rather than fail at once, so start over
912
+ # from a new source port.
913
+ @mutex.synchronize {
914
+ if @initialized
915
+ @socks&.each(&:close)
916
+ connect_socket
917
+ end
918
+ }
919
+ raise
877
920
  end
878
921
 
879
922
  def sender(msg, data, host=@host, port=@port)
@@ -884,7 +927,7 @@ class Resolv
884
927
  id = DNS.allocate_request_id(@host, @port)
885
928
  request = msg.encode
886
929
  request[0,2] = [id].pack('n')
887
- return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
930
+ return @senders[[nil,id]] = Sender.new(request, data, self)
888
931
  end
889
932
 
890
933
  def close
@@ -899,10 +942,23 @@ class Resolv
899
942
  end
900
943
  end
901
944
 
945
+ private def connect_socket
946
+ is_ipv6 = @host.index(':')
947
+ sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET)
948
+ @socks = [sock]
949
+ sock.do_not_reverse_lookup = true
950
+ DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0")
951
+ sock.connect(@host, @port)
952
+ end
953
+
902
954
  class Sender < Requester::Sender # :nodoc:
955
+ def initialize(msg, data, requester)
956
+ super(msg, data, nil)
957
+ @requester = requester
958
+ end
959
+
903
960
  def send
904
- raise "@sock is nil." if @sock.nil?
905
- @sock.send(@msg, 0)
961
+ @requester.sock.send(@msg, 0)
906
962
  end
907
963
  attr_reader :data
908
964
  end
@@ -933,15 +989,32 @@ class Resolv
933
989
  sock = TCPSocket.new(@host, @port)
934
990
  @socks = [sock]
935
991
  @senders = {}
992
+ @reusable = true
936
993
  end
937
994
 
938
- def recv_reply(readable_socks)
939
- len_data = readable_socks[0].read(2)
995
+ def reusable?
996
+ @reusable
997
+ end
998
+
999
+ def send_failed
1000
+ @reusable = false
1001
+ end
1002
+
1003
+ def recv_reply(readable_socks, timelimit = nil)
1004
+ sock = readable_socks[0]
1005
+ len_data = read_exactly(sock, 2, timelimit)
940
1006
  raise EOFError if len_data.nil? || len_data.bytesize != 2
941
1007
  len = len_data.unpack('n')[0]
942
- reply = @socks[0].read(len)
1008
+ reply = read_exactly(sock, len, timelimit)
943
1009
  raise EOFError if reply.nil? || reply.bytesize != len
1010
+ @reusable = true
944
1011
  return reply, nil
1012
+ rescue EOFError, SystemCallError
1013
+ # Whatever the kernel reported, this socket cannot be trusted for
1014
+ # another frame. In practice that is the peer closing or resetting;
1015
+ # the rest is rare enough that erring towards reconnecting is right.
1016
+ @reusable = false
1017
+ raise
945
1018
  end
946
1019
 
947
1020
  def sender(msg, data, host=@host, port=@port)
@@ -963,11 +1036,40 @@ class Resolv
963
1036
  end
964
1037
 
965
1038
  def close
1039
+ @reusable = false
966
1040
  super
967
1041
  @senders.each_key {|from,id|
968
1042
  DNS.free_request_id(@host, @port, id)
969
1043
  }
970
1044
  end
1045
+
1046
+ private
1047
+
1048
+ # Read +len+ bytes, giving up with ResolvTimeout once +timelimit+ (a
1049
+ # CLOCK_MONOTONIC value) has passed. A shorter result means the peer
1050
+ # closed the connection, which the caller turns into an EOFError.
1051
+ # Consuming any byte marks the requester unusable until the whole frame
1052
+ # has been read, since giving up in between loses frame sync. Without a
1053
+ # +timelimit+ the read blocks instead and keeps no such mark; that is
1054
+ # only for a caller still using the one argument form of #recv_reply.
1055
+ def read_exactly(sock, len, timelimit)
1056
+ return sock.read(len) unless timelimit
1057
+ buf = String.new
1058
+ while buf.bytesize < len
1059
+ case chunk = sock.read_nonblock(len - buf.bytesize, exception: false)
1060
+ when :wait_readable
1061
+ remaining = timelimit - Process.clock_gettime(Process::CLOCK_MONOTONIC)
1062
+ raise ResolvTimeout if remaining <= 0
1063
+ sock.wait_readable(remaining) or raise ResolvTimeout
1064
+ when nil
1065
+ break
1066
+ else
1067
+ @reusable = false
1068
+ buf << chunk
1069
+ end
1070
+ end
1071
+ buf
1072
+ end
971
1073
  end
972
1074
 
973
1075
  ##
@@ -1089,7 +1191,7 @@ class Resolv
1089
1191
  if /\./ =~ hostname
1090
1192
  @search = [Label.split($')]
1091
1193
  else
1092
- @search = [[]]
1194
+ @search = []
1093
1195
  end
1094
1196
  end
1095
1197
 
@@ -1136,23 +1238,18 @@ class Resolv
1136
1238
  end
1137
1239
 
1138
1240
  def generate_candidates(name)
1139
- candidates = nil
1140
1241
  name = Name.create(name)
1141
1242
  if name.absolute?
1142
- candidates = [name]
1243
+ [name]
1143
1244
  else
1245
+ abs = Name.new(name.to_a)
1246
+ search = @search.map {|domain| Name.new(name.to_a + domain)}
1144
1247
  if @ndots <= name.length - 1
1145
- candidates = [Name.new(name.to_a)]
1248
+ [abs, *search].uniq
1146
1249
  else
1147
- candidates = []
1148
- end
1149
- candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
1150
- fname = Name.create("#{name}.")
1151
- if !candidates.include?(fname)
1152
- candidates << fname
1250
+ [*search, abs].uniq
1153
1251
  end
1154
1252
  end
1155
- return candidates
1156
1253
  end
1157
1254
 
1158
1255
  InitialTimeout = 5
@@ -3347,7 +3444,7 @@ class Resolv
3347
3444
 
3348
3445
  # Regular expression LOC size must match.
3349
3446
 
3350
- Regex = /^(\d+\.*\d*)[m]$/
3447
+ Regex = /\A0*(\d{1,8}(?:\.\d+)?)m\z/
3351
3448
 
3352
3449
  ##
3353
3450
  # Creates a new LOC::Size from +arg+ which may be:
@@ -3360,13 +3457,14 @@ class Resolv
3360
3457
  when Size
3361
3458
  return arg
3362
3459
  when String
3363
- scalar = ''
3364
- if Regex =~ arg
3365
- scalar = [(($1.to_f*(1e2)).to_i.to_s[0].to_i*(2**4)+(($1.to_f*(1e2)).to_i.to_s.length-1))].pack("C")
3366
- else
3460
+ unless Regex =~ arg
3367
3461
  raise ArgumentError.new("not a properly formed Size string: " + arg)
3368
3462
  end
3369
- return Size.new(scalar)
3463
+ unless (0.0...1e8) === (scalar = $1.to_f)
3464
+ raise ArgumentError.new("out of range as Size: #{arg}")
3465
+ end
3466
+ str = (scalar * 100).to_i.to_s
3467
+ return new([(str[0].to_i << 4) + (str.bytesize-1)].pack("C"))
3370
3468
  else
3371
3469
  raise ArgumentError.new("cannot interpret as Size: #{arg.inspect}")
3372
3470
  end
@@ -3383,8 +3481,8 @@ class Resolv
3383
3481
  attr_reader :scalar
3384
3482
 
3385
3483
  def to_s # :nodoc:
3386
- s = @scalar.unpack("H2").join.to_s
3387
- return ((s[0].to_i)*(10**(s[1].to_i-2))).to_s << "m"
3484
+ s, = @scalar.unpack("C")
3485
+ return "#{(s >> 4) * (10.0 ** ((s & 0xf) - 2))}m"
3388
3486
  end
3389
3487
 
3390
3488
  def inspect # :nodoc:
@@ -3412,7 +3510,10 @@ class Resolv
3412
3510
 
3413
3511
  # Regular expression LOC Coord must match.
3414
3512
 
3415
- Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/
3513
+ Regex = /\A0*(\d{1,3})\s([0-5]?\d)\s([0-5]?\d(?:\.\d+)?)\s([NESW])\z/
3514
+
3515
+ # Bias for the equator/prime meridian, in thousandths of a second of arc.
3516
+ Bias = 1 << 31
3416
3517
 
3417
3518
  ##
3418
3519
  # Creates a new LOC::Coord from +arg+ which may be:
@@ -3425,17 +3526,19 @@ class Resolv
3425
3526
  when Coord
3426
3527
  return arg
3427
3528
  when String
3428
- coordinates = ''
3429
- if Regex =~ arg && $1.to_f < 180
3430
- m = $~
3431
- hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1
3432
- coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) +
3433
- (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N")
3434
- orientation = m[4][/[NS]/] ? 'lat' : 'lon'
3435
- else
3529
+ unless m = Regex.match(arg)
3436
3530
  raise ArgumentError.new("not a properly formed Coord string: " + arg)
3437
3531
  end
3438
- return Coord.new(coordinates,orientation)
3532
+
3533
+ arc = (m[1].to_i * 3_600_000) + (m[2].to_i * 60_000) + (m[3].to_f * 1_000).to_i
3534
+ dir = m[4]
3535
+ lat = dir[/[NS]/]
3536
+ unless arc <= (lat ? 324_000_000 : 648_000_000) # (lat ? 90 : 180) * 3_600_000
3537
+ raise ArgumentError.new("out of range as Coord: #{arg}")
3538
+ end
3539
+
3540
+ hemi = dir[/[NE]/] ? 1 : -1
3541
+ return new([arc * hemi + Bias].pack("N"), lat ? "lat" : "lon")
3439
3542
  else
3440
3543
  raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}")
3441
3544
  end
@@ -3443,10 +3546,10 @@ class Resolv
3443
3546
 
3444
3547
  # Internal use; use self.create.
3445
3548
  def initialize(coordinates,orientation)
3446
- unless coordinates.kind_of?(String)
3549
+ unless coordinates.kind_of?(String) and coordinates.bytesize == 4
3447
3550
  raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}")
3448
3551
  end
3449
- unless orientation.kind_of?(String) && orientation[/^lon$|^lat$/]
3552
+ unless orientation == "lon" || orientation == "lat"
3450
3553
  raise ArgumentError.new('Coord expects orientation to be a String argument of "lat" or "lon"')
3451
3554
  end
3452
3555
  @coordinates = coordinates
@@ -3463,22 +3566,17 @@ class Resolv
3463
3566
  attr_reader :orientation
3464
3567
 
3465
3568
  def to_s # :nodoc:
3466
- c = @coordinates.unpack("N").join.to_i
3467
- val = (c - (2**31)).abs
3468
- fracsecs = (val % 1e3).to_i.to_s
3469
- val = val / 1e3
3470
- secs = (val % 60).to_i.to_s
3471
- val = val / 60
3472
- mins = (val % 60).to_i.to_s
3473
- degs = (val / 60).to_i.to_s
3474
- posi = (c >= 2**31)
3475
- case posi
3476
- when true
3477
- hemi = @orientation[/^lat$/] ? "N" : "E"
3569
+ c, = @coordinates.unpack("N")
3570
+ val = (c -= Bias).abs
3571
+ val, fracsecs = val.divmod(1000)
3572
+ val, secs = val.divmod(60)
3573
+ degs, mins = val.divmod(60)
3574
+ hemi = if c.negative?
3575
+ @orientation == "lon" ? "W" : "S"
3478
3576
  else
3479
- hemi = @orientation[/^lon$/] ? "W" : "S"
3577
+ @orientation == "lat" ? "N" : "E"
3480
3578
  end
3481
- return degs << " " << mins << " " << secs << "." << fracsecs << " " << hemi
3579
+ format("%d %02d %02d.%03d %s", degs, mins, secs, fracsecs, hemi)
3482
3580
  end
3483
3581
 
3484
3582
  def inspect # :nodoc:
@@ -3506,7 +3604,10 @@ class Resolv
3506
3604
 
3507
3605
  # Regular expression LOC Alt must match.
3508
3606
 
3509
- Regex = /^([+-]*\d+\.*\d*)[m]$/
3607
+ Regex = /\A([+-]?0*\d{1,8}(?:\.\d+)?)m\z/
3608
+
3609
+ # Bias to a base of 100,000m below the WGS 84 reference spheroid.
3610
+ Bias = 100_000_00
3510
3611
 
3511
3612
  ##
3512
3613
  # Creates a new LOC::Alt from +arg+ which may be:
@@ -3519,13 +3620,14 @@ class Resolv
3519
3620
  when Alt
3520
3621
  return arg
3521
3622
  when String
3522
- altitude = ''
3523
- if Regex =~ arg
3524
- altitude = [($1.to_f*(1e2))+(1e7)].pack("N")
3525
- else
3623
+ unless Regex =~ arg
3526
3624
  raise ArgumentError.new("not a properly formed Alt string: " + arg)
3527
3625
  end
3528
- return Alt.new(altitude)
3626
+ altitude = ($1.to_f * 100).to_i + Bias
3627
+ unless (0...0x1_0000_0000) === altitude
3628
+ raise ArgumentError.new("out of raise as Alt: #{arg}")
3629
+ end
3630
+ return new([altitude].pack("N"))
3529
3631
  else
3530
3632
  raise ArgumentError.new("cannot interpret as Alt: #{arg.inspect}")
3531
3633
  end
@@ -3542,8 +3644,8 @@ class Resolv
3542
3644
  attr_reader :altitude
3543
3645
 
3544
3646
  def to_s # :nodoc:
3545
- a = @altitude.unpack("N").join.to_i
3546
- return ((a.to_f/1e2)-1e5).to_s + "m"
3647
+ a, = @altitude.unpack("N")
3648
+ return "#{(a - Bias).fdiv(100)}m"
3547
3649
  end
3548
3650
 
3549
3651
  def inspect # :nodoc:
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resolv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tanaka Akira
@@ -21,6 +21,7 @@ files:
21
21
  - BSDL
22
22
  - COPYING
23
23
  - README.md
24
+ - ext/win32/resolv/depend
24
25
  - ext/win32/resolv/extconf.rb
25
26
  - ext/win32/resolv/lib/resolv.rb
26
27
  - ext/win32/resolv/resolv.c