Skip to content

Commit ee5dd64

Browse files
authored
Merge pull request #470 from djs55/dns
dns: set the TC bit and truncate if UDP response > 512 bytes
2 parents c253dad + ea9dbea commit ee5dd64

File tree

4 files changed

+354
-115
lines changed

4 files changed

+354
-115
lines changed

src/hostnet/hostnet_dns.ml

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ let src =
77

88
module Log = (val Logs.src_log src : Logs.LOG)
99

10+
(* Maximum size of a UDP DNS response before we must truncate *)
11+
let max_udp_response = 512
12+
1013
module Config = struct
1114
type t = [
1215
| `Upstream of Dns_forward.Config.t
@@ -326,17 +329,35 @@ struct
326329
Log.info (fun f -> f "Will use the host's DNS resolver");
327330
Lwt.return { local_ip; builtin_names; resolver = Host }
328331

332+
let search f low high =
333+
if not(f low)
334+
then None (* none of the elements satisfy the predicate *)
335+
else
336+
let rec loop low high =
337+
if low = high
338+
then Some low
339+
else
340+
let mid = (low + high + 1) / 2 in
341+
(* since low <> high, mid <> low but it might be mid = high *)
342+
if f mid
343+
then loop mid high
344+
else
345+
if mid = high
346+
then Some low
347+
else loop low mid in
348+
loop low high
349+
329350
let answer t is_tcp buf =
330351
let open Dns.Packet in
331352
let len = Cstruct.len buf in
332353
match Dns.Protocol.Server.parse (Cstruct.sub buf 0 len) with
333354
| None ->
334355
Lwt.return (Error (`Msg "failed to parse DNS packet"))
335356
| Some ({ questions = [ question ]; _ } as request) ->
336-
let reply answers =
357+
let reply ~tc answers =
337358
let id = request.id in
338359
let detail =
339-
{ request.detail with Dns.Packet.qr = Dns.Packet.Response; ra = true }
360+
{ request.detail with Dns.Packet.qr = Dns.Packet.Response; ra = true; tc }
340361
in
341362
let questions = request.questions in
342363
let authorities = [] and additionals = [] in
@@ -354,31 +375,70 @@ struct
354375
{ Dns.Packet.id; detail; questions; answers; authorities;
355376
additionals }
356377
in
378+
let marshal_reply answers =
379+
let buf = marshal @@ reply ~tc:false answers in
380+
if is_tcp
381+
then Some buf (* No need to truncate for TCP *)
382+
else begin
383+
(* If the packet is too big then set the TC bit and truncate by dropping answers *)
384+
let take n from =
385+
let rec loop n from acc = match n, from with
386+
| 0, _ -> acc
387+
| _, [] -> acc
388+
| n, x :: xs -> loop (n - 1) xs (x :: acc) in
389+
List.rev @@ loop n from [] in
390+
if Cstruct.len buf > max_udp_response then begin
391+
match search (fun num ->
392+
(* use only the first 'num' answers *)
393+
Cstruct.len (marshal @@ reply ~tc:true (take num answers)) <= max_udp_response
394+
) 0 (List.length answers) with
395+
| None -> None
396+
| Some num -> Some (marshal @@ reply ~tc:true (take num answers))
397+
end
398+
else Some buf
399+
end in
357400
begin
358401
(* Consider the builtins (from the command-line) to have higher priority
359402
than the addresses in the /etc/hosts file. *)
360403
match try_builtins t.builtin_names question with
361404
| `Does_not_exist ->
362-
Lwt.return (Ok (marshal nxdomain))
405+
Lwt.return (Ok (Some (marshal nxdomain)))
363406
| `Answers answers ->
364-
Lwt.return (Ok (marshal @@ reply answers))
407+
Lwt.return (Ok (marshal_reply answers))
365408
| `Dont_know ->
366409
match try_etc_hosts question with
367410
| Some answers ->
368-
Lwt.return (Ok (marshal @@ reply answers))
411+
Lwt.return (Ok (marshal_reply answers))
369412
| None ->
370413
match is_tcp, t.resolver with
371414
| true, Upstream { dns_tcp_resolver; _ } ->
372-
Dns_tcp_resolver.answer buf dns_tcp_resolver
415+
begin
416+
Dns_tcp_resolver.answer buf dns_tcp_resolver
417+
>>= function
418+
| Error e -> Lwt.return (Error e)
419+
| Ok buf -> Lwt.return (Ok (Some buf))
420+
end
373421
| false, Upstream { dns_udp_resolver; _ } ->
374-
Dns_udp_resolver.answer buf dns_udp_resolver
422+
begin
423+
Dns_udp_resolver.answer buf dns_udp_resolver
424+
>>= function
425+
| Error e -> Lwt.return (Error e)
426+
| Ok buf ->
427+
(* We need to parse and re-marshal so we can set the TC bit and truncate *)
428+
begin match Dns.Protocol.Server.parse buf with
429+
| None ->
430+
Lwt.return (Error (`Msg "Failed to unmarshal DNS response from upstream"))
431+
| Some { answers; _ } ->
432+
Lwt.return (Ok (marshal_reply answers))
433+
end
434+
end
375435
| _, Host ->
376436
D.resolve question
377437
>>= function
378438
| [] ->
379-
Lwt.return (Ok (marshal nxdomain))
439+
Lwt.return (Ok (Some (marshal nxdomain)))
380440
| answers ->
381-
Lwt.return (Ok (marshal @@ reply answers))
441+
Lwt.return (Ok (marshal_reply answers))
382442
end
383443
| _ ->
384444
Lwt.return (Error (`Msg "DNS packet had multiple questions"))
@@ -395,7 +455,10 @@ struct
395455
| Error (`Msg m) ->
396456
Log.warn (fun f -> f "%s lookup failed: %s" (describe buf) m);
397457
Lwt.return (Ok ())
398-
| Ok buffer ->
458+
| Ok None ->
459+
Log.err (fun f -> f "%s unable to marshal response" (describe buf));
460+
Lwt.return (Ok ())
461+
| Ok (Some buffer) ->
399462
Udp.write ~src_port:53 ~dst:src ~dst_port:src_port udp buffer
400463

401464
let handle_tcp ~t =
@@ -414,7 +477,10 @@ struct
414477
| Error (`Msg m) ->
415478
Log.warn (fun f -> f "%s lookup failed: %s" (describe request) m);
416479
Lwt.return_unit
417-
| Ok buffer ->
480+
| Ok None ->
481+
Log.err (fun f -> f "%s unable to marshal response to" (describe request));
482+
Lwt.return_unit
483+
| Ok (Some buffer) ->
418484
Dns_tcp_framing.write packets buffer >>= function
419485
| Error (`Msg m) ->
420486
Log.warn (fun f ->

src/hostnet_test/slirp_stack.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ module Dns_policy = struct
3939

4040
let t = ref (IntMap.add 0 google_dns IntMap.empty)
4141

42+
let clear () = t := (IntMap.add 0 google_dns IntMap.empty)
43+
4244
let config () =
4345
snd @@ IntMap.max_binding !t
4446

src/hostnet_test/suite.ml

Lines changed: 1 addition & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -28,94 +28,6 @@ let test_dhcp_query () =
2828
in
2929
run ~pcap:"test_dhcp_query.pcap" t
3030

31-
let set_dns_policy ?builtin_names use_host =
32-
Mclock.connect () >|= fun clock ->
33-
Dns_policy.remove ~priority:3;
34-
Dns_policy.add ~priority:3
35-
~config:(if use_host then `Host else Dns_policy.google_dns);
36-
Slirp_stack.Debug.update_dns ?builtin_names clock
37-
38-
let test_dns_query server use_host () =
39-
let t _ stack =
40-
set_dns_policy use_host >>= fun () ->
41-
let resolver = DNS.create stack.Client.t in
42-
DNS.gethostbyname ~server resolver "www.google.com" >|= function
43-
| (_ :: _) as ips ->
44-
Log.info (fun f -> f "www.google.com has IPs: %a" pp_ips ips);
45-
| _ ->
46-
Log.err (fun f -> f "Failed to lookup www.google.com");
47-
failwith "Failed to lookup www.google.com"
48-
in
49-
run ~pcap:"test_dns_query.pcap" t
50-
51-
let test_builtin_dns_query server use_host () =
52-
let name = "experimental.host.name.localhost" in
53-
let t _ stack =
54-
set_dns_policy ~builtin_names:[ Dns.Name.of_string name, Ipaddr.V4 (Ipaddr.V4.localhost) ] use_host
55-
>>= fun () ->
56-
let resolver = DNS.create stack.Client.t in
57-
DNS.gethostbyname ~server resolver name >>= function
58-
| (_ :: _) as ips ->
59-
Log.info (fun f -> f "%s has IPs: %a" name pp_ips ips);
60-
Lwt.return ()
61-
| _ ->
62-
Log.err (fun f -> f "Failed to lookup %s" name);
63-
failwith ("Failed to lookup " ^ name)
64-
in
65-
run ~pcap:"test_builtin_dns_query.pcap" t
66-
67-
let test_etc_hosts_query server use_host () =
68-
let test_name = "vpnkit.is.cool.yes.really" in
69-
let t _ stack =
70-
set_dns_policy use_host >>= fun () ->
71-
let resolver = DNS.create stack.Client.t in
72-
DNS.gethostbyname ~server resolver test_name >>= function
73-
| (_ :: _) as ips ->
74-
Log.err (fun f ->
75-
f "This test relies on the name %s not existing but it really \
76-
has IPs: %a" test_name pp_ips ips);
77-
Fmt.kstrf failwith "Test name %s really does exist" test_name
78-
| _ ->
79-
Hosts.etc_hosts := [
80-
test_name, Ipaddr.V4 (Ipaddr.V4.localhost);
81-
];
82-
DNS.gethostbyname ~server resolver test_name >|= function
83-
| (_ :: _) as ips ->
84-
Log.info (fun f -> f "Name %s has IPs: %a" test_name pp_ips ips);
85-
Hosts.etc_hosts := []
86-
| _ ->
87-
Log.err (fun f -> f "Failed to lookup name from /etc/hosts");
88-
Hosts.etc_hosts := [];
89-
failwith "failed to lookup name from /etc/hosts"
90-
in
91-
run ~pcap:"test_etc_hosts_query.pcap" t
92-
93-
let test_etc_hosts_priority server use_host () =
94-
let name = "builtins.should.be.higher.priority" in
95-
let builtin_ip = Ipaddr.of_string_exn "127.0.0.1" in
96-
let hosts_ip = Ipaddr.of_string_exn "127.0.0.2" in
97-
let t _ stack =
98-
set_dns_policy ~builtin_names:[ Dns.Name.of_string name, builtin_ip ] use_host
99-
>>= fun () ->
100-
Hosts.etc_hosts := [
101-
name, hosts_ip;
102-
];
103-
let resolver = DNS.create stack.Client.t in
104-
DNS.gethostbyname ~server resolver name >>= function
105-
| [ ip ] ->
106-
Log.info (fun f -> f "%s has single IP: %a" name Ipaddr.pp_hum ip);
107-
if Ipaddr.compare ip builtin_ip = 0
108-
then Lwt.return ()
109-
else failwith ("Builtin DNS names should have higher priority than /etc/hosts")
110-
| (_ :: _) as ips ->
111-
Log.info (fun f -> f "%s has IPs: %a" name pp_ips ips);
112-
failwith ("Duplicate DNS names resolved for " ^ name);
113-
| _ ->
114-
Log.err (fun f -> f "Failed to lookup %s" name);
115-
failwith ("Failed to lookup " ^ name)
116-
in
117-
run ~pcap:"test_etc_hosts_priority.pcap" t
118-
11931
let test_max_connections () =
12032
let t _ stack =
12133
Lwt.finalize (fun () ->
@@ -373,21 +285,6 @@ let test_dhcp = [
373285
["check that the DHCP server works", `Quick, test_dhcp_query];
374286
]
375287

376-
let test_dns use_host =
377-
let prefix = if use_host then "Host resolver" else "DNS forwarder" in [
378-
prefix ^ ": lookup ",
379-
["", `Quick, test_dns_query primary_dns_ip use_host];
380-
381-
prefix ^ ": builtins",
382-
[ "", `Quick, test_builtin_dns_query primary_dns_ip use_host ];
383-
384-
prefix ^ ": _etc_hosts",
385-
[ "", `Quick, test_etc_hosts_query primary_dns_ip use_host ];
386-
387-
prefix ^ ": _etc_hosts_priority",
388-
[ "", `Quick, test_etc_hosts_priority primary_dns_ip use_host ];
389-
]
390-
391288
let test_tcp = [
392289
"HTTP GET", [ "HTTP GET http://www.google.com/", `Quick, test_http_fetch ];
393290

@@ -412,7 +309,7 @@ let test_tcp = [
412309

413310
let tests =
414311
Hosts_test.tests @ Forwarding.tests @ test_dhcp
415-
@ (test_dns true) @ (test_dns false)
312+
@ Test_dns.suite
416313
@ test_tcp @ Test_nat.tests @ Test_http.tests @ Test_http.Exclude.tests
417314
@ Test_half_close.tests @ Test_ping.tests
418315
@ Test_bridge.tests @ Test_forward_protocol.suite

0 commit comments

Comments
 (0)