PageRenderTime 22ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/collects/net/dns-unit.rkt

http://github.com/gmarceau/PLT
Racket | 338 lines | 289 code | 36 blank | 13 comment | 48 complexity | c62a38dfcae3a0bdc329c9569d8826e2 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
  1. #lang racket/unit
  2. (require "dns-sig.rkt" racket/system racket/udp)
  3. (import)
  4. (export dns^)
  5. ;; UDP retry timeout:
  6. (define INIT-TIMEOUT 50)
  7. (define types
  8. '((a 1)
  9. (ns 2)
  10. (md 3)
  11. (mf 4)
  12. (cname 5)
  13. (soa 6)
  14. (mb 7)
  15. (mg 8)
  16. (mr 9)
  17. (null 10)
  18. (wks 11)
  19. (ptr 12)
  20. (hinfo 13)
  21. (minfo 14)
  22. (mx 15)
  23. (txt 16)))
  24. (define classes
  25. '((in 1)
  26. (cs 2)
  27. (ch 3)
  28. (hs 4)))
  29. (define (cossa i l)
  30. (cond [(null? l) #f]
  31. [(equal? (cadar l) i) (car l)]
  32. [else (cossa i (cdr l))]))
  33. (define (number->octet-pair n)
  34. (list (arithmetic-shift n -8)
  35. (modulo n 256)))
  36. (define (octet-pair->number a b)
  37. (+ (arithmetic-shift a 8) b))
  38. (define (octet-quad->number a b c d)
  39. (+ (arithmetic-shift a 24)
  40. (arithmetic-shift b 16)
  41. (arithmetic-shift c 8)
  42. d))
  43. (define (name->octets s)
  44. (let ([do-one (lambda (s) (cons (bytes-length s) (bytes->list s)))])
  45. (let loop ([s s])
  46. (let ([m (regexp-match #rx#"^([^.]*)[.](.*)" s)])
  47. (if m
  48. (append (do-one (cadr m)) (loop (caddr m)))
  49. (append (do-one s) (list 0)))))))
  50. (define (make-std-query-header id question-count)
  51. (append (number->octet-pair id)
  52. (list 1 0) ; Opcode & flags (recusive flag set)
  53. (number->octet-pair question-count)
  54. (number->octet-pair 0)
  55. (number->octet-pair 0)
  56. (number->octet-pair 0)))
  57. (define (make-query id name type class)
  58. (append (make-std-query-header id 1)
  59. (name->octets name)
  60. (number->octet-pair (cadr (assoc type types)))
  61. (number->octet-pair (cadr (assoc class classes)))))
  62. (define (add-size-tag m)
  63. (append (number->octet-pair (length m)) m))
  64. (define (rr-data rr)
  65. (cadddr (cdr rr)))
  66. (define (rr-type rr)
  67. (cadr rr))
  68. (define (rr-name rr)
  69. (car rr))
  70. (define (parse-name start reply)
  71. (let ([v (car start)])
  72. (cond
  73. [(zero? v)
  74. ;; End of name
  75. (values #f (cdr start))]
  76. [(zero? (bitwise-and #xc0 v))
  77. ;; Normal label
  78. (let loop ([len v][start (cdr start)][accum null])
  79. (if (zero? len)
  80. (let-values ([(s start) (parse-name start reply)])
  81. (let ([s0 (list->bytes (reverse accum))])
  82. (values (if s (bytes-append s0 #"." s) s0)
  83. start)))
  84. (loop (sub1 len) (cdr start) (cons (car start) accum))))]
  85. [else
  86. ;; Compression offset
  87. (let ([offset (+ (arithmetic-shift (bitwise-and #x3f v) 8)
  88. (cadr start))])
  89. (let-values ([(s ignore-start)
  90. (parse-name (list-tail reply offset) reply)])
  91. (values s (cddr start))))])))
  92. (define (parse-rr start reply)
  93. (let-values ([(name start) (parse-name start reply)])
  94. (let* ([type (car (cossa (octet-pair->number (car start) (cadr start))
  95. types))]
  96. [start (cddr start)]
  97. ;;
  98. [class (car (cossa (octet-pair->number (car start) (cadr start))
  99. classes))]
  100. [start (cddr start)]
  101. ;;
  102. [ttl (octet-quad->number (car start) (cadr start)
  103. (caddr start) (cadddr start))]
  104. [start (cddddr start)]
  105. ;;
  106. [len (octet-pair->number (car start) (cadr start))]
  107. [start (cddr start)])
  108. ;; Extract next len bytes for data:
  109. (let loop ([len len] [start start] [accum null])
  110. (if (zero? len)
  111. (values (list name type class ttl (reverse accum))
  112. start)
  113. (loop (sub1 len) (cdr start) (cons (car start) accum)))))))
  114. (define (parse-ques start reply)
  115. (let-values ([(name start) (parse-name start reply)])
  116. (let* ([type (car (cossa (octet-pair->number (car start) (cadr start))
  117. types))]
  118. [start (cddr start)]
  119. ;;
  120. [class (car (cossa (octet-pair->number (car start) (cadr start))
  121. classes))]
  122. [start (cddr start)])
  123. (values (list name type class) start))))
  124. (define (parse-n parse start reply n)
  125. (let loop ([n n][start start][accum null])
  126. (if (zero? n)
  127. (values (reverse accum) start)
  128. (let-values ([(rr start) (parse start reply)])
  129. (loop (sub1 n) start (cons rr accum))))))
  130. (define (dns-query nameserver addr type class)
  131. (unless (assoc type types)
  132. (raise-type-error 'dns-query "DNS query type" type))
  133. (unless (assoc class classes)
  134. (raise-type-error 'dns-query "DNS query class" class))
  135. (let* ([query (make-query (random 256) (string->bytes/latin-1 addr)
  136. type class)]
  137. [udp (udp-open-socket)]
  138. [reply
  139. (dynamic-wind
  140. void
  141. (lambda ()
  142. (let ([s (make-bytes 512)])
  143. (let retry ([timeout INIT-TIMEOUT])
  144. (udp-send-to udp nameserver 53 (list->bytes query))
  145. (sync (handle-evt (udp-receive!-evt udp s)
  146. (lambda (r)
  147. (bytes->list (subbytes s 0 (car r)))))
  148. (handle-evt (alarm-evt (+ (current-inexact-milliseconds)
  149. timeout))
  150. (lambda (v)
  151. (retry (* timeout 2))))))))
  152. (lambda () (udp-close udp)))])
  153. ;; First two bytes must match sent message id:
  154. (unless (and (= (car reply) (car query))
  155. (= (cadr reply) (cadr query)))
  156. (error 'dns-query "bad reply id from server"))
  157. (let ([v0 (caddr reply)]
  158. [v1 (cadddr reply)])
  159. ;; Check for error code:
  160. (let ([rcode (bitwise-and #xf v1)])
  161. (unless (zero? rcode)
  162. (error 'dns-query "error from server: ~a"
  163. (case rcode
  164. [(1) "format error"]
  165. [(2) "server failure"]
  166. [(3) "name error"]
  167. [(4) "not implemented"]
  168. [(5) "refused"]))))
  169. (let ([qd-count (octet-pair->number (list-ref reply 4) (list-ref reply 5))]
  170. [an-count (octet-pair->number (list-ref reply 6) (list-ref reply 7))]
  171. [ns-count (octet-pair->number (list-ref reply 8) (list-ref reply 9))]
  172. [ar-count (octet-pair->number (list-ref reply 10) (list-ref reply 11))])
  173. (let ([start (list-tail reply 12)])
  174. (let*-values ([(qds start) (parse-n parse-ques start reply qd-count)]
  175. [(ans start) (parse-n parse-rr start reply an-count)]
  176. [(nss start) (parse-n parse-rr start reply ns-count)]
  177. [(ars start) (parse-n parse-rr start reply ar-count)])
  178. (unless (null? start)
  179. (error 'dns-query "error parsing server reply"))
  180. (values (positive? (bitwise-and #x4 v0))
  181. qds ans nss ars reply)))))))
  182. (define cache (make-hasheq))
  183. (define (dns-query/cache nameserver addr type class)
  184. (let ([key (string->symbol (format "~a;~a;~a;~a" nameserver addr type class))])
  185. (let ([v (hash-ref cache key (lambda () #f))])
  186. (if v
  187. (apply values v)
  188. (let-values ([(auth? qds ans nss ars reply)
  189. (dns-query nameserver addr type class)])
  190. (hash-set! cache key (list auth? qds ans nss ars reply))
  191. (values auth? qds ans nss ars reply))))))
  192. (define (ip->string s)
  193. (format "~a.~a.~a.~a"
  194. (list-ref s 0) (list-ref s 1) (list-ref s 2) (list-ref s 3)))
  195. (define (try-forwarding k nameserver)
  196. (let loop ([nameserver nameserver][tried (list nameserver)])
  197. ;; Normally the recusion is done for us, but it's technically optional
  198. (let-values ([(v ars auth?) (k nameserver)])
  199. (or v
  200. (and (not auth?)
  201. (let* ([ns (ormap (lambda (ar)
  202. (and (eq? (rr-type ar) 'a)
  203. (ip->string (rr-data ar))))
  204. ars)])
  205. (and ns
  206. (not (member ns tried))
  207. (loop ns (cons ns tried)))))))))
  208. (define (ip->in-addr.arpa ip)
  209. (let ([result (regexp-match #rx"^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)$"
  210. ip)])
  211. (format "~a.~a.~a.~a.in-addr.arpa"
  212. (list-ref result 4)
  213. (list-ref result 3)
  214. (list-ref result 2)
  215. (list-ref result 1))))
  216. (define (get-ptr-list-from-ans ans)
  217. (filter (lambda (ans-entry) (eq? (list-ref ans-entry 1) 'ptr)) ans))
  218. (define (dns-get-name nameserver ip)
  219. (or (try-forwarding
  220. (lambda (nameserver)
  221. (let-values ([(auth? qds ans nss ars reply)
  222. (dns-query/cache nameserver (ip->in-addr.arpa ip) 'ptr 'in)])
  223. (values (and (positive? (length (get-ptr-list-from-ans ans)))
  224. (let ([s (rr-data (car (get-ptr-list-from-ans ans)))])
  225. (let-values ([(name null) (parse-name s reply)])
  226. (bytes->string/latin-1 name))))
  227. ars auth?)))
  228. nameserver)
  229. (error 'dns-get-name "bad ip address")))
  230. (define (get-a-list-from-ans ans)
  231. (filter (lambda (ans-entry) (eq? (list-ref ans-entry 1) 'a))
  232. ans))
  233. (define (dns-get-address nameserver addr)
  234. (or (try-forwarding
  235. (lambda (nameserver)
  236. (let-values ([(auth? qds ans nss ars reply) (dns-query/cache nameserver addr 'a 'in)])
  237. (values (and (positive? (length (get-a-list-from-ans ans)))
  238. (let ([s (rr-data (car (get-a-list-from-ans ans)))])
  239. (ip->string s)))
  240. ars auth?)))
  241. nameserver)
  242. (error 'dns-get-address "bad address")))
  243. (define (dns-get-mail-exchanger nameserver addr)
  244. (or (try-forwarding
  245. (lambda (nameserver)
  246. (let-values ([(auth? qds ans nss ars reply) (dns-query/cache nameserver addr 'mx 'in)])
  247. (values (let loop ([ans ans][best-pref +inf.0][exchanger #f])
  248. (cond
  249. [(null? ans)
  250. (or exchanger
  251. ;; Does 'soa mean that the input address is fine?
  252. (and (ormap (lambda (ns) (eq? (rr-type ns) 'soa))
  253. nss)
  254. addr))]
  255. [else
  256. (let ([d (rr-data (car ans))])
  257. (let ([pref (octet-pair->number (car d) (cadr d))])
  258. (if (< pref best-pref)
  259. (let-values ([(name start) (parse-name (cddr d) reply)])
  260. (loop (cdr ans) pref name))
  261. (loop (cdr ans) best-pref exchanger))))]))
  262. ars auth?)))
  263. nameserver)
  264. (error 'dns-get-mail-exchanger "bad address")))
  265. (define (dns-find-nameserver)
  266. (case (system-type)
  267. [(unix macosx)
  268. (with-handlers ([void (lambda (x) #f)])
  269. (with-input-from-file "/etc/resolv.conf"
  270. (lambda ()
  271. (let loop ()
  272. (let ([l (read-line)])
  273. (or (and (string? l)
  274. (let ([m (regexp-match
  275. #rx"nameserver[ \t]+([0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)"
  276. l)])
  277. (and m (cadr m))))
  278. (and (not (eof-object? l))
  279. (loop))))))))]
  280. [(windows)
  281. (let ([nslookup (find-executable-path "nslookup.exe" #f)])
  282. (and nslookup
  283. (let-values ([(pin pout pid perr proc)
  284. (apply
  285. values
  286. (process/ports
  287. #f (open-input-file "NUL") (current-error-port)
  288. nslookup))])
  289. (let loop ([name #f] [ip #f] [try-ip? #f])
  290. (let ([line (read-line pin 'any)])
  291. (cond [(eof-object? line)
  292. (close-input-port pin)
  293. (proc 'wait)
  294. (or ip name)]
  295. [(and (not name)
  296. (regexp-match #rx"^Default Server: +(.*)$" line))
  297. => (lambda (m) (loop (cadr m) #f #t))]
  298. [(and try-ip?
  299. (regexp-match #rx"^Address: +(.*)$" line))
  300. => (lambda (m) (loop name (cadr m) #f))]
  301. [else (loop name ip #f)]))))))]
  302. [else #f]))