@@ -140,168 +140,4 @@ def test_accept_multithread
140
140
server_threads . each ( &:join )
141
141
end
142
142
end
143
-
144
- def test_ai_addrconfig
145
- # This test verifies that we pass AI_ADDRCONFIG to the DNS resolver when making
146
- # an outgoing connection.
147
- # The verification of this is unfortunately incredibly convoluted. We perform the
148
- # test by setting up a fake DNS server to receive queries. Then, we construct
149
- # an environment which has only IPv4 addresses and uses that fake DNS server. We
150
- # then attempt to make an outgoing TCP connection. Finally, we verify that we
151
- # only received A and not AAAA queries on our fake resolver.
152
- # This test can only possibly work on Linux, and only when run as root. If either
153
- # of these conditions aren't met, the test will be skipped.
154
-
155
- # The construction of our IPv6-free environment must happen in a child process,
156
- # which we can put in its own network & mount namespaces.
157
-
158
- omit "This test is disabled. It is retained to show the original intent of [ruby-core:110870]"
159
-
160
- IO . popen ( "-" ) do |test_io |
161
- if test_io . nil?
162
- begin
163
- # Child program
164
- require 'fiddle'
165
- require 'resolv'
166
- require 'open3'
167
-
168
- libc = Fiddle . dlopen ( nil )
169
- begin
170
- unshare = Fiddle ::Function . new ( libc [ 'unshare' ] , [ Fiddle ::TYPE_INT ] , Fiddle ::TYPE_INT )
171
- rescue Fiddle ::DLError
172
- # Test can't run because we don't have unshare(2) in libc
173
- # This will be the case on not-linux, and also on very old glibc versions (or
174
- # possibly other libc's that don't expose this syscall wrapper)
175
- $stdout. write ( Marshal . dump ( { result : :skip , reason : "unshare(2) or mount(2) not in libc" } ) )
176
- exit
177
- end
178
-
179
- # Move our test process into a new network & mount namespace.
180
- # This environment will be configured to be IPv6 free and point DNS resolution
181
- # at a fake DNS server.
182
- # (n.b. these flags are CLONE_NEWNS | CLONE_NEWNET)
183
- ret = unshare . call ( 0x00020000 | 0x40000000 )
184
- errno = Fiddle . last_error
185
- if ret == -1 && errno == Errno ::EPERM ::Errno
186
- # Test can't run because we're not root.
187
- $stdout. write ( Marshal . dump ( { result : :skip , reason : "insufficient permissions to unshare namespaces" } ) )
188
- exit
189
- elsif ret == -1 && ( errno == Errno ::ENOSYS ::Errno || errno == Errno ::EINVAL ::Errno )
190
- # No unshare(2) in the kernel (or kernel too old to know about this namespace type)
191
- $stdout. write ( Marshal . dump ( { result : :skip , reason : "errno #{ errno } calling unshare(2)" } ) )
192
- exit
193
- elsif ret == -1
194
- # Unexpected failure
195
- raise "errno #{ errno } calling unshare(2)"
196
- end
197
-
198
- # Set up our fake DNS environment. Clean out /etc/hosts...
199
- fake_hosts_file = Tempfile . new ( 'ruby_test_hosts' )
200
- fake_hosts_file . write <<~HOSTS
201
- 127.0.0.1 localhost
202
- ::1 localhost
203
- HOSTS
204
- fake_hosts_file . flush
205
-
206
- # Have /etc/resolv.conf point to 127.0.0.1...
207
- fake_resolv_conf = Tempfile . new ( 'ruby_test_resolv' )
208
- fake_resolv_conf . write <<~RESOLV
209
- nameserver 127.0.0.1
210
- RESOLV
211
- fake_resolv_conf . flush
212
-
213
- # Also stub out /etc/nsswitch.conf; glibc can have other resolver modules
214
- # (like systemd-resolved) configured in there other than just using dns,
215
- # so rewrite it to remove any `hosts:` lines and add one which just uses
216
- # dns.
217
- real_nsswitch_conf = File . read ( '/etc/nsswitch.conf' ) rescue ""
218
- fake_nsswitch_conf = Tempfile . new ( 'ruby_test_nsswitch' )
219
- real_nsswitch_conf . lines . reject { _1 =~ /^\s *hosts:/ } . each do |ln |
220
- fake_nsswitch_conf . puts ln
221
- end
222
- fake_nsswitch_conf . puts "hosts: files myhostname dns"
223
- fake_nsswitch_conf . flush
224
-
225
- # This is needed to make sure our bind-mounds aren't visible outside this process.
226
- system 'mount' , '--make-rprivate' , '/' , exception : true
227
- # Bind-mount the fake files over the top of the real files.
228
- system 'mount' , '--bind' , '--make-private' , fake_hosts_file . path , '/etc/hosts' , exception : true
229
- system 'mount' , '--bind' , '--make-private' , fake_resolv_conf . path , '/etc/resolv.conf' , exception : true
230
- system 'mount' , '--bind' , '--make-private' , fake_nsswitch_conf . path , '/etc/nsswitch.conf' , exception : true
231
-
232
- # Create a dummy interface with only an IPv4 address
233
- system 'ip' , 'link' , 'add' , 'dummy0' , 'type' , 'dummy' , exception : true
234
- system 'ip' , 'addr' , 'add' , '192.168.1.2/24' , 'dev' , 'dummy0' , exception : true
235
- system 'ip' , 'link' , 'set' , 'dummy0' , 'up' , exception : true
236
- system 'ip' , 'link' , 'set' , 'lo' , 'up' , exception : true
237
-
238
- # Disable IPv6 on this interface (this is needed to disable the link-local
239
- # IPv6 address)
240
- File . open ( '/proc/sys/net/ipv6/conf/dummy0/disable_ipv6' , 'w' ) do |f |
241
- f . puts "1"
242
- end
243
-
244
- # Create a fake DNS server which will receive the DNS queries triggered by TCPSocket.new
245
- fake_dns_server_socket = UDPSocket . new
246
- fake_dns_server_socket . bind ( '127.0.0.1' , 53 )
247
- received_dns_queries = [ ]
248
- fake_dns_server_thread = Thread . new do
249
- Socket . udp_server_loop_on ( [ fake_dns_server_socket ] ) do |msg , msg_src |
250
- request = Resolv ::DNS ::Message . decode ( msg )
251
- received_dns_queries << request
252
- response = request . dup . tap do |r |
253
- r . qr = 0
254
- r . rcode = 3 # NXDOMAIN
255
- end
256
- msg_src . reply response . encode
257
- end
258
- end
259
-
260
- # Make a request which will hit our fake DNS swerver - this needs to be in _another_
261
- # process because glibc will cache resolver info across the fork otherwise.
262
- load_path_args = $LOAD_PATH. flat_map { [ '-I' , _1 ] }
263
- Open3 . capture3 ( '/proc/self/exe' , *load_path_args , '-rsocket' , '-e' , <<~RUBY )
264
- TCPSocket.open('www.example.com', 4444)
265
- RUBY
266
-
267
- fake_dns_server_thread . kill
268
- fake_dns_server_thread . join
269
-
270
- have_aaaa_qs = received_dns_queries . any? do |query |
271
- query . question . any? do |question |
272
- question [ 1 ] == Resolv ::DNS ::Resource ::IN ::AAAA
273
- end
274
- end
275
-
276
- have_a_q = received_dns_queries . any? do |query |
277
- query . question . any? do |question |
278
- question [ 0 ] . to_s == "www.example.com"
279
- end
280
- end
281
-
282
- if have_aaaa_qs
283
- $stdout. write ( Marshal . dump ( { result : :fail , reason : "got AAAA queries, expected none" } ) )
284
- elsif !have_a_q
285
- $stdout. write ( Marshal . dump ( { result : :fail , reason : "got no A query for example.com" } ) )
286
- else
287
- $stdout. write ( Marshal . dump ( { result : :success } ) )
288
- end
289
- rescue => ex
290
- $stdout. write ( Marshal . dump ( { result : :fail , reason : ex . full_message } ) )
291
- ensure
292
- # Make sure the child process does not transfer control back into the test runner.
293
- exit!
294
- end
295
- else
296
- test_result = Marshal . load ( test_io . read )
297
-
298
- case test_result [ :result ]
299
- when :skip
300
- omit test_result [ :reason ]
301
- when :fail
302
- fail test_result [ :reason ]
303
- end
304
- end
305
- end
306
- end
307
143
end if defined? ( TCPSocket )
0 commit comments