;;; -*- Mode:LISP; Package:ICMP; Readtable:CL; Base:10 -*- #| Copyright LISP Machine, Inc. 1987 See filename "Copyright.Text" for licensing and release information. |# (export '( *icmp-stream* setup-icmp describe-icmp icmp with-icmp-socket ping)) ;;;ICMP interface to IP (defstream icmp-ip-transport-protocol (ip-transport-protocol) "ICMP-" (user-socket-alist nil) ;user sockets (next-identifier 0) ;next user socket identifier to assign (echo-sent 0) ;echo messages sent (echo-received 0) ;echo messages received (echo-reply-sent 0) ;echo-reply messages sent (echo-reply-received 0) ;echo-reply messages received (info-sent 0) ;info messages sent (info-received 0) ;info messages received (info-reply-sent 0) ;info-reply messages sent (info-reply-received 0) ;info-reply messages received (timestamp-sent 0) ;timestamp messages sent (timestamp-received 0) ;timestamp messages received (timestamp-reply-sent 0) ;timestamp-reply messages sent (timestamp-reply-received 0) ;timestamp-reply messages received (address-mask-sent 0) ;address-mask messages sent (address-mask-received 0) ;address-mask messages received (address-mask-reply-sent 0) ;address-mask-reply messages sent (address-mask-reply-received 0) ;address-mask-reply messages received (destination-unreachable-sent 0) ;destination-unreachable messages sent (destination-unreachable-received 0) ;destination-unreachable messages received (source-quench-sent 0) ;source-quench messages sent (source-quench-received 0) ;source-quench messages received (redirect-sent 0) ;redirect messages sent (redirect-received 0) ;redirect messages received (parameter-problem-sent 0) ;parameter-problem messages sent (parameter-problem-received 0) ;parameter-problem messages received (time-exceeded-sent 0) ;time-exceeded messages sent (time-exceeded-received 0) ;time-exceeded messages received (local-checksums nil) ;T if calculate checksums for looped-back packets (checksum-failures 0) ;number of packets with bad ICMP checksum ) (defop (icmp-ip-transport-protocol :peek-normal-fields) (tp) (ncons (tv:scroll-parse-item `(:function icmp-next-identifier (,tp) NIL (" Next identifier assigned = ~D")) `(:function icmp-checksum-failures (,tp) NIL (" Checksum failures ~D")) `(:mouse-item (nil :buttons ,(make-list 3 :initial-element `(nil :eval (setf (icmp-local-checksums tp) (not (icmp-local-checksums tp))) :bindings ((tp ',tp)))) :DOCUMENTATION "Click to toggle local checksums" :BINDINGS ((tp ',tp))) :function icmp-local-checksums (,tp) NIL (" Local checksums: ~A"))))) (defop (icmp-ip-transport-protocol :peek-verbose-fields) (tp) (list (tv:scroll-parse-item " ****** Echo/Rply Info/Rply Time/Rply Mask/Rply RDIR QNCH TEXC UNRC PARM") (tv:scroll-parse-item " Sent: " `(:function icmp-echo-sent (,tp) NIL ("~5d")) `(:function icmp-echo-reply-sent (,tp) NIL ("~5d")) `(:function icmp-info-sent (,tp) NIL ("~5d")) `(:function icmp-info-reply-sent (,tp) NIL ("~5d")) `(:function icmp-timestamp-sent (,tp) NIL ("~5d")) `(:function icmp-timestamp-reply-sent (,tp) NIL ("~5d")) `(:function icmp-address-mask-sent (,tp) NIL ("~5d")) `(:function icmp-address-mask-reply-sent (,tp) NIL ("~5d")) `(:function icmp-redirect-sent (,tp) NIL ("~5d")) `(:function icmp-source-quench-sent (,tp) NIL ("~5d")) `(:function icmp-time-exceeded-sent (,tp) NIL ("~5d")) `(:function icmp-destination-unreachable-sent (,tp) NIL ("~5d")) `(:function icmp-parameter-problem-sent (,tp) NIL ("~5d")) ) (tv:scroll-parse-item " Rcvd: " `(:function icmp-echo-received (,tp) NIL ("~5d")) `(:function icmp-echo-reply-received (,tp) NIL ("~5d")) `(:function icmp-info-received (,tp) NIL ("~5d")) `(:function icmp-info-reply-received (,tp) NIL ("~5d")) `(:function icmp-timestamp-received (,tp) NIL ("~5d")) `(:function icmp-timestamp-reply-received (,tp) NIL ("~5d")) `(:function icmp-address-mask-received (,tp) NIL ("~5d")) `(:function icmp-address-mask-reply-received (,tp) NIL ("~5d")) `(:function icmp-redirect-received (,tp) NIL ("~5d")) `(:function icmp-source-quench-received (,tp) NIL ("~5d")) `(:function icmp-time-exceeded-received (,tp) NIL ("~5d")) `(:function icmp-destination-unreachable-received (,tp) NIL ("~5d")) `(:function icmp-parameter-problem-received (,tp) NIL ("~5d")) ))) (defop (icmp-ip-transport-protocol :peek-final-fields) (tp) (ncons (TV:SCROLL-MAINTAIN-LIST #'(LAMBDA () (icmp-user-socket-alist tp)) 'peek-icmp-socket))) (defun peek-icmp-socket (elt) (let ((socket (cdr elt))) (send socket :peek-item elt))) (defvar *icmp-stream* nil "The ip-transport-protocol for ICMP") (defvar *icmp-receives-out* 4 "Number of outstanding receives") (defun setup-icmp () (setq *icmp-stream* (make-icmp-ip-transport-protocol :keyword :icmp :receive-function 'receive-icmp-packet :reset-function 'icmp-reset :close-function 'icmp-close :type icmp-protocol :gauge-name "ICMP" :broadcast-allowed-p t )) (send *icmp-stream* :open) ;let IP know about us (send *icmp-stream* :enable) ;set up for business (dotimes (i *icmp-receives-out*) ;and give IP some read buffers (send *icmp-stream* :receive (get-icmp-message))) t) (defun icmp-close (stream &optional abort) (declare (ignore abort)) (do () ((null (icmp-receive-buffers stream))) (free-icmp-message (pop (icmp-receive-buffers stream)))) (close-icmp-sockets stream)) (defun icmp-reset (stream) (close-icmp-sockets stream)) (defun close-icmp-sockets (stream) (dolist (user (icmp-user-socket-alist stream)) (send (cdr user) :close))) ;;;ICMP header definitions (defmacro icmp-type (icmp) `(aref ,icmp 0)) (defmacro icmp-code (icmp) `(aref ,icmp 1)) (defmacro icmp-checksum (icmp) `(dpb (aref ,icmp 2) (byte 8 8) (aref ,icmp 3))) (defun set-icmp-checksum (icmp val) (setf (aref icmp 2) (ldb (byte 8 8) val)) (setf (aref icmp 3) (ldb (byte 8 0) val)) val) (defsetf icmp-checksum set-icmp-checksum) ;;;Here follow the message-specific bytes. (defmacro icmp-byte-1 (icmp) `(aref ,icmp 4)) (defmacro icmp-byte-2 (icmp) `(aref ,icmp 5)) (defmacro icmp-byte-3 (icmp) `(aref ,icmp 6)) (defmacro icmp-byte-4 (icmp) `(aref ,icmp 7)) ;;;Redirect has a 4 byte gateway address (defsubst icmp-gateway (icmp) (dpb (aref icmp 4) (byte 8 24.) (dpb (aref icmp 5) (byte 8 16.) (dpb (aref icmp 6) (byte 8 8) (aref icmp 7))))) (defun set-icmp-gateway (icmp val) (setf (aref icmp 4) (ldb (byte 8 24.) val)) (setf (aref icmp 5) (ldb (byte 8 16.) val)) (setf (aref icmp 6) (ldb (byte 8 8) val)) (setf (aref icmp 7) (ldb (byte 8 0) val)) val) (defsetf icmp-gateway set-icmp-gateway) ;;;Parameter problem has a 1 byte pointer (defmacro icmp-parameter-problem-pointer (icmp) `(aref ,icmp 4)) ;;;Echo, Timestamp, Info, and Address Mask use sequence and identifier (defmacro icmp-identifier (icmp) `(dpb (aref ,icmp 4) (byte 8 8) (aref ,icmp 5))) (defun set-icmp-identifier (icmp val) (setf (aref icmp 4) (ldb (byte 8 8) val)) (setf (aref icmp 5) (ldb (byte 8 0) val)) val) (defsetf icmp-identifier set-icmp-identifier) (defmacro icmp-sequence (icmp) `(dpb (aref ,icmp 6) (byte 8 8) (aref ,icmp 7))) (defun set-icmp-sequence (icmp val) (setf (aref icmp 6) (ldb (byte 8 8) val)) (setf (aref icmp 7) (ldb (byte 8 0) val)) val) (defsetf icmp-sequence set-icmp-sequence) ;;;Timestamp uses 3 4-byte timestamps (defsubst icmp-originate-timestamp (icmp) (dpb (aref icmp 8) (byte 8 24.) (dpb (aref icmp 9) (byte 8 16.) (dpb (aref icmp 10) (byte 8 8) (aref icmp 11))))) (defun set-icmp-originate-timestamp (icmp val) (setf (aref icmp 8) (ldb (byte 8 24.) val)) (setf (aref icmp 9) (ldb (byte 8 16.) val)) (setf (aref icmp 10) (ldb (byte 8 8) val)) (setf (aref icmp 11) (ldb (byte 8 0) val)) val) (defsetf icmp-originate-timestamp set-icmp-originate-timestamp) (defsubst icmp-receive-timestamp (icmp) (dpb (aref icmp 12) (byte 8 24.) (dpb (aref icmp 13) (byte 8 16.) (dpb (aref icmp 14) (byte 8 8) (aref icmp 15))))) (defun set-icmp-receive-timestamp (icmp val) (setf (aref icmp 12) (ldb (byte 8 24.) val)) (setf (aref icmp 13) (ldb (byte 8 16.) val)) (setf (aref icmp 14) (ldb (byte 8 8) val)) (setf (aref icmp 15) (ldb (byte 8 0) val)) val) (defsetf icmp-receive-timestamp set-icmp-receive-timestamp) (defsubst icmp-transmit-timestamp (icmp) (dpb (aref icmp 16) (byte 8 24.) (dpb (aref icmp 17) (byte 8 16.) (dpb (aref icmp 18) (byte 8 8) (aref icmp 19))))) (defun set-icmp-transmit-timestamp (icmp val) (setf (aref icmp 16) (ldb (byte 8 24.) val)) (setf (aref icmp 17) (ldb (byte 8 16.) val)) (setf (aref icmp 18) (ldb (byte 8 8) val)) (setf (aref icmp 19) (ldb (byte 8 0) val)) val) (defsetf icmp-transmit-timestamp set-icmp-transmit-timestamp) ;;;Address Mask uses a 4-byte mask (defsubst icmp-address-mask (icmp) (dpb (aref icmp 8) (byte 8 24.) (dpb (aref icmp 9) (byte 8 16.) (dpb (aref icmp 10) (byte 8 8) (aref icmp 11))))) (defsetf icmp-address-mask set-icmp-originate-timestamp) (eval-when (load compile eval) (defvar *icmp-message-list* nil "Valid user-sendable ICMP message types") (defvar *icmp-type-alist* nil "Association list between keyword and code") (defvar *icmp-int-pkt-messages* nil "List of messages with int-pkt as parameter")) (defmacro define-icmp-message (keyword type &optional user-sendable-p int-pkt-p) (let ((name (intern (string-append "ICMP-" keyword)))) `(eval-when (load compile eval) (defconstant ,name ,type) (when ,int-pkt-p (pushnew ,keyword *icmp-int-pkt-messages*)) (unless (assoc ,keyword *icmp-type-alist*) (push (cons ,keyword ,type) *icmp-type-alist*)) (when ,user-sendable-p (pushnew ,keyword *icmp-message-list*))))) (define-icmp-message :echo-reply 0) (define-icmp-message :destination-unreachable 3 t t) (define-icmp-message :source-quench 4 t t) (define-icmp-message :redirect 5 t t) (define-icmp-message :echo 8 t) (define-icmp-message :time-exceeded 11 t t) (define-icmp-message :parameter-problem 12 t t) (define-icmp-message :timestamp 13 t) (define-icmp-message :timestamp-reply 14) (define-icmp-message :information-request 15 t) (define-icmp-message :information-reply 16) (define-icmp-message :address-mask-request 17 t) (define-icmp-message :address-mask-reply 18) (defvar *free-icmp-messages* nil "List of free ICMP message buffers") (defun get-icmp-message () "returns an array to hold an ICMP message." (if *free-icmp-messages* (pop *free-icmp-messages*) (zl:make-array 1480 ; + 20 IP = 1500 bytes :element-type '(unsigned-byte 8) :fill-pointer 0 :leader-length 2 :named-structure-symbol 'icmp-message))) (defun free-icmp-message (icmp) (push icmp *free-icmp-messages*) nil) (defun icmp-message-p (icmp) (eq (named-structure-p icmp) 'icmp-message)) (defselect ((:property icmp-message named-structure-invoke)) (:describe (message) (describe-icmp message)) (:print-self (message stream ignore ignore) (si:printing-random-object (message stream :type :no-pointer) (format stream "~A" (car (rassoc (icmp-type message) *icmp-type-alist*)))))) (defun describe-icmp (icmp &aux type-name ip-header) "Given an art-8b array, displays the ICMP message in it" (setq type-name (car (rassoc (icmp-type icmp) *icmp-type-alist*))) (format t "~&message type: ~20t~A (~D)" type-name (icmp-type icmp)) (format t "~&checksum: ~20t~D" (icmp-checksum icmp)) (when (member type-name *icmp-int-pkt-messages*) (setq ip-header (make-array 60 :element-type '(unsigned-byte 8) :displaced-to icmp :displaced-index-offset 8))) (case (icmp-type icmp) ((#.icmp-echo #.icmp-echo-reply) (format t "~&Identifier: ~20t~D" (icmp-identifier icmp)) (format t "~&Sequence number: ~20t~D" (icmp-sequence icmp)) (format t "~&Echo data:") (do* ((offset 8 (1+ offset)) (count (- (fill-pointer icmp) 8)) (index 0 (1+ index))) ((= index count) nil) (if (zerop (mod index 16)) (format t "~&")) (format t "~16,2,'0r " (aref icmp offset)))) ((#.icmp-timestamp #.icmp-timestamp-reply) (format t "~&Identifier: ~20t~D" (icmp-identifier icmp)) (format t "~&Sequence number: ~20t~D" (icmp-sequence icmp)) (format t "~&Originate timestamp: ~20t~D" (icmp-originate-timestamp icmp)) (format t "~&Receive timestamp: ~20t~D" (icmp-receive-timestamp icmp)) (format t "~&Transmit timestamp: ~20t~D" (icmp-transmit-timestamp icmp))) ((#.icmp-information-request #.icmp-information-reply) (format t "~&Identifier: ~20t~D" (icmp-identifier icmp)) (format t "~&Sequence number: ~20t~D" (icmp-sequence icmp))) ((#.icmp-address-mask-request #.icmp-address-mask-reply) (format t "~&Identifier: ~20t~D" (icmp-identifier icmp)) (format t "~&Sequence number: ~20t~D" (icmp-sequence icmp)) (format t "~&Address Mask: ~20t~16,8,'0r" (icmp-address-mask icmp))) (#.icmp-destination-unreachable (format t "~&code: ~20t~D" (icmp-code icmp)) (format t "~&In response to IP header:") (describe-ih ip-header 8)) (#.icmp-source-quench (format t "~&In response to IP header:") (describe-ih ip-header 8)) (#.icmp-redirect (format t "~&code: ~20t~D" (icmp-code icmp)) (format t "~&Gateway:~20t~A" (canonical-ip (icmp-gateway icmp))) (format t "~&In response to IP header:") (describe-ih ip-header 8)) (#.icmp-time-exceeded (format t "~&code: ~20t~D" (icmp-code icmp)) (format t "~&In response to IP header:") (describe-ih ip-header 8)) (#.icmp-parameter-problem (format t "~&In response to byte ~D of IP header:" (icmp-parameter-problem-pointer icmp)) (describe-ih ip-header 8))) icmp) ;;;ICMP message sending -- interface to IP (defun icmp (message buffers &rest args) "Send an ICMP message if the icmp stream is operational" (if (and *icmp-stream* ;first, make sure icmp is operational (ip:tp-enabled *icmp-stream*) (member message *icmp-message-list*)) ;;ICMP is up. See if this message is in response to a received IP message (if (member message *icmp-int-pkt-messages*) (let* ((packet-list (if (consp buffers) buffers (ncons buffers))) (header (first packet-list)) (address (ip:ih-dest-address header))) ;;It is. Send ICMP message only if first fragment & not for ICMP & not broadcast packet (when (and (zerop (ip:ih-fragment-offset header)) (/= (ip:ih-protocol header) icmp-protocol) (not (ip:ip-broadcast-address-p address)) (not (ip:ip-self-address-p address))) ;;OK to send -- call ICMP (apply *icmp-stream* message packet-list args))) ;;Message not in response to received IP packet (apply *icmp-stream* message args)))) (defop (icmp-ip-transport-protocol :destination-unreachable) (buffers code) (when (icmp-send-int-pkt-message icmp-destination-unreachable buffers code) (incf (icmp-destination-unreachable-sent *icmp-stream*)))) (defop (icmp-ip-transport-protocol :source-quench) (buffers) (when (icmp-send-int-pkt-message icmp-source-quench buffers 0) (incf (icmp-source-quench-sent *icmp-stream*)))) (defop (icmp-ip-transport-protocol :redirect) (buffers code gateway) (when (icmp-send-int-pkt-message icmp-redirect buffers code gateway) (incf (icmp-redirect-sent *icmp-stream*)))) (defop (icmp-ip-transport-protocol :time-exceeded) (buffers code) (when (icmp-send-int-pkt-message icmp-time-exceeded buffers code) (incf (icmp-time-exceeded-sent *icmp-stream*)))) (defop (icmp-ip-transport-protocol :parameter-problem) (buffers pointer) (when (icmp-send-int-pkt-message icmp-parameter-problem buffers 0 (dpb pointer (byte 8 24) 0)) (incf (icmp-parameter-problem-sent *icmp-stream*)))) (defun icmp-send-int-pkt-message (type buffers code &optional (4-bytes 0) &aux packet) (when (setq packet (allocate-packet nil)) (let ((message (get-icmp-message)) ;icmp message we are building header) ;IP header to send it with (setf (icmp-type message) type) (setf (icmp-code message) code) (setf (icmp-byte-1 message) (ldb (byte 8 24) 4-bytes)) (setf (icmp-byte-2 message) (ldb (byte 8 16) 4-bytes)) (setf (icmp-byte-3 message) (ldb (byte 8 8) 4-bytes)) (setf (icmp-byte-4 message) (ldb (byte 8 0) 4-bytes)) (unless (consp buffers) (setq buffers (ncons buffers))) (let ((count (+ 8 (ip:ih-ihl-bytes (first buffers))))) ;total bytes to copy (setf (fill-pointer message) (+ 8 count)) (do ((b buffers (cdr b)) ;buffers remaining to look at (doff 8 (+ doff length)) ;offset in ICMP message (length 0)) ;length to copy from current buffer ((or (null b) (zerop count))) (setq length (min count (length (car b)))) (copy-array-portion (car b) 0 length message doff (+ doff length)) (decf count length))) (let ((destination (ip:ih-source-address (first buffers)))) (setq header (make-ip-header :destination destination)) (store-icmp-checksum message (local-host-p destination))) (multiple-value-prog1 (send *icmp-stream* :send message header packet) (free-icmp-message message) (free-ip-header header))))) (defun store-icmp-checksum (buffers local-p) (unless (consp buffers) (setq buffers (ncons buffers))) (setf (icmp-checksum (first buffers)) 0) (when (and (not (icmp-local-checksums *icmp-stream*)) local-p) (return-from store-icmp-checksum t)) (setf (icmp-checksum (first buffers)) (checksum buffers 0))) ;;;ICMP message receiving (defun check-icmp-checksum (icmp local-p) (when (and (not (icmp-local-checksums *icmp-stream*)) local-p) (return-from check-icmp-checksum t)) (let ((sum (checksum-1 icmp 0 (length icmp)))) (values (zerop sum) sum))) (defun pass-up-icmp-message (buffer) "Given Destination Unreachable, Time Exceeded, or Source Quench ICMP message, inform the protocol that originated the problem packet" (let* ((header (make-array 60 :element-type '(unsigned-byte 8) :displaced-to buffer :displaced-index-offset 8)) (stream (cdr (assoc (ip:ih-protocol header) (ip:ip-protocols *ip-stream*))))) (when (and stream (ip:tp-icmp-notification-function stream)) (let* ((offset (+ 8 (ip:ih-ihl-bytes header))) (data (make-array 8 :element-type '(unsigned-byte 8)))) (copy-array-portion buffer offset (+ offset 8) data 0 8) (funcall (ip:tp-icmp-notification-function stream) (ecase (icmp-type buffer) (#.icmp-destination-unreachable (case (icmp-code buffer) (#.icmp-network-unreachable :network-unreachable) (#.icmp-host-unreachable :host-unreachable) (#.icmp-protocol-unreachable :protocol-unreachable) (#.icmp-port-unreachable :port-unreachable) (#.icmp-couldnt-fragment :couldnt-fragment) (#.icmp-source-route-failed :source-route-failed) (otherwise :unknown-unreachable))) (#.icmp-source-quench :source-quench) (#.icmp-time-exceeded (case (icmp-code buffer) (#.icmp-ttl-exceeded :time-to-live-expired) (#.icmp-fragment-timeout :fragment-reassembly-timeout) (otherwise :unknown-time-exceeded)))) (copy-ip-header header (get-ip-header) t) data))))) ;;;User interface to ICMP (defstream icmp-socket () "ICMP-USER-" (sequence 0) ;sequence number for next request (request-list nil) ;outstanding requests (reply-list nil) ;pending replies (echo-sent 0) ;echo requests sent (echo-received 0) ;echo replies received (info-sent 0) ;info requests sent (info-received 0) ;info replies received (timestamp-sent 0) ;timestamp requests sent (timestamp-received 0) ;timestamp replies received (address-mask-sent 0) ;address-mask requests sent (address-mask-received 0) ;address-mask replies received ) (defop (icmp-socket :peek-item) (elt) (let ((socket (cdr elt))) (list nil (tv:scroll-parse-item `(:MOUSE-ITEM (NIL :MENU-CHOOSE ("ICMP socket operations" ("Close" :EVAL (when (tv:mouse-y-or-n-p "Close this ICMP socket") (funcall socket :close)) :DOCUMENTATION "Click left to close this ICMP socket.") ("Inspect" :EVAL (send tv:selected-window :force-kbd-input `(inspect ,socket)) :DOCUMENTATION "Click left to INSPECT this ICMP socket.") ("Describe" :EVAL (send tv:selected-window :force-kbd-input `(describe ,socket)) :DOCUMENTATION "Click left to DESCRIBE this ICMP socket.")) :DOCUMENTATION "Menu of things to do to this ICMP socket." :BINDINGS ((socket ',socket))) :FUNCTION car (,elt) NIL (" ICMP socket ~D:")) `(:function icmp-user-sequence (,socket) NIL (" Next sequence = ~D")) `(:function icmp-user-echo-sent (,socket) NIL (" Echo S/R: ~D")) `(:function icmp-user-echo-received (,socket) NIL ("/~D")) `(:function icmp-user-info-sent (,socket) NIL (" Info S/R: ~D")) `(:function icmp-user-info-received (,socket) NIL ("/~D")) `(:function icmp-user-timestamp-sent (,socket) NIL (" Timestamp S/R: ~D")) `(:function icmp-user-timestamp-received (,socket) NIL ("/~D")) `(:function icmp-user-address-mask-sent (,socket) NIL (" Mask S/R: ~D")) `(:function icmp-user-address-mask-received (,socket) NIL ("/~D")))))) (defop (icmp-socket :open) () (unless (rassoc self (icmp-user-socket-alist *icmp-stream*)) (when (icmp-enabled *icmp-stream*) (do ((id (icmp-next-identifier *icmp-stream*) (1+ id))) ((null (assoc id (icmp-user-socket-alist *icmp-stream*))) (push (cons id self) (icmp-user-socket-alist *icmp-stream*)) (setf (icmp-next-identifier *icmp-stream*) (1+ id)) t))))) (defop (icmp-socket :close) () (let ((item (rassoc self (icmp-user-socket-alist *icmp-stream*)))) (when item (setf (icmp-user-socket-alist *icmp-stream*) (remove item (icmp-user-socket-alist *icmp-stream*))) t))) (defop (icmp-socket :print-self) (stream &optional ignore ignore) (si:printing-random-object (self stream :type :no-pointer) (format stream "~:[closed~;open~]" (and *icmp-stream* (rassoc self (icmp-user-socket-alist *icmp-stream*)))))) (defop (icmp-socket :echo) (data sequence-number &rest header-args) (icmp-user-message self :echo data sequence-number header-args)) (defop (icmp-socket :info) (ignore sequence-number &rest header-args) (icmp-user-message self :info nil sequence-number header-args)) (defop (icmp-socket :timestamp) (ignore sequence-number &rest header-args) (icmp-user-message self :timestamp nil sequence-number header-args)) (defop (icmp-socket :address-mask) (network sequence-number &rest header-args) (icmp-user-message self :address-mask network sequence-number header-args)) (defun icmp-user-message (socket operation data sequence header-args) (let ((header (apply 'make-ip-header header-args)) (message (get-icmp-message))) (unwind-protect (let ((buffers (ncons message)) (result nil)) (unless sequence (setq sequence (icmp-user-sequence socket)) (incf (icmp-user-sequence socket))) (setf (icmp-code message) 0) (setf (icmp-identifier message) (car (rassoc socket (icmp-user-socket-alist *icmp-stream*)))) (setf (icmp-sequence message) sequence) (case operation (:echo (setf (icmp-type message) icmp-echo) (setf (fill-pointer message) 8) (check-type data (satisfies byte-array-or-string-p)) (setq buffers (nconc buffers (ncons data)))) (:info (setf (icmp-type message) icmp-information-request) (setf (fill-pointer message) 8)) (:timestamp (setf (icmp-type message) icmp-timestamp) (setf (icmp-originate-timestamp message) (milliseconds-since-midnight-gmt)) (setf (icmp-receive-timestamp message) 0) (setf (icmp-transmit-timestamp message) 0) (setf (fill-pointer message) 20)) (:address-mask (setf (icmp-type message) icmp-address-mask-request) (setf (icmp-address-mask message) (or data 0)) (setf (fill-pointer message) 16))) (store-icmp-checksum buffers (local-host-p (ip::ih-dest-address header))) (push (cons sequence nil) (icmp-user-request-list socket)) (if (eq operation :address-mask) (setq result (send *icmp-stream* :broadcast buffers header data)) (setq result (send *icmp-stream* :send buffers header))) (cond ((or (null result) (zerop result)) ;;Couldn't route or send on interface (delete-from-alist sequence (icmp-user-request-list socket)) nil) (t (case operation (:echo (incf (icmp-user-echo-sent socket)) (incf (icmp-echo-sent *icmp-stream*))) (:info (incf (icmp-user-info-sent socket)) (incf (icmp-info-sent *icmp-stream*))) (:timestamp (incf (icmp-user-timestamp-sent socket)) (incf (icmp-timestamp-sent *icmp-stream*))) (:address-mask (incf (icmp-user-address-mask-sent socket)) (incf (icmp-address-mask-sent *icmp-stream*)))) sequence))) (free-ip-header header) (free-icmp-message message)))) (defmacro with-icmp-socket ((socket) &body body) (let ((s (gensym))) `(let ((,s (make-icmp-socket))) (unwind-protect (let ((,socket ,s)) (when (send ,s :open) ,@body)) (send ,s :close))))) (defun ping (host &optional (operation :echo) (data nil data-p) &rest ip-header-args) "Do an ICMP Echo, Timestamp, Information Request, or Address Mask" (let ((original-host host)) (assert (numberp (setq host (parse-internet-address (setq original-host host)))) (host) "~S is not a valid Internet host specification" original-host)) (check-type operation (member :echo :info :address-mask :timestamp)) (unless data-p (case operation (:echo ;Default data for :echo is a string... (setq data "Ping")) (:address-mask ;;Default data is remote-address -- which implies a specific network interface (setq data host)))) (with-icmp-socket (s) (let ((start (zl:time)) (sequence (apply s operation data nil :destination host ip-header-args))) (when sequence (process-wait-with-timeout "Ping Reply" 300 #'(lambda (socket sequence) (assoc sequence (icmp-user-reply-list socket))) s sequence) (let ((end (zl:time)) (result (assoc sequence (icmp-user-reply-list s)))) (when result (values (time-difference end start) (cddr result)))))))) ;;;Top level ICMP packet reception (defun receive-icmp-packet (stream buffer header local-p broadcast-p interface) (declare (ignore broadcast-p)) (unwind-protect (if (check-icmp-checksum buffer local-p) (case (icmp-type buffer) (#.icmp-echo (receive-icmp-echo stream buffer header local-p)) (#.icmp-information-request (receive-icmp-info stream buffer header local-p)) (#.icmp-timestamp (receive-icmp-timestamp stream buffer header local-p)) (#.icmp-address-mask-request (receive-icmp-address-mask stream buffer header local-p interface)) (#.icmp-echo-reply (receive-icmp-echo-reply stream buffer)) (#.icmp-information-reply (receive-icmp-info-reply stream buffer)) (#.icmp-timestamp-reply (receive-icmp-timestamp-reply stream buffer)) (#.icmp-address-mask-reply (receive-icmp-address-mask-reply stream buffer)) (#.icmp-redirect (receive-icmp-redirect stream buffer)) (#.icmp-destination-unreachable (receive-icmp-unreachable stream buffer)) (#.icmp-source-quench (receive-icmp-source-quench stream buffer)) (#.icmp-time-exceeded (receive-icmp-time-exceeded stream buffer)) (#.icmp-parameter-problem (receive-icmp-parameter-problem stream buffer))) (incf (icmp-checksum-failures stream))) (free-ip-header header) (send stream :receive buffer))) (defun receive-icmp-echo (stream buffer header local-p) (incf (icmp-echo-received stream)) (reverse-route header) (setf (icmp-type buffer) icmp-echo-reply) (store-icmp-checksum buffer local-p) (let ((result (send stream :send buffer header))) (and result (plusp result) (incf (icmp-echo-reply-sent stream))))) (defun receive-icmp-info (stream buffer header local-p) (incf (icmp-info-received stream)) (reverse-route header) (setf (icmp-type buffer) icmp-information-reply) (store-icmp-checksum buffer local-p) (let ((result (send stream :send buffer header))) (and result (plusp result) (incf (icmp-info-reply-sent stream))))) (defun receive-icmp-timestamp (stream buffer header local-p) (setf (icmp-receive-timestamp buffer) (milliseconds-since-midnight-gmt)) (incf (icmp-timestamp-received stream)) (reverse-route header) (setf (icmp-type buffer) icmp-timestamp-reply) (setf (icmp-transmit-timestamp buffer) (milliseconds-since-midnight-gmt)) (store-icmp-checksum buffer local-p) (let ((result (send stream :send buffer header))) (and result (plusp result) (incf (icmp-timestamp-reply-sent stream))))) (defun receive-icmp-address-mask (stream buffer header local-p interface) (incf (icmp-address-mask-received stream)) (when (ip:ip-gateway *ip-stream*) ;Only a gateway responds to Address Mask request (reverse-route header) (setf (icmp-type buffer) icmp-address-mask-reply) (setf (ip:ih-source-address header) (second (assoc :internet (net:ni-address-alist interface)))) (setf (icmp-address-mask buffer) (third (assoc :internet (net:ni-network-alist interface)))) (store-icmp-checksum buffer local-p) (let ((result (if (zerop (ip:ih-dest-address header)) (send stream :broadcast buffer header (ip:ih-source-address header)) (send stream :send buffer header)))) (and result (plusp result) (incf (icmp-address-mask-reply-sent stream)))))) (defun receive-icmp-echo-reply (stream buffer) (incf (icmp-echo-reply-received stream)) (let ((user (assoc (icmp-identifier buffer) (icmp-user-socket-alist stream))) (data-length (- (length buffer) 8)) (string nil)) (when (and user (assoc (icmp-sequence buffer) (icmp-user-request-list (cdr user)))) (delete-from-alist (icmp-sequence buffer) (icmp-user-request-list (cdr user))) (copy-array-portion buffer 8 (length buffer) (setq string (make-string data-length)) 0 data-length) (push (list (icmp-sequence buffer) (zl:time) string) (icmp-user-reply-list (cdr user))) (incf (icmp-user-echo-received (cdr user)))))) (defun receive-icmp-info-reply (stream buffer) (incf (icmp-info-reply-received stream)) (let ((user (assoc (icmp-identifier buffer) (icmp-user-socket-alist stream)))) (when (and user (assoc (icmp-sequence buffer) (icmp-user-request-list (cdr user)))) (delete-from-alist (icmp-sequence buffer) (icmp-user-request-list (cdr user))) (push (list (icmp-sequence buffer) (zl:time)) (icmp-user-reply-list (cdr user))) (incf (icmp-user-info-received (cdr user)))))) (defun receive-icmp-timestamp-reply (stream buffer) (incf (icmp-timestamp-reply-received stream)) (let ((user (assoc (icmp-identifier buffer) (icmp-user-socket-alist stream)))) (when (and user (assoc (icmp-sequence buffer) (icmp-user-request-list (cdr user)))) (delete-from-alist (icmp-sequence buffer) (icmp-user-request-list (cdr user))) (push (list (icmp-sequence buffer) (zl:time) (icmp-originate-timestamp buffer) (icmp-receive-timestamp buffer) (icmp-transmit-timestamp buffer)) (icmp-user-reply-list (cdr user))) (incf (icmp-user-timestamp-received (cdr user)))))) (defun receive-icmp-address-mask-reply (stream buffer) (incf (icmp-address-mask-reply-received stream)) (let ((user (assoc (icmp-identifier buffer) (icmp-user-socket-alist stream)))) (when (and user (assoc (icmp-sequence buffer) (icmp-user-request-list (cdr user)))) (delete-from-alist (icmp-sequence buffer) (icmp-user-request-list (cdr user))) (push (list (icmp-sequence buffer) (zl:time) (icmp-address-mask buffer)) (icmp-user-reply-list (cdr user))) (incf (icmp-user-address-mask-received (cdr user)))))) (defun receive-icmp-redirect (stream buffer) (incf (icmp-redirect-received stream)) (let* ((header (make-array 60 :element-type '(unsigned-byte 8) :displaced-to buffer :displaced-index-offset 8)) (address (ip:ih-dest-address header)) (gateway (icmp-gateway buffer)) network tos) (case (icmp-code buffer) (#.icmp-redirect-network (setq network (ip:ip-network-number-from-address address))) (#.icmp-redirect-host (setq network address)) (#.icmp-redirect-network-and-tos (setq network (ip:ip-network-number-from-address address)) (setq tos (ip:ih-tos header))) (#.icmp-redirect-host-and-tos (setq network address) (setq tos (ip:ih-tos header)))) (when network ;Valid code... (multiple-value-bind (found interface ignore) (ip:route gateway) (when (eql found gateway) ;And we know how to get to the gateway directly (ip:add-gateway network gateway interface tos *icmp-redirect-expiration*)))))) (defun receive-icmp-unreachable (stream buffer) (incf (icmp-destination-unreachable-received stream)) ;;****change routing table? (pass-up-icmp-message buffer)) (defun receive-icmp-source-quench (stream buffer) (incf (icmp-source-quench-received stream)) (pass-up-icmp-message buffer)) (defun receive-icmp-time-exceeded (stream buffer) (incf (icmp-time-exceeded-received stream)) (pass-up-icmp-message buffer)) (defun receive-icmp-parameter-problem (stream buffer) (declare (ignore buffer)) (incf (icmp-parameter-problem-received stream)) ;;****Log this somehow -- bug in our IP??!! )