summaryrefslogtreecommitdiff
path: root/lisp/net/dns.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/net/dns.el')
-rw-r--r--lisp/net/dns.el284
1 files changed, 174 insertions, 110 deletions
diff --git a/lisp/net/dns.el b/lisp/net/dns.el
index cefe0851f03..c368cd773c2 100644
--- a/lisp/net/dns.el
+++ b/lisp/net/dns.el
@@ -1,4 +1,4 @@
-;;; dns.el --- Domain Name Service lookups
+;;; dns.el --- Domain Name Service lookups -*- lexical-binding:t -*-
;; Copyright (C) 2002-2020 Free Software Foundation, Inc.
@@ -24,6 +24,8 @@
;;; Code:
+(require 'cl-lib)
+
(defvar dns-timeout 5
"How many seconds to wait when doing DNS queries.")
@@ -73,7 +75,7 @@ updated. Set this variable to t to disable the check.")
(defun dns-write-bytes (value &optional length)
(let (bytes)
- (dotimes (i (or length 1))
+ (dotimes (_ (or length 1))
(push (% value 256) bytes)
(setq value (/ value 256)))
(dolist (byte bytes)
@@ -81,7 +83,7 @@ updated. Set this variable to t to disable the check.")
(defun dns-read-bytes (length)
(let ((value 0))
- (dotimes (i length)
+ (dotimes (_ length)
(setq value (logior (* value 256) (following-char)))
(forward-char 1))
value))
@@ -138,7 +140,7 @@ updated. Set this variable to t to disable the check.")
(defun dns-write (spec &optional tcp-p)
"Write a DNS packet according to SPEC.
-If TCP-P, the first two bytes of the package with be the length field."
+If TCP-P, the first two bytes of the packet will be the length field."
(with-temp-buffer
(set-buffer-multibyte nil)
(dns-write-bytes (dns-get 'id spec) 2)
@@ -189,13 +191,15 @@ If TCP-P, the first two bytes of the package with be the length field."
(dns-write-bytes (buffer-size) 2))
(buffer-string)))
-(defun dns-read (packet)
+(defun dns-read (packet &optional tcp-p)
(with-temp-buffer
(set-buffer-multibyte nil)
(let ((spec nil)
queries answers authorities additionals)
(insert packet)
- (goto-char (point-min))
+ ;; When using TCP we have a 2 byte length field to ignore.
+ (goto-char (+ (point-min)
+ (if tcp-p 2 0)))
(push (list 'id (dns-read-bytes 2)) spec)
(let ((byte (dns-read-bytes 1)))
(push (list 'response-p (if (zerop (logand byte (ash 1 7))) nil t))
@@ -227,7 +231,7 @@ If TCP-P, the first two bytes of the package with be the length field."
(setq authorities (dns-read-bytes 2))
(setq additionals (dns-read-bytes 2))
(let ((qs nil))
- (dotimes (i queries)
+ (dotimes (_ queries)
(push (list (dns-read-name)
(list 'type (dns-inverse-get (dns-read-bytes 2)
dns-query-types))
@@ -235,33 +239,36 @@ If TCP-P, the first two bytes of the package with be the length field."
dns-classes)))
qs))
(push (list 'queries qs) spec))
- (dolist (slot '(answers authorities additionals))
- (let ((qs nil)
- type)
- (dotimes (i (symbol-value slot))
- (push (list (dns-read-name)
- (list 'type
- (setq type (dns-inverse-get (dns-read-bytes 2)
- dns-query-types)))
- (list 'class (dns-inverse-get (dns-read-bytes 2)
- dns-classes))
- (list 'ttl (dns-read-bytes 4))
- (let ((length (dns-read-bytes 2)))
- (list 'data
- (dns-read-type
- (buffer-substring
- (point)
- (progn (forward-char length) (point)))
- type))))
- qs))
- (push (list slot qs) spec)))
+ (cl-loop for (slot length) in `((answers ,answers)
+ (authorities ,authorities)
+ (additionals ,additionals))
+ do (let ((qs nil)
+ type)
+ (dotimes (_ length)
+ (push (list (dns-read-name)
+ (list 'type
+ (setq type (dns-inverse-get
+ (dns-read-bytes 2)
+ dns-query-types)))
+ (list 'class (dns-inverse-get
+ (dns-read-bytes 2)
+ dns-classes))
+ (list 'ttl (dns-read-bytes 4))
+ (let ((length (dns-read-bytes 2)))
+ (list 'data
+ (dns-read-type
+ (buffer-substring
+ (point)
+ (progn (forward-char length)
+ (point)))
+ type))))
+ qs))
+ (push (list slot qs) spec)))
(nreverse spec))))
(defun dns-read-int32 ()
- ;; Full 32 bit Integers can't be handled by 32-bit Emacsen. If we
- ;; use floats, it works.
- (format "%.0f" (+ (* (dns-read-bytes 1) 16777216.0)
- (dns-read-bytes 3))))
+ (declare (obsolete nil "28.1"))
+ (number-to-string (dns-read-bytes 4)))
(defun dns-read-type (string type)
(let ((buffer (current-buffer))
@@ -274,23 +281,23 @@ If TCP-P, the first two bytes of the package with be the length field."
(cond
((eq type 'A)
(let ((bytes nil))
- (dotimes (i 4)
+ (dotimes (_ 4)
(push (dns-read-bytes 1) bytes))
(mapconcat 'number-to-string (nreverse bytes) ".")))
((eq type 'AAAA)
(let (hextets)
- (dotimes (i 8)
+ (dotimes (_ 8)
(push (dns-read-bytes 2) hextets))
(mapconcat (lambda (n) (format "%x" n))
(nreverse hextets) ":")))
((eq type 'SOA)
(list (list 'mname (dns-read-name buffer))
(list 'rname (dns-read-name buffer))
- (list 'serial (dns-read-int32))
- (list 'refresh (dns-read-int32))
- (list 'retry (dns-read-int32))
- (list 'expire (dns-read-int32))
- (list 'minimum (dns-read-int32))))
+ (list 'serial (dns-read-bytes 4))
+ (list 'refresh (dns-read-bytes 4))
+ (list 'retry (dns-read-bytes 4))
+ (list 'expire (dns-read-bytes 4))
+ (list 'minimum (dns-read-bytes 4))))
((eq type 'SRV)
(list (list 'priority (dns-read-bytes 2))
(list 'weight (dns-read-bytes 2))
@@ -309,16 +316,14 @@ If TCP-P, the first two bytes of the package with be the length field."
"Return false if we need to recheck the list of DNS servers."
(and dns-servers
(or (eq dns-servers-valid-for-interfaces t)
- ;; `network-interface-list' was introduced in Emacs 22.1.
- (not (fboundp 'network-interface-list))
(equal dns-servers-valid-for-interfaces
(network-interface-list)))))
(defun dns-set-servers ()
"Set `dns-servers' to a list of DNS servers or nil if none are found.
Parses \"/etc/resolv.conf\" or calls \"nslookup\"."
+ (setq dns-servers nil)
(or (when (file-exists-p "/etc/resolv.conf")
- (setq dns-servers nil)
(with-temp-buffer
(insert-file-contents "/etc/resolv.conf")
(goto-char (point-min))
@@ -329,11 +334,10 @@ Parses \"/etc/resolv.conf\" or calls \"nslookup\"."
(with-temp-buffer
(call-process "nslookup" nil t nil "localhost")
(goto-char (point-min))
- (re-search-forward
- "^Address:[ \t]*\\([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\\)" nil t)
- (setq dns-servers (list (match-string 1))))))
- (when (fboundp 'network-interface-list)
- (setq dns-servers-valid-for-interfaces (network-interface-list))))
+ (when (re-search-forward
+ "^Address:[ \t]*\\([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\\|[[:xdigit:]:]*\\)" nil t)
+ (setq dns-servers (list (match-string 1)))))))
+ (setq dns-servers-valid-for-interfaces (network-interface-list)))
(defun dns-read-txt (string)
(if (> (length string) 1)
@@ -355,23 +359,6 @@ Parses \"/etc/resolv.conf\" or calls \"nslookup\"."
result))
;;; Interface functions.
-(defmacro dns-make-network-process (server)
- `(let ((server ,server)
- (coding-system-for-read 'binary)
- (coding-system-for-write 'binary))
- (if (fboundp 'make-network-process)
- (make-network-process
- :name "dns"
- :coding 'binary
- :buffer (current-buffer)
- :host server
- :service "domain"
- :type 'datagram)
- ;; Older versions of Emacs doesn't have
- ;; `make-network-process', so we fall back on opening a TCP
- ;; connection to the DNS server.
- (open-network-stream "dns" (current-buffer) server "domain"))))
-
(defvar dns-cache (make-vector 4096 0))
(defun dns-query-cached (name &optional type fullp reversep)
@@ -384,64 +371,141 @@ Parses \"/etc/resolv.conf\" or calls \"nslookup\"."
(set (intern key dns-cache) result)
result))))
-;; The old names `query-dns' and `query-dns-cached' weren't used in Emacs 23
-;; yet, so no alias are provided. --rsteib
-
-(defun dns-query (name &optional type fullp reversep)
+(defun dns-query-asynchronous (name callback &optional type full reverse)
"Query a DNS server for NAME of TYPE.
-If FULLP, return the entire record returned.
-If REVERSEP, look up an IP address."
+CALLBACK will be called with a single parameter: The result.
+
+If there's no result, or `dns-timeout' has passed, CALLBACK will
+be called with nil as the parameter.
+
+If FULL, return the entire record.
+If REVERSE, look up an IP address."
(setq type (or type 'A))
(unless (dns-servers-up-to-date-p)
(dns-set-servers))
- (when reversep
+ (when reverse
(setq name (concat
(mapconcat 'identity (nreverse (split-string name "\\.")) ".")
".in-addr.arpa")
type 'PTR))
(if (not dns-servers)
- (message "No DNS server configuration found")
- (with-temp-buffer
- (set-buffer-multibyte nil)
- (let ((process (condition-case ()
- (dns-make-network-process (car dns-servers))
- (error
- (message
- "dns: Got an error while trying to talk to %s"
- (car dns-servers))
- nil)))
- (step 100)
- (times (* dns-timeout 1000))
- (id (random 65000)))
- (when process
- (process-send-string
- process
- (dns-write `((id ,id)
- (opcode query)
- (queries ((,name (type ,type))))
- (recursion-desired-p t))))
- (while (and (zerop (buffer-size))
- (> times 0))
- (let ((step-sec (/ step 1000.0)))
- (sit-for step-sec)
- (accept-process-output process step-sec))
- (setq times (- times step)))
- (condition-case nil
- (delete-process process)
- (error nil))
- (when (and (>= (buffer-size) 2)
- ;; We had a time-out.
- (> times 0))
- (let ((result (dns-read (buffer-string))))
- (if fullp
- result
- (let ((answer (car (dns-get 'answers result))))
- (when (eq type (dns-get 'type answer))
- (if (eq type 'TXT)
- (dns-get-txt-answer (dns-get 'answers result))
- (dns-get 'data answer))))))))))))
+ (progn
+ (message "No DNS server configuration found")
+ nil)
+ (dns--lookup name callback type full)))
+
+(defun dns--lookup (name callback type full)
+ (with-current-buffer (generate-new-buffer " *dns*")
+ (set-buffer-multibyte nil)
+ (let* ((tcp nil)
+ (process
+ (condition-case ()
+ (let ((server (car dns-servers))
+ (coding-system-for-read 'binary)
+ (coding-system-for-write 'binary))
+ (if (featurep 'make-network-process '(:type datagram))
+ (make-network-process
+ :name "dns"
+ :coding 'binary
+ :buffer (current-buffer)
+ :host server
+ :service "domain"
+ :type 'datagram)
+ ;; On MS-Windows datagram sockets are not
+ ;; supported, so we fall back on opening a TCP
+ ;; connection to the DNS server.
+ (progn
+ (setq tcp t)
+ (open-network-stream "dns" (current-buffer)
+ server "domain"))))
+ (error
+ (message
+ "dns: Got an error while trying to talk to %s"
+ (car dns-servers))
+ nil)))
+ (triggered nil)
+ (buffer (current-buffer))
+ timer)
+ (if (not process)
+ (progn
+ (kill-buffer buffer)
+ (funcall callback nil))
+ ;; Call the callback if we don't get any response at all.
+ (setq timer (run-at-time dns-timeout nil
+ (lambda ()
+ (unless triggered
+ (setq triggered t)
+ (delete-process process)
+ (kill-buffer buffer)
+ (funcall callback nil)))))
+ (process-send-string
+ process
+ (dns-write `((id ,(random 65000))
+ (opcode query)
+ (queries ((,name (type ,type))))
+ (recursion-desired-p t))
+ tcp))
+ (set-process-filter
+ process
+ (lambda (process string)
+ (with-current-buffer (process-buffer process)
+ (goto-char (point-max))
+ (insert string)
+ (goto-char (point-min))
+ ;; If this is DNS, then we always get the full data in
+ ;; one packet. If it's TCP, we may only get part of the
+ ;; data, but the first two bytes says how long the data
+ ;; is supposed to be.
+ (when (or (not tcp)
+ (>= (buffer-size) (dns-read-bytes 2)))
+ (setq triggered t)
+ (cancel-timer timer)
+ (dns--filter process callback type full tcp)))))
+ ;; In case we the process is deleted for some reason, then do
+ ;; a failure callback.
+ (set-process-sentinel
+ process
+ (lambda (_ state)
+ (when (and (eq state 'deleted)
+ ;; Ensure we don't trigger this callback twice.
+ (not triggered))
+ (setq triggered t)
+ (cancel-timer timer)
+ (kill-buffer buffer)
+ (funcall callback nil))))))))
+
+(defun dns--filter (process callback type full tcp)
+ (let ((message (buffer-string)))
+ (when (process-live-p process)
+ (delete-process process))
+ (kill-buffer (current-buffer))
+ (when (>= (length message) 2)
+ (let ((result (dns-read message tcp)))
+ (funcall callback
+ (if full
+ result
+ (let ((answer (car (dns-get 'answers result))))
+ (when (eq type (dns-get 'type answer))
+ (if (eq type 'TXT)
+ (dns-get-txt-answer (dns-get 'answers result))
+ (dns-get 'data answer))))))))))
+
+(defun dns-query (name &optional type full reverse)
+ "Query a DNS server for NAME of TYPE.
+If FULL, return the entire record returned.
+If REVERSE, look up an IP address."
+ (let ((result nil))
+ (dns-query-asynchronous
+ name
+ (lambda (response)
+ (setq result (list response)))
+ type full reverse)
+ ;; Loop until we get the callback.
+ (while (not result)
+ (sleep-for 0.01))
+ (car result)))
(provide 'dns)