commit 9b5f5db7cd18e0678d4591dc889b200b717c8cc2
parent df145908860c79bb9ac51bab06456d3860a69a7f
Author: Tomas Hlavaty <tom@logand.com>
Date: Sat, 1 Nov 2014 23:31:11 +0100
dns added
Diffstat:
M | cl-rw.asd | | | 4 | +++- |
A | dns.lisp | | | 212 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | socket.lisp | | | 25 | +++++++++++-------------- |
A | wire.lisp | | | 249 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 475 insertions(+), 15 deletions(-)
diff --git a/cl-rw.asd b/cl-rw.asd
@@ -48,5 +48,7 @@
(:file "cas")
(:file "zip")
(:file "der")
- (:file "tls-macros")
+ (:file "wire")
+ (:file "dns")
+ (:file "tls-macros") ;; TODO use wire!
(:file "tls")))
diff --git a/dns.lisp b/dns.lisp
@@ -0,0 +1,212 @@
+;;; Copyright (C) 2014 Tomas Hlavaty <tom@logand.com>
+;;;
+;;; Permission is hereby granted, free of charge, to any person
+;;; obtaining a copy of this software and associated documentation
+;;; files (the "Software"), to deal in the Software without
+;;; restriction, including without limitation the rights to use, copy,
+;;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;;; of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+;;;
+;;; The above copyright notice and this permission notice shall be
+;;; included in all copies or substantial portions of the Software.
+;;;
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+;;; DEALINGS IN THE SOFTWARE.
+
+(defpackage :rw.dns
+ (:use :cl)
+ (:export :dns-query))
+
+(in-package :rw.dns)
+
+;;https://www.ietf.org/rfc/rfc1035.txt
+
+(defvar *name-from-position*)
+
+(defun next-$name (reader) ;; TODO encoding?
+ (with-output-to-string (s)
+ (flet ((next ()
+ (rw:next-u8 reader)))
+ (loop
+ for n = (next)
+ for i from 0
+ while (plusp n)
+ do (progn
+ (when (plusp i)
+ (write-char #\. s))
+ (cond
+ ((< n 64)
+ (dotimes (i n)
+ (let ((n (next)))
+ (assert (<= 1 n 127))
+ (write-char (code-char n) s))))
+ (t
+ (assert (= #xc0 (logand #xc0 n)))
+ (write-string
+ (funcall
+ *name-from-position*
+ (logior (ash (logand #x3f n) 8) (next)))
+ s)
+ (return))))))))
+
+;;(next-$name (rw:reader #(3 109 120 49 6 108 111 103 97 110 100 3 99 111 109 0)))
+
+(defun write-$name (writer x) ;; TODO encoding?
+ (let ((r (rw:peek-reader (rw:reader x))))
+ (loop
+ for y = (rw:till r '(#\.))
+ for i from 0
+ while (progn
+ (rw:next r)
+ (rw:write-u8 writer (length y))
+ y)
+ do (dolist (e y)
+ (let ((n (char-code e)))
+ (assert (<= 1 n 127))
+ (rw:write-u8 writer n))))))
+
+#+nil
+(let ((b (rw.wire::make-octet-buffer 42)))
+ (write-$name (rw:writer b) "mx1.logand.com")
+ (values b (next-$name (rw:reader b))))
+
+(defun next-$ipv4-address (reader)
+ (vector (rw:next-u8 reader)
+ (rw:next-u8 reader)
+ (rw:next-u8 reader)
+ (rw:next-u8 reader)))
+
+(defun write-$ipv4-address (writer x)
+ (assert (= 4 (length x)))
+ (map nil (lambda (x) (rw:write-u8 writer x)) x))
+
+(rw.wire:defenum $resource-type (:nbits 16)
+ (A . 1)
+ (NS . 2)
+ (MD . 3)
+ (MF . 4)
+ (CNAME . 5)
+ (SOA . 6)
+ (MB . 7)
+ (MG . 8)
+ (MR . 9)
+ (NULL . 10)
+ (WKS . 11)
+ (PTR . 12)
+ (HINFO . 13)
+ (MINFO . 14)
+ (MX . 15)
+ (TXT . 16)
+ (SRV . 33)
+ (OPT . 41)
+ (IXFR . 251)
+ (AXFR . 252)
+ (MAILB . 253)
+ (MAILA . 254)
+ (ALL . 255))
+
+(rw.wire:defenum $resource-class (:nbits 16)
+ (IN . 1)
+ (CS . 2)
+ (CH . 3)
+ (HS . 4))
+
+(rw.wire:defstruc $question ()
+ ($name name)
+ ($resource-type type)
+ ($resource-class class))
+
+(rw.wire:defstruc $mx-rdata ()
+ (rw.wire:u16 preference)
+ ($name name))
+
+(rw.wire:defstruc $resource ()
+ ($name name)
+ ($resource-type type)
+ ($resource-class class)
+ (rw.wire:u32 ttl)
+ #+nil(rw.wire:u8 data :length rw.wire:u16)
+ ((ecase type
+ (A $ipv4-address)
+ (CNAME $name)
+ (NS $name)
+ (MX $mx-rdata))
+ data :length rw.wire:u16))
+
+(rw.wire:defstruc $message ()
+ (rw.wire:u16 tid)
+ (rw.wire:u16 flags)
+ (rw.wire:u16 nquestion)
+ (rw.wire:u16 nanswer)
+ (rw.wire:u16 nauthority)
+ (rw.wire:u16 nadditional)
+ ($question question :size nquestion)
+ ($resource answer :size nanswer)
+ ($resource authority :size nauthority)
+ ($resource additional :size nadditional))
+
+(defun udp-query (hostname server &key (type 'A) (class 'IN) (port 53))
+ (let ((n 512) ;; TODO minus IP/UDP headers
+ (s (rw.socket::make-udp-socket server port)))
+ (unwind-protect
+ (let ((b (rw.wire::make-octet-buffer n)))
+ ;;(sb-bsd-sockets:socket-connect s server port)
+ (write-$message
+ (rw:writer b)
+ (make-$message
+ :tid #x3141 #+nil(random 65536)
+ :flags #x100 ;; std query TODO flags
+ :nquestion 1
+ :nanswer 0
+ :nauthority 0
+ :nadditional 0
+ :question (list (make-$question :name hostname
+ :type type
+ :class class))
+ :answer nil
+ :authority nil
+ :additional nil))
+ (assert (<= (length b) n))
+ (sb-bsd-sockets:socket-send s b (length b) :address (list server port))
+ (setf (fill-pointer b) n)
+ (multiple-value-bind (buf len addr)
+ (sb-bsd-sockets:socket-receive s b n)
+ (declare (ignore addr))
+ ;;(print (list ::@@@ (subseq buf 0 len)))
+ (flet ((cb (n)
+ (let (*name-from-position*)
+ (next-$name
+ (rw:skip (rw:reader buf) n)))))
+ (let ((*name-from-position* #'cb))
+ (next-$message
+ (rw:shorter-reader (rw:reader buf) len))))))
+ (sb-bsd-sockets:socket-close s))))
+
+;;(print (udp-query "mx1.logand.com" #(8 8 8 8)))
+;;(print (udp-query "seznam.cz" #(8 8 8 8)))
+;;(print (udp-query "seznam.cz" #(192 168 1 1)))
+;;(print (udp-query "www.google.com" #(8 8 8 8)))
+
+;;(print (udp-query "mx1.logand.com" #(8 8 8 8) :type 'NS))
+
+;;(print (udp-query "mx1.logand.com" #(8 8 8 8) :type 'MX))
+;;(print (udp-query "seznam.cz" #(8 8 8 8) :type 'MX))
+;;(print (udp-query "www.google.com" #(8 8 8 8) :type 'MX)) ;; TODO SOA follow authoritative nameservers
+
+#+nil ;; TODO dns over tcp doesnt seem to work, depends on server?
+(defun tcp-query (hostname server &key (port 53))
+ (with-open-stream (s (rw.socket:make-tcp-client-socket server port))
+ (let ((w (rw.wire:packet-writer s)))
+ (write-dns-question-packet w hostname)
+ (rw.wire:flush w)
+ (rw:next-u8 (rw:byte-reader s)))))
+
+;;(dns-query "seznam.cz" "8.8.8.8")
+;;(dns-query "seznam.cz" "192.168.1.1")
diff --git a/socket.lisp b/socket.lisp
@@ -68,26 +68,23 @@
:remote-host host
:remote-port port))
-(defun make-passive-udp-socket (host port)
- #-ccl
+(defun make-udp-socket (host port &key remote-host remote-port) ;; TODO understand
+ #-(or ccl sbcl)
(error "TODO port RW.SOCKET:MAKE-PASSIVE-UDP-SOCKET")
#+ccl
- (ccl:make-socket :connect :passive
+ (ccl:make-socket ;;:connect :passive
:address-family :internet
:type :datagram
- :local-host host
+ :local-host remote-host
:local-port port
- :reuse-address t))
+ :remote-host remote-host
+ :remote-port remote-port
+ :reuse-address t)
+ #+sbcl
+ (make-instance 'sb-bsd-sockets:inet-socket
+ :type :datagram
+ :protocol :udp))
-(defun make-active-udp-socket (host port)
- #-ccl
- (error "TODO port RW.SOCKET:MAKE-ACTIVE-UDP-SOCKET")
- #+ccl
- (ccl:make-socket :connect :active
- :address-family :internet
- :type :datagram
- :remote-host host
- :remote-port port))
;; eol
;; keepalive nodelay broadcast linger
;; backlog class out-of-band-inline
diff --git a/wire.lisp b/wire.lisp
@@ -0,0 +1,249 @@
+;;; Copyright (C) 2014 Tomas Hlavaty <tom@logand.com>
+;;;
+;;; Permission is hereby granted, free of charge, to any person
+;;; obtaining a copy of this software and associated documentation
+;;; files (the "Software"), to deal in the Software without
+;;; restriction, including without limitation the rights to use, copy,
+;;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;;; of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+;;;
+;;; The above copyright notice and this permission notice shall be
+;;; included in all copies or substantial portions of the Software.
+;;;
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+;;; DEALINGS IN THE SOFTWARE.
+
+(defpackage :rw.wire
+ (:use :cl)
+ (:export :defenum
+ :defstruc
+ :flush
+ :packet-writer
+ :u8
+ :u16
+ :u24
+ :u32))
+
+(in-package :rw.wire)
+
+;; TODO lots of cut&paste from tls
+
+(defun make-octet-buffer (length)
+ (make-array length
+ :element-type '(unsigned-byte 8)
+ :initial-element 0
+ :adjustable t
+ :fill-pointer 0))
+
+(defun next-u8 (reader)
+ (rw:next-u8 reader))
+
+(defun next-u16 (reader)
+ (rw:next-u16 reader))
+
+(defun next-u24 (reader)
+ (rw:next-u24 reader))
+
+(defun next-u32 (reader)
+ (rw:next-u32 reader))
+
+(defun write-u8 (writer x)
+ (rw:write-u8 writer x))
+
+(defun write-u16 (writer x)
+ (rw:write-u16 writer x))
+
+(defun write-u24 (writer x)
+ (assert (<= 0 x #.(1- (expt 2 24))))
+ (write-u8 writer (ash x -16))
+ (write-u8 writer (logand #xff (ash x -8)))
+ (write-u8 writer (logand #xff x)))
+
+(defun write-u32 (writer x)
+ (rw:write-u32 writer x))
+
+(defun %intern (pre x post)
+ (intern (format nil "~a~a~a" pre x post) (symbol-package x)))
+
+(defun fname (x)
+ (%intern "" x ""))
+
+(defun mname (x)
+ (%intern "MAKE-" x ""))
+
+(defun rname (x)
+ (%intern "NEXT-" x ""))
+
+(defun wname (x)
+ (%intern "WRITE-" x ""))
+
+(defmacro defenum (name (&key nbits) &body alist)
+ (let ((fname (fname name))
+ (sname (%intern "" name "-SYMBOLS"))
+ (cname (%intern "" name "-CODES"))
+ (rname (rname name))
+ (wname (wname name)))
+ `(let* ((alist ',alist)
+ (symbols (mapcar #'car alist))
+ (codes (mapcar #'cdr alist)))
+ (defun ,fname (x)
+ (etypecase x
+ (symbol (cdr (assoc x alist)))
+ (integer (car (rassoc x alist)))))
+ (defun ,sname () symbols)
+ (defun ,cname () codes)
+ (defun ,rname (reader)
+ (let ((z (,fname (, (ecase nbits
+ (8 'rw:next-u8)
+ (16 'rw:next-u16))
+ reader))))
+ (assert z)
+ z))
+ (defun ,wname (writer x)
+ (, (ecase nbits
+ (8 'rw:write-u8)
+ (16 'rw:write-u16))
+ writer
+ (etypecase x
+ (symbol (,fname x))
+ (integer (when (member x codes) x))))))))
+
+(defun aname (struc &optional slot)
+ (intern (format nil "~a-~a" struc slot) (symbol-package struc)))
+
+(defun defun-rname-slot (slot)
+ (destructuring-bind (ty na &key length size min max compute next) slot
+ `(,na
+ , (flet ((r1 ()
+ (if (listp ty)
+ `(ecase ,(cadr ty)
+ ,@(loop
+ for (nm ty) in (cddr ty)
+ collect (if ty
+ `(,nm (,(rname ty) r))
+ `(,nm))))
+ `(,(rname ty) r))))
+ (cond
+ ((or compute next)
+ (assert (eq 'computed ty))
+ (assert (not (or length size min max)))
+ (or compute next))
+ (length
+ `(let ((l (,(rname length) r))
+ (b (make-octet-buffer 100)))
+ ,@(when min `((assert (<= ,min l))))
+ ,@(when max `((assert (<= l ,max))))
+ ,@(when (integerp size) `((assert (= l ,size))))
+ (dotimes (i l)
+ (vector-push-extend (next-u8 r) b))
+ ,(if (eq 'u8 ty)
+ 'b
+ (if size
+ `(let ((r (rw:peek-reader (rw:reader b))))
+ (loop
+ while (rw:peek r)
+ collect ,(r1)))
+ `(let ((r (rw:reader b)))
+ ,(r1))))))
+ (size
+ ;;(assert (eq 'u8 ty))
+ `(loop for i from 0 below ,size collect ,(r1)))
+ (t
+ `(let ((v ,(r1)))
+ ,@(when min `((assert (<= ,min v))))
+ ,@(when max `((assert (<= v ,max))))
+ v)))))))
+
+(defun defun-rname (name slots)
+ `(defun ,(rname name) (r)
+ (let* (,@(mapcar 'defun-rname-slot slots))
+ (,(mname name)
+ ,@(loop
+ for slot in slots
+ appending (let ((na (cadr slot)))
+ (list (intern (symbol-name na) :keyword) na)))))))
+
+(defun defun-wname (name slots)
+ `(defun ,(wname name) (w x)
+ ,@(loop
+ for slot in slots
+ collect
+ (destructuring-bind (ty na &key length size min max compute next) slot
+ (flet ((w1 ()
+ (if (listp ty)
+ (ecase (car ty)
+ (ecase `(ecase (,(aname name (cadr ty)) x)
+ ,@(loop
+ for (nm ty) in (cddr ty)
+ collect
+ (if ty
+ `(,nm (,(wname ty) w v))
+ `(,nm))))))
+ `(,(wname ty) w v))))
+ (cond
+ ((or compute next)
+ (assert (eq 'computed ty))
+ (assert (not (or length size min max)))
+ (when compute
+ `(setf (,(aname name na) x) ,compute)))
+ (length
+ `(let ((v (,(aname name na) x))
+ (b (make-octet-buffer 100)))
+ (let ((w (rw:writer b)))
+ ,(cond
+ (size
+ `(if (listp v)
+ (loop for v in v do ,(w1))
+ (loop for v across v do ,(w1))))
+ (t (w1))))
+ (let ((l (length b)))
+ ,@(when min `((assert (<= ,min l))))
+ ,@(when max `((assert (<= l ,max))))
+ ,@(when (integerp size) `((assert (= l ,size))))
+ (,(wname length) w l))
+ (loop for e across b do (write-u8 w e))))
+ (size
+ ;;(assert (eq 'u8 ty))
+ `(let ((v (,(aname name na) x)))
+ ,@ (when (or min max (integerp size))
+ `((let ((l (length v)))
+ ,@(when min `((assert (<= ,min l))))
+ ,@(when max `((assert (<= l ,max))))
+ ,@(when (integerp size) `((assert (= l ,size)))))))
+ (if (listp v)
+ (loop for v in v do ,(w1))
+ (loop for v across v do ,(w1)))))
+ (t
+ `(let ((v (,(aname name na) x)))
+ ,@(when min `((assert (<= ,min v))))
+ ,@(when max `((assert (<= v ,max))))
+ ,(w1)))))))))
+
+(defmacro defstruc (name () &body slots)
+ `(progn
+ (defstruct ,(fname name) ,@(mapcar #'cadr slots))
+ ,(defun-rname name slots)
+ ,(defun-wname name slots)))
+
+(defun packet-writer (stream)
+ (let ((b (make-octet-buffer 42)))
+ (lambda (x)
+ (case x
+ (flush
+ (print b)
+ (write-sequence b stream)
+ (finish-output stream)
+ (setf (fill-pointer b) 0))
+ (t
+ (vector-push-extend x b)))
+ x)))
+
+(defun flush (writer)
+ (funcall writer 'flush))