-
Notifications
You must be signed in to change notification settings - Fork 42
/
dns.ml
4744 lines (4315 loc) · 172 KB
/
dns.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(* (c) 2017-2019 Hannes Mehnert, all rights reserved *)
type proto = [ `Tcp | `Udp ]
let max_rdata_length =
(* The maximum length of a single resource record data must be limited, such
a resource record needs to fit into a DNS message together with some more
data (namely question, header, and TSIG). The size of a DNS message is
limited by EDNS, which uses the class field (2 byte) for the indication,
and the TCP transport requires a 2 byte length prefix.
so in total it may not exceed 65535 bytes, including:
- DNS header
- 1 QUESTION
- 1 RR
- 1 TSIG
being conservative (name compression = off):
- header: ID (2 byte) OP+FLAGS (2 byte), 4 * 2 byte count of question, answer, authority, additional = 12 byte
- question: name (max 255 bytes), typ (2 byte), class (2 byte) = 259
- RR: name, typ, class, ttl (4 byte), rdlength (2 byte), rdata (this is the size we are interested in) = 265 + x
- TSIG being key name, typ, class, ttl, rdlength, 16 bytes (base TSIG struct), algorithm name (atm 13 bytes "hmac-sha512"), mac (64 bytes, sha512) = 358
--> 65535 - 894 = 64641 bytes
*)
64641
let andThen v f = match v with 0 -> f | x -> x
let opt_eq f a b = match a, b with
| Some a, Some b -> f a b
| None, None -> true
| _ -> false
let guard p err = if p then Ok () else Error err
let src = Logs.Src.create "dns" ~doc:"DNS core"
module Log = (val Logs.src_log src : Logs.LOG)
module Ptime_extra = struct
(* this is here because I don't like float, and rather convert Ptime.t to int64 *)
let s_in_d = 86_400L
let ps_in_s = 1_000_000_000_000L
let span_to_int64 ts =
let d_min, d_max = Int64.(div min_int s_in_d, div max_int s_in_d) in
let d, ps = Ptime.Span.to_d_ps ts in
let d = Int64.of_int d in
if d < d_min || d > d_max then
None
else
let s = Int64.mul d s_in_d in
let s' = Int64.(add s (div ps ps_in_s)) in
if s' < s then
None
else
Some s'
let to_int64 t = span_to_int64 (Ptime.to_span t)
let of_int64 ?(off = 0) s =
let d, ps = Int64.(div s s_in_d, mul (rem s s_in_d) ps_in_s) in
if d < Int64.of_int min_int || d > Int64.of_int max_int then
Error (`Malformed (off, Fmt.str "timestamp does not fit in time range %Ld" s))
else
match Ptime.Span.of_d_ps (Int64.to_int d, ps) with
| Some span ->
begin match Ptime.of_span span with
| Some ts -> Ok ts
| None -> Error (`Malformed (off, Fmt.str "span does not fit into timestamp %Ld" s))
end
| None -> Error (`Malformed (off, Fmt.str "timestamp does not fit %Ld" s))
end
module Class = struct
(* 16 bit *)
type t =
(* Reserved0 [@id 0] RFC6895 *)
| IN (* RFC1035 *)
(* 2 Uassigned *)
| CHAOS (* D. Moon, "Chaosnet", A.I. Memo 628, Massachusetts Institute of Technology Artificial Intelligence Laboratory, June 1981. *)
| HESIOD (* Dyer, S., and F. Hsu, "Hesiod", Project Athena Technical Plan - Name Service, April 1987. *)
| NONE (* RFC2136 *)
| ANY_CLASS (* RFC1035 *)
(* 256-65279 Unassigned *)
(* 65280-65534 Reserved for Private Use [RFC6895] *)
(* ReservedFFFF [@id 65535] *)
let to_int = function
| IN -> 1
| CHAOS -> 3
| HESIOD -> 4
| NONE -> 254
| ANY_CLASS -> 255
let _compare a b = Int.compare (to_int a) (to_int b)
let of_int ?(off = 0) = function
| 1 -> Ok IN
| 3 -> Ok CHAOS
| 4 -> Ok HESIOD
| 254 -> Ok NONE
| 255 -> Ok ANY_CLASS
| c -> Error (`Not_implemented (off, Fmt.str "class %X" c))
let to_string = function
| IN -> "IN"
| CHAOS -> "CHAOS"
| HESIOD -> "HESIOD"
| NONE -> "NONE"
| ANY_CLASS -> "ANY_CLASS"
let _pp ppf c = Fmt.string ppf (to_string c)
end
module Opcode = struct
(* 4 bit *)
type t =
| Query (* RFC1035 *)
| IQuery (* Inverse Query, OBSOLETE) [RFC3425] *)
| Status (* RFC1035 *)
(* 3 Unassigned *)
| Notify (* RFC1996 *)
| Update (* RFC2136 *)
(* 6-15 Unassigned *)
let to_int = function
| Query -> 0
| IQuery -> 1
| Status -> 2
| Notify -> 4
| Update -> 5
let compare a b = Int.compare (to_int a) (to_int b)
let of_int ?(off = 0) = function
| 0 -> Ok Query
| 1 -> Ok IQuery
| 2 -> Ok Status
| 4 -> Ok Notify
| 5 -> Ok Update
| x -> Error (`Not_implemented (off, Fmt.str "opcode 0x%X" x))
let to_string = function
| Query -> "Query"
| IQuery -> "IQuery"
| Status -> "Status"
| Notify -> "Notify"
| Update -> "Update"
let pp ppf t = Fmt.string ppf (to_string t)
end
module Rcode = struct
(* 4 bit + 16 in EDNS/TSIG*)
type t =
| NoError (* No Error,[RFC1035] *)
| FormErr (* Format Error,[RFC1035] *)
| ServFail (* Server Failure,[RFC1035] *)
| NXDomain (* Non-Existent Domain,[RFC1035] *)
| NotImp (* Not Implemented,[RFC1035] *)
| Refused (* Query Refused,[RFC1035] *)
| YXDomain (* Name Exists when it should not,[RFC2136][RFC6672] *)
| YXRRSet (* RR Set Exists when it should not,[RFC2136] *)
| NXRRSet (* RR Set that should exist does not,[RFC2136] *)
| NotAuth (* Server Not Authoritative for zone,[RFC2136]
9,NotAuth,Not Authorized,[RFC2845] *)
| NotZone (* Name not contained in zone,[RFC2136] *)
(* 11-15,Unassigned *)
| BadVersOrSig (* 16,BADVERS,Bad OPT Version,[RFC6891]
16,BADSIG,TSIG Signature Failure,[RFC2845] *)
| BadKey (* Key not recognized,[RFC2845] *)
| BadTime (* Signature out of time window,[RFC2845] *)
| BadMode (* BADMODE,Bad TKEY Mode,[RFC2930] *)
| BadName (* BADNAME,Duplicate key name,[RFC2930] *)
| BadAlg (* BADALG,Algorithm not supported,[RFC2930] *)
| BadTrunc (* BADTRUNC,Bad Truncation,[RFC4635] *)
| BadCookie (* BADCOOKIE,Bad/missing Server Cookie,[RFC7873] *)
(* 24-3840,Unassigned *)
(* 3841-4095,Reserved for Private Use,,[RFC6895] *)
(* 4096-65534,Unassigned *)
(* 65535,"Reserved, can be allocated by Standards Action",,[RFC6895] *)
let to_int = function
| NoError -> 0 | FormErr -> 1 | ServFail -> 2 | NXDomain -> 3
| NotImp -> 4 | Refused -> 5 | YXDomain -> 6 | YXRRSet -> 7
| NXRRSet -> 8 | NotAuth -> 9 | NotZone -> 10 | BadVersOrSig -> 16
| BadKey -> 17 | BadTime -> 18 | BadMode -> 19 | BadName -> 20
| BadAlg -> 21 | BadTrunc -> 22 | BadCookie -> 23
let compare a b = Int.compare (to_int a) (to_int b)
let of_int ?(off = 0) = function
| 0 -> Ok NoError | 1 -> Ok FormErr | 2 -> Ok ServFail
| 3 -> Ok NXDomain | 4 -> Ok NotImp | 5 -> Ok Refused
| 6 -> Ok YXDomain | 7 -> Ok YXRRSet | 8 -> Ok NXRRSet
| 9 -> Ok NotAuth | 10 -> Ok NotZone | 16 -> Ok BadVersOrSig
| 17 -> Ok BadKey | 18 -> Ok BadTime | 19 -> Ok BadMode
| 20 -> Ok BadName | 21 -> Ok BadAlg | 22 -> Ok BadTrunc
| 23 -> Ok BadCookie
| x -> Error (`Not_implemented (off, Fmt.str "rcode 0x%04X" x))
let to_string = function
| NoError -> "no error" | FormErr -> "form error"
| ServFail -> "server failure" | NXDomain -> "no such domain"
| NotImp -> "not implemented" | Refused -> "refused"
| YXDomain -> "name exists when it should not"
| YXRRSet -> "resource record set exists when it should not"
| NXRRSet -> "resource record set that should exist does not"
| NotAuth -> "server not authoritative for zone or not authorized"
| NotZone -> "name not contained in zone"
| BadVersOrSig -> "bad version or signature"
| BadKey -> "bad TSIG key" | BadTime -> "signature time out of window"
| BadMode -> "bad TKEY mode" | BadName -> "duplicate key name"
| BadAlg -> "unsupported algorithm" | BadTrunc -> "bad truncation"
| BadCookie -> "bad cookie"
let pp ppf r = Fmt.string ppf (to_string r)
end
let ( let* ) = Result.bind
module Name = struct
module Int_map = Map.Make(struct
type t = int
let compare = Int.compare
end)
type name_offset_map = int Domain_name.Map.t
let ptr_tag = 0xC0 (* = 1100 0000 *)
let decode names buf ~off =
(* first collect all the labels (and their offsets) *)
let rec aux offsets off =
match Cstruct.get_uint8 buf off with
| 0 -> Ok ((`Z, off), offsets, succ off)
| i when i >= ptr_tag ->
let ptr = (i - ptr_tag) lsl 8 + Cstruct.get_uint8 buf (succ off) in
Ok ((`P ptr, off), offsets, off + 2)
| i when i >= 64 -> Error (`Malformed (off, Fmt.str "label tag 0x%x" i)) (* bit patterns starting with 10 or 01 *)
| i -> (* this is clearly < 64! *)
let name = Cstruct.to_string (Cstruct.sub buf (succ off) i) in
aux ((name, off) :: offsets) (succ off + i)
in
(* Cstruct.xxx can raise, and we'll have a partial parse then *)
let* l, offs, foff = (try aux [] off with Invalid_argument _ -> Error `Partial) in
(* treat last element special -- either Z or P *)
let* off, name, size =
match l with
| `Z, off -> Ok (off, Domain_name.root, 1)
| `P p, off -> match Int_map.find p names with
| exception Not_found ->
Error (`Malformed (off, "bad label offset: " ^ string_of_int p))
| (exp, size) -> Ok (off, exp, size)
in
(* insert last label into names Map*)
let names = Int_map.add off (name, size) names in
(* fold over offs, insert into names Map, and reassemble the actual name *)
let t = Array.(append (Domain_name.to_array name) (make (List.length offs) "")) in
let names, _, size =
List.fold_left (fun (names, idx, size) (label, off) ->
let s = succ size + String.length label in
Array.set t idx label ;
let sub = Domain_name.of_array (Array.sub t 0 (succ idx)) in
Int_map.add off (sub, s) names, succ idx, s)
(names, Array.length (Domain_name.to_array name), size) offs
in
let t = Domain_name.of_array t in
if size > 255 then
Error (`Malformed (off, "name too long"))
else
Ok (t, names, foff)
let encode : ?compress:bool -> 'a Domain_name.t -> int Domain_name.Map.t ->
Cstruct.t -> int -> int Domain_name.Map.t * int =
fun ?(compress = true) name names buf off ->
let name = Domain_name.raw name in
let encode_lbl lbl off =
let l = String.length lbl in
Cstruct.set_uint8 buf off l ;
Cstruct.blit_from_string lbl 0 buf (succ off) l ;
off + succ l
and z off =
Cstruct.set_uint8 buf off 0 ;
succ off
in
let maybe_insert_label name off names =
(* do not add label to our map if it'd overflow the pointer (14 bit) *)
if off < 1 lsl 14 then
Domain_name.Map.add name off names
else
names
and name_remainder arr l off =
let last = Array.get arr (pred l)
and rem = Array.sub arr 0 (pred l)
in
let l = encode_lbl last off in
l, Domain_name.of_array rem
in
let names, off =
if compress then
let rec one names off name =
let arr = Domain_name.to_array name in
let l = Array.length arr in
if l = 0 then
names, z off
else
match Domain_name.Map.find name names with
| None ->
let l, rem = name_remainder arr l off in
one (maybe_insert_label name off names) l rem
| Some ptr ->
let data = ptr_tag lsl 8 + ptr in
Cstruct.BE.set_uint16 buf off data ;
names, off + 2
in
one names off name
else
let rec one names off name =
let arr = Domain_name.to_array name in
let l = Array.length arr in
if l = 0 then
names, z off
else
let l, rem = name_remainder arr l off in
one (maybe_insert_label name off names) l rem
in
one names off name
in
names, off
let host off name =
Result.map_error (function `Msg m ->
`Malformed (off, Fmt.str "invalid hostname %a: %s" Domain_name.pp name m))
(Domain_name.host name)
(*
(* enable once https://github.com/ocaml/dune/issues/897 is resolved *)
let%expect_test "decode_name" =
let test ?(map = Int_map.empty) ?(off = 0) data rmap roff =
match decode map (Cstruct.of_string data) ~off with
| Error _ -> Format.printf "decode error"
| Ok (name, omap, ooff) ->
begin match Int_map.equal (fun (n, off) (n', off') ->
Domain_name.equal n n' && off = off') rmap omap, roff = ooff
with
| true, true -> Format.printf "%a" Domain_name.pp name
| false, _ -> Format.printf "map mismatch"
| _, false -> Format.printf "offset mismatch"
end
in
let test_err ?(map = Int_map.empty) ?(off = 0) data =
match decode map (Cstruct.of_string data) ~off with
| Error _ -> Format.printf "error (as expected)"
| Ok _ -> Format.printf "expected error, got ok"
in
let n_of_s = Domain_name.of_string_exn in
let map =
Int_map.add 0 (n_of_s "foo.com", 9)
(Int_map.add 4 (n_of_s "com", 5)
(Int_map.add 8 (Domain_name.root, 1) Int_map.empty))
in
test "\003foo\003com\000" map 9;
[%expect {|foo.com|}];
test ~map ~off:9 "\003foo\003com\000\xC0\000" (Int_map.add 9 (n_of_s "foo.com", 9) map) 11;
[%expect {|foo.com|}];
let map' =
Int_map.add 13 (n_of_s "foo.com", 9)
(Int_map.add 9 (n_of_s "bar.foo.com", 13) map)
in
test ~map ~off:9 "\003foo\003com\000\003bar\xC0\000" map' 15;
[%expect {|bar.foo.com|}];
let map' =
Int_map.add 14 (n_of_s "foo.com", 9)
(Int_map.add 9 (n_of_s "bar-.foo.com", 14) map)
in
test ~map ~off:9 "\003foo\003com\000\004bar-\xC0\000" map' 16;
[%expect {|bar-.foo.com|}];
let map' =
Int_map.add 0 (n_of_s "f23", 5) Int_map.(add 4 (Domain_name.root, 1) empty)
in
test "\003f23\000" map' 5;
[%expect {|f23|}];
let map' =
Int_map.add 0 (n_of_s "23", 4)
(Int_map.add 3 (Domain_name.root, 1) Int_map.empty)
in
test "\00223\000" map' 4;
[%expect {|23|}];
test_err "\003bar"; (* incomplete label *)
[%expect {|error (as expected)|}];
test_err "\xC0"; (* incomplete ptr *)
[%expect {|error (as expected)|}];
test_err "\005foo"; (* incomplete label *)
[%expect {|error (as expected)|}];
test_err "\xC0\x0A"; (* bad pointer *)
[%expect {|error (as expected)|}];
test_err "\xC0\x00"; (* cyclic pointer *)
[%expect {|error (as expected)|}];
test_err "\xC0\x01"; (* pointer to middle of pointer *)
[%expect {|error (as expected)|}];
test_err "\x40"; (* bad tag 0x40 *)
[%expect {|error (as expected)|}];
test_err "\x80"; (* bad tag 0x80 *)
[%expect {|error (as expected)|}];
let map' =
Int_map.add 0 (n_of_s "-", 3)
(Int_map.add 2 (Domain_name.root, 1) Int_map.empty)
in
test "\001-\000" map' 3; (* "-" at start of label *)
[%expect {|-|}];
let map' =
Int_map.add 0 (n_of_s "foo-+", 7)
(Int_map.add 6 (Domain_name.root, 1) Int_map.empty)
in
test "\005foo-+\000" map' 7; (* content foo-+ in label *)
[%expect {|foo-+|}];
let map' =
Int_map.add 0 (n_of_s "23", 4)
(Int_map.add 3 (Domain_name.root, 1) Int_map.empty)
in
test "\00223\000" map' 4; (* content 23 in label *)
[%expect {|23|}];
(* longest allowed domain name *)
let max = "s23456789012345678901234567890123456789012345678901234567890123" in
let lst = String.sub max 0 61 in
let full = n_of_s (String.concat "." [ max ; max ; max ; lst ]) in
let map' =
Int_map.add 0 (full, 255)
(Int_map.add 64 (n_of_s (String.concat "." [ max ; max ; lst ]), 191)
(Int_map.add 128 (n_of_s (String.concat "." [ max ; lst ]), 127)
(Int_map.add 192 (n_of_s lst, 63)
(Int_map.add 254 (Domain_name.root, 1) Int_map.empty))))
in
test ("\x3F" ^ max ^ "\x3F" ^ max ^ "\x3F" ^ max ^ "\x3D" ^ lst ^ "\000")
map' 255 ;
[%expect {|s23456789012345678901234567890123456789012345678901234567890123.s23456789012345678901234567890123456789012345678901234567890123.s23456789012345678901234567890123456789012345678901234567890123.s234567890123456789012345678901234567890123456789012345678901|}];
test_err ("\x3F" ^ max ^ "\x3F" ^ max ^ "\x3F" ^ max ^ "\x3E" ^ lst ^ "1\000"); (* name too long *)
[%expect {|error (as expected)|}];
test_err ("\x3F" ^ max ^ "\x3F" ^ max ^ "\x3F" ^ max ^ "\x3F" ^ max ^ "\000"); (* domain name really too long *)
[%expect {|error (as expected)|}]
let%expect_test "encode_name" =
let cs = Cstruct.create 30 in
let test_cs ?(off = 0) len =
Format.printf "%a" Cstruct.hexdump_pp (Cstruct.sub cs off len)
in
let test ?compress ?(map = Domain_name.Map.empty) ?(off = 0) name rmap roff =
let omap, ooff = encode ?compress name map cs off in
if Domain_name.Map.equal (fun a b -> int_compare a b = 0) rmap omap && roff = ooff then
Format.printf "ok"
else
Format.printf "error"
in
let n_of_s = Domain_name.of_string_exn in
test Domain_name.root Domain_name.Map.empty 1; (* compressed encode of root is good *)
[%expect {|ok|}];
test_cs 1;
[%expect {|00|}];
test ~compress:false Domain_name.root Domain_name.Map.empty 1;
[%expect {|ok|}];
test_cs 1;
[%expect {|00|}];
let map =
Domain_name.Map.add (n_of_s "foo.bar") 0
(Domain_name.Map.add (n_of_s "bar") 4 Domain_name.Map.empty)
in
test (n_of_s "foo.bar") map 9; (* encode of foo.bar is good *)
[%expect {|ok|}];
test_cs 9;
[%expect {|03 66 6f 6f 03 62 61 72 00|}];
test ~compress:false (n_of_s "foo.bar") map 9; (* uncompressed foo.bar is good *)
[%expect {|ok|}];
test_cs 9;
[%expect {|03 66 6f 6f 03 62 61 72 00|}];
let emap = Domain_name.Map.add (n_of_s "baz.foo.bar") 9 map in
test ~map ~off:9 (n_of_s "baz.foo.bar") emap 15; (* encode of baz.foo.bar is good *)
[%expect {|ok|}];
test_cs 15;
[%expect {|03 66 6f 6f 03 62 61 72 00 03 62 61 7a c0 00|}];
let map' =
Domain_name.Map.add (n_of_s "baz.foo.bar") 9
(Domain_name.Map.add (n_of_s "foo.bar") 13
(Domain_name.Map.add (n_of_s "bar") 17 Domain_name.Map.empty))
in
test ~compress:false ~map ~off:9 (n_of_s "baz.foo.bar") map' 22;
[%expect {|ok|}];
test_cs 22;
[%expect {|
03 66 6f 6f 03 62 61 72 00 03 62 61 7a 03 66 6f
6f 03 62 61 72 00|}]
*)
end
(* start of authority *)
module Soa = struct
type t = {
nameserver : [ `raw ] Domain_name.t ;
hostmaster : [ `raw ] Domain_name.t ;
serial : int32 ;
refresh : int32 ;
retry : int32 ;
expiry : int32 ;
minimum : int32 ;
}
let default_refresh = 86400l (* 24 hours *)
let default_retry = 7200l (* 2 hours *)
let default_expiry = 3600000l (* 1000 hours *)
let default_minimum = 3600l (* 1 hour *)
let create ?(serial = 0l) ?(refresh = default_refresh) ?(retry = default_retry)
?(expiry = default_expiry) ?(minimum = default_minimum) ?hostmaster nameserver =
let nameserver = Domain_name.raw nameserver in
let hostmaster = match hostmaster with
| None -> Domain_name.(prepend_label_exn (drop_label_exn nameserver) "hostmaster")
| Some x -> Domain_name.raw x
in
{ nameserver ; hostmaster ; serial ; refresh ; retry ; expiry ; minimum }
let canonical t =
{ t with nameserver = Domain_name.canonical t.nameserver ;
hostmaster = Domain_name.canonical t.hostmaster }
let pp ppf soa =
Fmt.pf ppf "SOA %a %a %lu %lu %lu %lu %lu"
Domain_name.pp soa.nameserver Domain_name.pp soa.hostmaster
soa.serial soa.refresh soa.retry soa.expiry soa.minimum
let compare soa soa' =
andThen (Int32.compare soa.serial soa'.serial)
(andThen (Domain_name.compare soa.nameserver soa'.nameserver)
(andThen (Domain_name.compare soa.hostmaster soa'.hostmaster)
(andThen (Int32.compare soa.refresh soa'.refresh)
(andThen (Int32.compare soa.retry soa'.retry)
(andThen (Int32.compare soa.expiry soa'.expiry)
(Int32.compare soa.minimum soa'.minimum))))))
let newer ~old soa = Int32.sub soa.serial old.serial > 0l
let decode_exn names buf ~off ~len:_ =
let* nameserver, names, off = Name.decode names buf ~off in
let* hostmaster, names, off = Name.decode names buf ~off in
let serial = Cstruct.BE.get_uint32 buf off in
let refresh = Cstruct.BE.get_uint32 buf (off + 4) in
let retry = Cstruct.BE.get_uint32 buf (off + 8) in
let expiry = Cstruct.BE.get_uint32 buf (off + 12) in
let minimum = Cstruct.BE.get_uint32 buf (off + 16) in
let soa =
{ nameserver ; hostmaster ; serial ; refresh ; retry ; expiry ; minimum }
in
Ok (soa, names, off + 20)
let encode ?compress soa names buf off =
let names, off = Name.encode ?compress soa.nameserver names buf off in
let names, off = Name.encode ?compress soa.hostmaster names buf off in
Cstruct.BE.set_uint32 buf off soa.serial ;
Cstruct.BE.set_uint32 buf (off + 4) soa.refresh ;
Cstruct.BE.set_uint32 buf (off + 8) soa.retry ;
Cstruct.BE.set_uint32 buf (off + 12) soa.expiry ;
Cstruct.BE.set_uint32 buf (off + 16) soa.minimum ;
names, off + 20
end
(* name server *)
module Ns = struct
type t = [ `host ] Domain_name.t
let canonical t = Domain_name.canonical t
let pp ppf ns = Fmt.pf ppf "NS %a" Domain_name.pp ns
let compare = Domain_name.compare
let decode names buf ~off ~len:_ =
let* name, names, off' = Name.decode names buf ~off in
let* host = Name.host off name in
Ok (host, names, off')
let encode = Name.encode
end
(* mail exchange *)
module Mx = struct
type t = {
preference : int ;
mail_exchange : [ `host ] Domain_name.t ;
}
let canonical t =
{ t with mail_exchange = Domain_name.canonical t.mail_exchange }
let pp ppf { preference ; mail_exchange } =
Fmt.pf ppf "MX %u %a" preference Domain_name.pp mail_exchange
let compare mx mx' =
andThen (Int.compare mx.preference mx'.preference)
(Domain_name.compare mx.mail_exchange mx'.mail_exchange)
let decode_exn names buf ~off ~len:_ =
let preference = Cstruct.BE.get_uint16 buf off in
let off = off + 2 in
let* mx, names, off' = Name.decode names buf ~off in
let* mail_exchange = Name.host off mx in
Ok ({ preference ; mail_exchange }, names, off')
let encode ?compress { preference ; mail_exchange } names buf off =
Cstruct.BE.set_uint16 buf off preference ;
Name.encode ?compress mail_exchange names buf (off + 2)
end
(* canonical name *)
module Cname = struct
type t = [ `raw ] Domain_name.t
let canonical t = Domain_name.canonical t
let pp ppf alias = Fmt.pf ppf "CNAME %a" Domain_name.pp alias
let compare = Domain_name.compare
let decode names buf ~off ~len:_ = Name.decode names buf ~off
let encode = Name.encode
end
(* address record *)
module A = struct
type t = Ipaddr.V4.t
let pp ppf address = Fmt.pf ppf "A %a" Ipaddr.V4.pp address
let compare = Ipaddr.V4.compare
let decode_exn names buf ~off ~len:_ =
let ip = Cstruct.BE.get_uint32 buf off in
Ok (Ipaddr.V4.of_int32 ip, names, off + 4)
let encode ip names buf off =
let ip = Ipaddr.V4.to_int32 ip in
Cstruct.BE.set_uint32 buf off ip ;
names, off + 4
end
(* quad-a record *)
module Aaaa = struct
type t = Ipaddr.V6.t
let pp ppf address = Fmt.pf ppf "AAAA %a" Ipaddr.V6.pp address
let compare = Ipaddr.V6.compare
let decode_exn names buf ~off ~len:_ =
let iph = Cstruct.BE.get_uint64 buf off
and ipl = Cstruct.BE.get_uint64 buf (off + 8)
in
Ok (Ipaddr.V6.of_int64 (iph, ipl), names, off + 16)
let encode ip names buf off =
let iph, ipl = Ipaddr.V6.to_int64 ip in
Cstruct.BE.set_uint64 buf off iph ;
Cstruct.BE.set_uint64 buf (off + 8) ipl ;
names, off + 16
end
(* domain name pointer - reverse entries *)
module Ptr = struct
type t = [ `host ] Domain_name.t
let canonical t = Domain_name.canonical t
let pp ppf rev = Fmt.pf ppf "PTR %a" Domain_name.pp rev
let compare = Domain_name.compare
let decode names buf ~off ~len:_ =
let* rname, names, off' = Name.decode names buf ~off in
let* ptr = Name.host off rname in
Ok (ptr, names, off')
let encode = Name.encode
end
(* service record *)
module Srv = struct
type t = {
priority : int ;
weight : int ;
port : int ;
target : [ `host ] Domain_name.t
}
let canonical t =
{ t with target = Domain_name.canonical t.target }
let pp ppf t =
Fmt.pf ppf
"SRV priority %d weight %d port %d target %a"
t.priority t.weight t.port Domain_name.pp t.target
let compare a b =
andThen (Int.compare a.priority b.priority)
(andThen (Int.compare a.weight b.weight)
(andThen (Int.compare a.port b.port)
(Domain_name.compare a.target b.target)))
let decode_exn names buf ~off ~len:_ =
let priority = Cstruct.BE.get_uint16 buf off
and weight = Cstruct.BE.get_uint16 buf (off + 2)
and port = Cstruct.BE.get_uint16 buf (off + 4)
in
let off = off + 6 in
let* target, names, off' = Name.decode names buf ~off in
let* target = Name.host off target in
Ok ({ priority ; weight ; port ; target }, names, off')
let encode t names buf off =
Cstruct.BE.set_uint16 buf off t.priority ;
Cstruct.BE.set_uint16 buf (off + 2) t.weight ;
Cstruct.BE.set_uint16 buf (off + 4) t.port ;
(* as of rfc2782, no name compression for target! rfc2052 required it *)
Name.encode ~compress:false t.target names buf (off + 6)
end
(* DNS key *)
module Dnskey = struct
(* 8 bit *)
type algorithm =
| RSA_SHA1 | RSASHA1_NSEC3_SHA1 | RSA_SHA256 | RSA_SHA512
| P256_SHA256 | P384_SHA384 | ED25519
| MD5 | SHA1 | SHA224 | SHA256 | SHA384 | SHA512 | Unknown of int
let algorithm_to_int = function
| RSA_SHA1 -> 5
| RSASHA1_NSEC3_SHA1 -> 7
| RSA_SHA256 -> 8
| RSA_SHA512 -> 10
| P256_SHA256 -> 13
| P384_SHA384 -> 14
| ED25519 -> 15
| MD5 -> 157
| SHA1 -> 161
| SHA224 -> 162
| SHA256 -> 163
| SHA384 -> 164
| SHA512 -> 165
| Unknown x -> x
let int_to_algorithm = function
| 5 -> RSA_SHA1
| 7 -> RSASHA1_NSEC3_SHA1
| 8 -> RSA_SHA256
| 10 -> RSA_SHA512
| 13 -> P256_SHA256
| 14 -> P384_SHA384
| 15 -> ED25519
| 157 -> MD5
| 161 -> SHA1
| 162 -> SHA224
| 163 -> SHA256
| 164 -> SHA384
| 165 -> SHA512
| x ->
if x >= 0 && x < 256 then
Unknown x
else
invalid_arg ("invalid DNSKEY algorithm " ^ string_of_int x)
let algorithm_to_string = function
| RSA_SHA1 -> "RSASHA1"
| RSASHA1_NSEC3_SHA1 -> "RSASHA1NSEC3SHA1"
| RSA_SHA256 -> "RSASHA256"
| RSA_SHA512 -> "RSASHA512"
| P256_SHA256 -> "ECDSAP256SHA256"
| P384_SHA384 -> "ECDSAP384SHA384"
| ED25519 -> "ED25519"
| MD5 -> "MD5"
| SHA1 -> "SHA1"
| SHA224 -> "SHA224"
| SHA256 -> "SHA256"
| SHA384 -> "SHA384"
| SHA512 -> "SHA512"
| Unknown x -> string_of_int x
let string_to_algorithm = function
| "RSASHA1" -> Ok RSA_SHA1
| "RSASHA1NSEC3SHA1" -> Ok RSASHA1_NSEC3_SHA1
| "RSASHA256" -> Ok RSA_SHA256
| "RSASHA512" -> Ok RSA_SHA512
| "ECDSAP256SHA256" -> Ok P256_SHA256
| "ECDSAP384SHA384" -> Ok P384_SHA384
| "ED25519" -> Ok ED25519
| "MD5" -> Ok MD5
| "SHA1" -> Ok SHA1
| "SHA224" -> Ok SHA224
| "SHA256" -> Ok SHA256
| "SHA384" -> Ok SHA384
| "SHA512" -> Ok SHA512
| x -> try Ok (Unknown (int_of_string x)) with
Failure _ -> Error (`Msg ("DNSKEY algorithm not implemented " ^ x))
let pp_algorithm ppf k = Fmt.string ppf (algorithm_to_string k)
let compare_algorithm a b =
Int.compare (algorithm_to_int a) (algorithm_to_int b)
type flag = [ `Zone | `Revoke | `Secure_entry_point ]
let bit = function
| `Zone -> 7
| `Revoke -> 8
| `Secure_entry_point -> 15
let all = [ `Zone ; `Revoke ; `Secure_entry_point ]
let compare_flag a b = match a, b with
| `Zone, `Zone -> 0 | `Zone, _ -> 1 | _, `Zone -> -1
| `Revoke, `Revoke -> 0 | `Revoke, _ -> 1 | _, `Revoke -> -1
| `Secure_entry_point, `Secure_entry_point -> 0
module F = Set.Make(struct type t = flag let compare = compare_flag end)
let pp_flag ppf = function
| `Zone -> Fmt.string ppf "zone"
| `Revoke -> Fmt.string ppf "revoke"
| `Secure_entry_point -> Fmt.string ppf "secure entry point"
let number f = 1 lsl (15 - bit f)
let decode_flags i =
List.fold_left (fun flags f ->
if number f land i > 0 then F.add f flags else flags)
F.empty all
let encode_flags f =
F.fold (fun f acc -> acc + number f) f 0
type t = {
flags : F.t ;
algorithm : algorithm ; (* u_int8_t *)
key : Cstruct.t ;
}
let compare a b =
andThen (F.compare a.flags b.flags)
(andThen (compare_algorithm a.algorithm b.algorithm)
(Cstruct.compare a.key b.key))
let decode_exn names buf ~off ~len =
let flags = Cstruct.BE.get_uint16 buf off
and proto = Cstruct.get_uint8 buf (off + 2)
and algo = Cstruct.get_uint8 buf (off + 3)
in
let* () =
guard (proto = 3)
(`Not_implemented (off + 2, Fmt.str "dnskey protocol 0x%x" proto))
in
let algorithm = int_to_algorithm algo in
let key = Cstruct.sub buf (off + 4) (len - 4) in
let flags = decode_flags flags in
Ok ({ flags ; algorithm ; key }, names, off + len)
let encode t names buf off =
let flags = encode_flags t.flags in
Cstruct.BE.set_uint16 buf off flags ;
Cstruct.set_uint8 buf (off + 2) 3 ;
Cstruct.set_uint8 buf (off + 3) (algorithm_to_int t.algorithm) ;
let kl = Cstruct.length t.key in
Cstruct.blit t.key 0 buf (off + 4) kl ;
names, off + 4 + kl
let key_tag t =
let data = Cstruct.create (4 + Cstruct.length t.key) in
let _names, _off = encode t Domain_name.Map.empty data 0 in
let rec go idx ac =
if idx >= Cstruct.length data then
(ac + (ac lsr 16) land 0xFFFF) land 0xFFFF
else
let b = Cstruct.get_uint8 data idx in
let lowest_bit_set = idx land 1 <> 0 in
let ac = ac + if lowest_bit_set then b else b lsl 8 in
go (succ idx) ac
in
go 0 0
let pp ppf t =
Fmt.pf ppf "DNSKEY flags %a algo %a key_tag %d key %a"
Fmt.(list ~sep:(any ", ") pp_flag) (F.elements t.flags)
pp_algorithm t.algorithm
(key_tag t)
Cstruct.hexdump_pp t.key
let digest_prep owner t =
let kl = Cstruct.length t.key in
let buf = Cstruct.create (kl + 255 + 4) in (* key length + max name + 4 *)
let names = Domain_name.Map.empty in
let _, off = Name.encode ~compress:false owner names buf 0 in
let _, off' = encode t names buf off in
Cstruct.sub buf 0 off'
let of_string key =
let parse algo key =
let key = Cstruct.of_string key in
let* algorithm = string_to_algorithm algo in
Ok { flags = F.empty ; algorithm ; key }
in
match String.split_on_char ':' key with
| [ algo ; key ] -> parse algo key
| _ -> Error (`Msg ("invalid DNSKEY string " ^ key))
let name_key_of_string str =
match String.split_on_char ':' str with
| name :: key ->
let* name = Domain_name.of_string name in
let* dnskey = of_string (String.concat ":" key) in
Ok (name, dnskey)
| [] -> Error (`Msg ("couldn't parse name:key in " ^ str))
let pp_name_key ppf (name, key) =
Fmt.pf ppf "%a %a" Domain_name.pp name pp key
end
(** RRSIG *)
module Rrsig = struct
type t = {
type_covered : int ;
algorithm : Dnskey.algorithm ;
label_count : int ;
original_ttl : int32 ;
signature_expiration : Ptime.t ;
signature_inception : Ptime.t ;
key_tag : int ;
signer_name : [ `raw ] Domain_name.t ;
signature : Cstruct.t
}
let canonical t =
{ t with signer_name = Domain_name.canonical t.signer_name }
let pp ppf t =
Fmt.pf ppf "RRSIG type covered %u algo %a labels %u original ttl %lu signature expiration %a signature inception %a key tag %u signer name %a signature %a"
t.type_covered
Dnskey.pp_algorithm t.algorithm
t.label_count t.original_ttl
(Ptime.pp_rfc3339 ()) t.signature_expiration
(Ptime.pp_rfc3339 ()) t.signature_inception
t.key_tag Domain_name.pp t.signer_name
Cstruct.hexdump_pp t.signature
let compare a b =
andThen (Int.compare a.type_covered b.type_covered)
(andThen (Dnskey.compare_algorithm a.algorithm b.algorithm)
(andThen (Int.compare a.label_count b.label_count)
(andThen (Int32.compare a.original_ttl b.original_ttl)
(andThen (Ptime.compare a.signature_expiration b.signature_expiration)
(andThen (Ptime.compare a.signature_inception b.signature_inception)
(andThen (Int.compare a.key_tag b.key_tag)
(andThen (Domain_name.compare a.signer_name b.signer_name)
(Cstruct.compare a.signature b.signature))))))))
let decode_exn names buf ~off ~len =
let type_covered = Cstruct.BE.get_uint16 buf off
and algo = Cstruct.get_uint8 buf (off + 2)
and label_count = Cstruct.get_uint8 buf (off + 3)
and original_ttl = Cstruct.BE.get_uint32 buf (off + 4)
and sig_exp = Cstruct.BE.get_uint32 buf (off + 8)
and sig_inc = Cstruct.BE.get_uint32 buf (off + 12)
and key_tag = Cstruct.BE.get_uint16 buf (off + 16)
in
let* signer_name, names, off' = Name.decode names buf ~off:(off + 18) in
let signature = Cstruct.sub buf off' (len - (off' - off)) in
let algorithm = Dnskey.int_to_algorithm algo in
(* sig_exp and sig_inc are supposed seconds since UNIX epoch (1970-01-01),
TODO but may only be +68years and -68years in respect to current timestamp *)
let* signature_expiration =
Ptime_extra.of_int64 ~off:(off + 8) (Int64.of_int32 sig_exp)
in
let* signature_inception =
Ptime_extra.of_int64 ~off:(off + 12) (Int64.of_int32 sig_inc)
in
Ok ({ type_covered ; algorithm ; label_count ; original_ttl ;
signature_expiration ; signature_inception ; key_tag ; signer_name ;
signature },
names, off + len)
let encode t names buf off =
Cstruct.BE.set_uint16 buf off t.type_covered ;
Cstruct.set_uint8 buf (off + 2) (Dnskey.algorithm_to_int t.algorithm) ;
Cstruct.set_uint8 buf (off + 3) t.label_count ;
Cstruct.BE.set_uint32 buf (off + 4) t.original_ttl ;
(* TODO +-68 years in respect to current timestamp *)
let int_s ts =
match Ptime_extra.to_int64 ts with
| None -> 0l
| Some s ->
if Int64.(s > of_int32 Int32.min_int && s < of_int32 Int32.max_int) then
Int64.to_int32 s
else
0l
in
Cstruct.BE.set_uint32 buf (off + 8) (int_s t.signature_expiration) ;
Cstruct.BE.set_uint32 buf (off + 12) (int_s t.signature_inception) ;
Cstruct.BE.set_uint16 buf (off + 16) t.key_tag ;
let names, off = Name.encode ~compress:false t.signer_name names buf (off + 18) in
let slen = Cstruct.length t.signature in