;;; -*- Mode:LISP; Package:INTERNET; Base:10; Readtable:CL -*- #| Copyright LISP Machine, Inc. 1987 See filename "Copyright.Text" for licensing and release information. |# (export '( *ip-stream* ip-transport-protocol make-ip-header copy-ip-header reverse-route set-destination-address set-precedence get-ip-header free-ip-header ip-header-p parse-internet-address canonical-ip describe-ih mss local-host-p find-security-and-precedence checksum checksum-1 pseudo-header-checksum setup-ip)) ;;; This is the network-protocol structure for IP -- the interface between IP ;;; and the link-level network interfaces (defstream ip-network-protocol (network-protocol) "IP-" (gateway nil) ; t if act as IP gateway and forward packets ; NOTE: will always act as gateway for share device (checksum-failures 0) ; packets received with bad checksum (bad-header 0) ; packets dropped because of bad header (bad-options 0) ; packets dropped because of bad options (ttl-expired 0) ; packets dropped because ttl expired (unknown-protocol-packets 0) ; packets dropped due to unregistered transport protocol (fragment-timeouts 0) ; packets dropped because fragments timed-out (self-address-packets 0) ; packets received with the IP "self" address (local-checksums nil) ; T if calculate checksums for looped-back packets (hardware-broadcast-packets 0) ; packets received on a hardware broadcast address (ip-broadcast-packets 0) ; packets received with IP broadcast address (not-for-me 0) ; (if gateway nil) packets dropped: not for this address (no-forwarding-address 0) ; packets dropped because no link-level address translation (cant-forward 0) ; packets dropped because too many already queued (packets-forwarded 0) ; packets successfully forwarded (default-address 0) ; default IP address (packets-queued 0) ; packets queued for forwarding by the background process ) (defop (ip-network-protocol :peek-special-fields) (np) (list (tv:scroll-parse-item `(:function ip-checksum-failures (,np) NIL ("Bad checksum ~D")) `(:function ip-bad-header (,np) NIL (" bad header ~D")) `(:function ip-bad-options (,np) NIL (" bad options ~D")) `(:function ip-unknown-protocol-packets (,np) NIL (" bad protocol ~D")) `(:function ip-fragment-timeouts (,np) NIL (" frag timeouts ~D"))) (tv:scroll-parse-item `(:function ip-hardware-broadcast-packets (,np) NIL ("Hardware Broadcast packets = ~D")) `(:function ip-ip-broadcast-packets (,np) NIL (" IP Broadcast packets ~D")) `(:function ip-self-address-packets (,np) NIL (" Self Address packets ~D")) `(:mouse-item (nil :buttons ,(make-list 3 :initial-element `(nil :eval (setf (ip-local-checksums np) (not (ip-local-checksums np))) :bindings ((np ',np)))) :DOCUMENTATION "Click to toggle local checksums" :BINDINGS ((np ',np))) :function ip-local-checksums (,np) NIL (" Local checksums: ~A"))) (tv:scroll-parse-item `(:mouse-item (nil :buttons ,(make-list 3 :initial-element `(nil :eval (setf (ip-gateway np) (not (ip-gateway np))) :bindings ((np ',np)))) :DOCUMENTATION "Click to toggle IP gateway functions" :BINDINGS ((np ',np))) :function ip-gateway (,np) NIL ("IP gateway: ~A")) `(:function ip-packets-forwarded (,np) NIL (" forwarded ~D")) `(:function ip-packets-queued (,np) NIL (" queued ~D")) `(:function ip-not-for-me (,np) NIL (" not for me ~D")) `(:function ip-no-forwarding-address (,np) NIL (" no address ~D")) `(:function ip-cant-forward (,np) NIL (" queue full ~D")) `(:function ip-ttl-expired (,np) NIL (" TTL expired ~D"))) (TV:SCROLL-MAINTAIN-LIST #'(LAMBDA () (ip-protocols np)) 'net:peek-transport-protocol))) (defvar *ip-stream* nil "ip-network-protocol for DoD IP") ;;; This is the transport-protocol structure for protocols above IP (defstream ip-transport-protocol (transport-protocol (network-protocol *ip-stream*)) "TP-" (next-identification 0) ; Next datagram identification (fragment-list nil) ; list of receive buffers being used for fragment reassembly (icmp-notification-function nil) ; function to receive ICMP notifications: ; (:unreachable :source-quench :time-exceeded) (broadcast-allowed-p nil) ; T if broadcast packets allowed ) ;;; Stuff to deal with IP headers and options. (defvar *free-ip-headers* nil "List of free IP header arrays") (defun get-ip-header () "returns an array to hold an IP header." (do ((header nil)) (header header) (if *free-ip-headers* (without-interrupts (setq header (pop *free-ip-headers*))) (setq header (zl:make-array 60 :element-type '(unsigned-byte 8) :fill-pointer 0 :leader-length 2 :named-structure-symbol 'ip-header))))) (defun free-ip-header (ih) (without-interrupts (push ih *free-ip-headers*)) nil) (defun ip-header-p (ih) (eq (named-structure-p ih) 'ip-header)) ;;; IP HEADER FIELDS (defconstant ip-version-number 4) ; This version of DoD IP (defmacro ih-version (ip) "IP version number" `(ldb (byte 4 4) (aref ,ip 0))) (defmacro ih-ihl (ip) "IP header length in 32 bit units" `(ldb (byte 4 0) (aref ,ip 0))) (defmacro ih-ihl-words (ip) "IP header length in 16 bit units" `(* 2 (ldb (byte 4 0) (aref ,ip 0)))) (defmacro ih-ihl-bytes (ip) "IP header length in 8 bit units" `(* 4 (ldb (byte 4 0) (aref ,ip 0)))) (defmacro ih-tos (ip) "Precedence and type of service" `(aref ,ip 1)) (defmacro ih-precedence (ip) "Precedence" `(ldb (byte 3 5) (aref ,ip 1))) (eval-when (load compile eval) (defvar *ih-precedence-alist* nil "Valid Internet header precedence values")) (defmacro define-ih-precedence (name value) `(eval-when (load compile eval) (unless (assoc ,name *ih-precedence-alist*) (push (cons ,name ,value) *ih-precedence-alist*)))) (define-ih-precedence :network-control 7) (define-ih-precedence :internetwork-control 6) (define-ih-precedence :critic/ecp 5) (define-ih-precedence :flash-override 4) (define-ih-precedence :flash 3) (define-ih-precedence :immediate 2) (define-ih-precedence :priority 1) (define-ih-precedence :routine 0) (defmacro ih-delay (ip) "Delay" `(ldb (byte 1 4) (aref ,ip 1))) (eval-when (load compile eval) (defvar *ih-delay-alist* nil "Valid Internet header delay values")) (defmacro define-ih-delay (name value) `(eval-when (load compile eval) (unless (assoc ,name *ih-delay-alist*) (push (cons ,name ,value) *ih-delay-alist*)))) (define-ih-delay :low 1) (define-ih-delay :normal 0) (defmacro ih-throughput (ip) "Throughput" `(ldb (byte 1 3) (aref ,ip 1))) (eval-when (load compile eval) (defvar *ih-throughput-alist* nil "Valid Internet header throughput values")) (defmacro define-ih-throughput (name value) `(eval-when (load compile eval) (unless (assoc ,name *ih-throughput-alist*) (push (cons ,name ,value) *ih-throughput-alist*)))) (define-ih-throughput :high 1) (define-ih-throughput :normal 0) (defmacro ih-reliability (ip) "Reliability" `(ldb (byte 1 2) (aref ,ip 1))) (eval-when (load compile eval) (defvar *ih-reliability-alist* nil "Valid Internet header reliability values")) (defmacro define-ih-reliability (name value) `(eval-when (load compile eval) (unless (assoc ,name *ih-reliability-alist*) (push (cons ,name ,value) *ih-reliability-alist*)))) (define-ih-reliability :high 1) (define-ih-reliability :normal 0) (defsubst ih-length (ip) "Total packet length in bytes" (dpb (aref ip 2) (byte 8 8) (aref ip 3))) (defun set-ih-length (ip val) (setf (aref ip 2) (ldb (byte 8 8) val)) (setf (aref ip 3) (ldb (byte 8 0) val)) val) (defsetf ih-length set-ih-length) (defsubst ih-identification (ip) (dpb (aref ip 4) (byte 8 8) (aref ip 5))) (defun set-ih-identification (ip val) (setf (aref ip 4) (ldb (byte 8 8) val)) (setf (aref ip 5) (ldb (byte 8 0) val)) val) (defsetf ih-identification set-ih-identification) (defmacro ih-flags (ip) `(ldb (byte 3 5) (aref ,ip 6))) (defconstant mf-flag 1) (defconstant df-flag 2) (defmacro ih-flags-mf (ip) `(ldb (byte 1 5) (aref ,ip 6))) (defmacro ih-flags-df (ip) `(ldb (byte 1 6) (aref ,ip 6))) (defmacro ih-fragment-offset (ip) `(dpb (ldb (byte 5 0) (aref ,ip 6)) (byte 5 8) (aref ,ip 7))) (defun set-ih-fragment-offset (ip val) (setf (ldb (byte 5 0) (aref ip 6)) (ldb (byte 5 0) val)) (setf (aref ip 7) (ldb (byte 8 0) val))) (defsetf ih-fragment-offset set-ih-fragment-offset) (defmacro ih-ttl (ip) `(aref ,ip 8)) (defmacro ih-protocol (ip) `(aref ,ip 9)) (defmacro ih-checksum (ip) `(dpb (aref ,ip 10) (byte 8 8) (aref ,ip 11))) (defun set-ih-checksum (ip val) (setf (aref ip 10) (ldb (byte 8 8) val)) (setf (aref ip 11) (ldb (byte 8 0) val))) (defsetf ih-checksum set-ih-checksum) (defsubst ih-source-address (ip) (dpb (aref ip 12) (byte 8 24.) (dpb (aref ip 13) (byte 8 16.) (dpb (aref ip 14) (byte 8 8) (aref ip 15))))) (defun set-ih-source-address (ip val) (setf (aref ip 12) (ldb (byte 8 24) val)) (setf (aref ip 13) (ldb (byte 8 16) val)) (setf (aref ip 14) (ldb (byte 8 8) val)) (setf (aref ip 15) (ldb (byte 8 0) val)) val) (defsetf ih-source-address set-ih-source-address) (defsubst ih-dest-address (ip) (dpb (aref ip 16) (byte 8 24.) (dpb (aref ip 17) (byte 8 16.) (dpb (aref ip 18) (byte 8 8) (aref ip 19))))) (defun set-ih-dest-address (ip val) (setf (aref ip 16) (ldb (byte 8 24) val)) (setf (aref ip 17) (ldb (byte 8 16) val)) (setf (aref ip 18) (ldb (byte 8 8) val)) (setf (aref ip 19) (ldb (byte 8 0) val)) val) (defsetf ih-dest-address set-ih-dest-address) (defselect ((:property ip-header named-structure-invoke)) (:describe (header) (describe-ih header)) (:print-self (header stream ignore ignore) (si:printing-random-object (header stream :type :no-pointer) (format stream "(~D) ~A -> ~A" (ih-protocol header) (canonical-ip (ih-source-address header)) (canonical-ip (ih-dest-address header)))))) ;;; IP header option definitions (defconstant ih-option-offset 20) ;byte-offset of start of options (defconstant max-option-length 40) ;maximum length of options in bytes (defmacro define-ih-option (name copy class number) (let ((nn (intern (string-append "IH-OPT-" name))) (copy-number (case copy (copied #o200) (not-copied 0))) (class-number (case class (control 0) (debug #o100)))) `(eval-when (load compile eval) (defconstant ,nn (+ ,copy-number ,class-number ,number))))) (define-ih-option end not-copied control 0) (define-ih-option nop not-copied control 1) (define-ih-option sec copied control 2) (define-ih-option lsrr copied control 3) (define-ih-option ssrr copied control 9) (define-ih-option rr not-copied control 7) (define-ih-option stream copied control 8) (define-ih-option ts not-copied debug 4) (eval-when (load compile eval) (defvar *ih-security-alist* nil "Valid IP Security option security types")) (defmacro define-ih-security-type (name value) `(eval-when (load compile eval) (unless (assoc ,name *ih-security-alist*) (push (cons ,name ,value) *ih-security-alist*)))) (define-ih-security-type :unclassified #b0000000000000000) (define-ih-security-type :confidential #b1111000100110101) (define-ih-security-type :efto #b0111100010011010) (define-ih-security-type :mmmm #b1011110001001101) (define-ih-security-type :prog #b0101111000100110) (define-ih-security-type :restricted #b1010111100010011) (define-ih-security-type :secret #b1101011110001000) (define-ih-security-type :top-secret #b0110101111000101) (define-ih-security-type :reserved-1 #b0011010111100010) (define-ih-security-type :reserved-2 #b1001101011110001) (define-ih-security-type :reserved-3 #b0100110101111000) (define-ih-security-type :reserved-4 #b0010010010111101) (define-ih-security-type :reserved-5 #b0001001101011110) (define-ih-security-type :reserved-6 #b1000100110101111) (define-ih-security-type :reserved-7 #b1100010011010110) (define-ih-security-type :reserved-8 #b1110001001101011) (eval-when (load compile eval) (defvar *ih-timestamp-type-alist* nil "Valid Internet header timestamp types")) (defmacro define-ih-timestamp-type (name value) `(eval-when (load compile eval) (unless (assoc ,name *ih-timestamp-type-alist*) (push (cons ,name ,value) *ih-timestamp-type-alist*)))) (define-ih-timestamp-type :ts-only 0) (define-ih-timestamp-type :ts-address 1) (define-ih-timestamp-type :ts-specified-address 3) (defmacro ih-sub-header-offset (ip) `(ih-ihl-bytes ,ip)) ;;;ICMP definitions (defconstant icmp:icmp-protocol 1 "IP protocol number of ICMP") ;;;destination-unreachable codes (defconstant icmp:icmp-network-unreachable 0) (defconstant icmp:icmp-host-unreachable 1) (defconstant icmp:icmp-protocol-unreachable 2) (defconstant icmp:icmp-port-unreachable 3) (defconstant icmp:icmp-couldnt-fragment 4) (defconstant icmp:icmp-source-route-failed 5) ;;;time-exceeded codes (defconstant icmp:icmp-ttl-exceeded 0) (defconstant icmp:icmp-fragment-timeout 1) ;;;redirect codes (defconstant icmp:icmp-redirect-network 0) (defconstant icmp:icmp-redirect-host 1) (defconstant icmp:icmp-redirect-network-and-tos 2) (defconstant icmp:icmp-redirect-host-and-tos 3) ;;; IP header display routines (defun describe-ih (ih &optional display-data-p) "Given an art-8b array, displays the IP header at the head of it" (format t "~&version: ~20t~s" (ih-version ih)) (format t "~&header-length: ~20t~s" (ih-ihl ih)) (format t "~&tos: ~20t~s" (ih-tos ih)) (describe-tos (ih-tos ih)) (format t "~&length: ~20t~s" (ih-length ih)) (format t "~&identification: ~20t~s" (ih-identification ih)) (format t "~&flags: ~20t~s" (ih-flags ih)) (describe-flags (ih-flags ih)) (format t "~&fragment-offset: ~20t~s" (ih-fragment-offset ih)) (format t "~&time-to-live: ~20t~s" (ih-ttl ih)) (format t "~&protocol: ~20t~s" (ih-protocol ih)) (format t "~&header-checksum: ~20t~s" (ih-checksum ih)) (format t "~&source-address: ~20t~a ~s" (canonical-ip (ih-source-address ih)) (si:get-host-from-address (ih-source-address ih) :internet)) (format t "~&dest-address: ~20t~a ~s" (canonical-ip (ih-dest-address ih)) (si:get-host-from-address (ih-dest-address ih) :internet)) (describe-ih-options ih) (when (and display-data-p (> (ih-length ih) (ih-ihl-bytes ih))) (if (numberp display-data-p) (format t "~&~D bytes of data:" display-data-p) (format t "~&rest of data:")) (do* ((offset (ih-sub-header-offset ih) (1+ offset)) (length (- (ih-length ih) offset)) (count (if (numberp display-data-p) (min length display-data-p) length)) (index 0 (1+ index))) ((= index count) nil) (if (zerop (mod index 32)) (format t "~&")) (format t "~16,2,'0r " (aref ih offset)))) ih) (defun describe-tos (tos) (format t " prec = ~a delay = ~a throughput = ~a reliability = ~a" (car (rassoc (ldb (byte 3 5) tos) *ih-precedence-alist*)) (car (rassoc (ldb (byte 1 4) tos) *ih-delay-alist*)) (car (rassoc (ldb (byte 1 3) tos) *ih-throughput-alist*)) (car (rassoc (ldb (byte 1 2) tos) *ih-reliability-alist*)))) (defun describe-flags (flags) (format t "~:[ DF~;~]~:[ MF~;~]" (zerop (logand flags df-flag)) (zerop (logand flags mf-flag)))) (defun describe-ih-options (ih) "Describes all the options in an IP header" (let ((length (ih-ihl-bytes ih))) (when (> length ih-option-offset) (format t "~&Options:") (do ((offset ih-option-offset)) ((>= offset length)) (incf offset (describe-ih-option ih offset)))))) (defun get-two-bytes-from-option (opt offset) (+ (dpb (aref opt offset) (byte 8 8) 0) (aref opt (incf offset)))) (defun get-three-bytes-from-option (opt offset) (+ (dpb (aref opt offset) (byte 8 16) 0) (dpb (aref opt (incf offset)) (byte 8 8) 0) (aref opt (incf offset)))) (defun get-four-bytes-from-option (opt offset) (+ (dpb (aref opt offset) (byte 8 24) 0) (dpb (aref opt (incf offset)) (byte 8 16) 0) (dpb (aref opt (incf offset)) (byte 8 8) 0) (aref opt (incf offset)))) (defun describe-ih-option (opt offset) "Describes an IP header option and returns the length of the option" (case (aref opt offset) (#.ih-opt-end (format t " END(1)") 1) (#.ih-opt-nop (format t " NOP(1)") 1) (#.ih-opt-sec (format t " SEC(11)") (describe-ih-security-option opt offset)) (#.ih-opt-lsrr (format t " LSRR") (describe-ih-route opt offset)) (#.ih-opt-ssrr (format t " SSRR") (describe-ih-route opt offset)) (#.ih-opt-rr (format t " RR") (describe-ih-route opt offset)) (#.ih-opt-stream (format t " STREAM(4)~D" (get-two-bytes-from-option opt (+ 2 offset))) 4) (#.ih-opt-ts (format t " TIME") (describe-ih-timestamp opt offset)) (otherwise (format t " UNKNOWN:~D(~D)" (aref opt offset) (aref opt (1+ offset))) (aref opt (1+ offset))))) (defun describe-ih-security-option (opt offset) "Describes an IP Security option and returns its length" (format t "SEC=~A,COMP=~16,4,'0r,HAND=~16,4,'0r,TCC=~16,6,'0r" (car (rassoc (get-two-bytes-from-option opt (+ 2 offset)) *ih-security-alist*)) (get-two-bytes-from-option opt (+ 4 offset)) (get-two-bytes-from-option opt (+ 6 offset)) (get-three-bytes-from-option opt (+ 8 offset))) 11) (defun describe-ih-route (opt offset &aux address) "Describes an IP routing option and returns its length" (let* ((length (aref opt (incf offset))) (pointer (aref opt (incf offset))) (index (1- (truncate pointer 4)))) (format t "(~D)" length) (incf offset) (dotimes (i (truncate (- length 3) 4)) (setq address (get-four-bytes-from-option opt offset)) (incf offset 4) (format t "~:[~;,~]~:[~;*~]~A" (plusp i) (= index i) (canonical-ip address))) (if (> pointer length) (format t "*")) length)) (defun describe-ih-timestamp (opt offset &aux timestamp address index) "Describes an IP Timestamp option and returns its length" (let* ((length (aref opt (incf offset))) (pointer (aref opt (incf offset))) (ovf-and-type (aref opt (incf offset))) (overflow (ldb (byte 4 4) ovf-and-type)) (type (ldb (byte 4 0) ovf-and-type))) (format t "(~D,~D)" length overflow) (incf offset) (case (car (rassoc type *ih-timestamp-type-alist*)) (:ts-only (setq index (truncate (- pointer 5) 4)) (dotimes (i (truncate (- length 4) 4)) (setq timestamp (get-four-bytes-from-option opt offset)) (incf offset 4) (format t "~:[~;,~]~:[~;*~]~D" (plusp i) (= index i) timestamp)) (if (> pointer length) (format t "*"))) ((:ts-address :ts-specified-address) (setq index (truncate (- pointer 5) 8)) (dotimes (i (truncate (- length 4) 8)) (setq address (get-four-bytes-from-option opt offset)) (incf offset 4) (setq timestamp (get-four-bytes-from-option opt offset)) (incf offset 4) (format t "~:[~;,~]~:[~;*~]~A:~D" (plusp i) (= index i) (canonical-ip address) timestamp)) (if (> pointer length) (format t "*")))) length)) ;;;Checksum routines: used by IP, ICMP, UDP, and TCP (defsubst build-checksum-from-bytes (low-sum high-sum) "%ip-checksum and pseudo-header-checksum both accumulate low-sum and high-sum separately. This routine adds the low-sum carries into the high-sum and the high-sum carries into the low-sum and returns the 16 bit result. The miracles of ones-complement..." (incf high-sum (ash low-sum -8)) (do ((sum (dpb high-sum (byte 24 8) low-sum))) ((<= sum #xffff) sum) (setq sum (+ (ldb (byte 16 0) sum) (ldb (byte 16 16) sum))))) (defun %ip-checksum (array sum count odd-flag) "The primitive checksum function." (check-type array (satisfies byte-array-or-string-p)) (let ((high-sum (logand #xff (ash sum -8))) (low-sum (logand #xff sum)) (offset 0)) (unless (zerop count) (tagbody (when odd-flag (go get-low-byte)) top (incf high-sum (aref array offset)) (incf offset) get-low-byte (unless (= offset count) (incf low-sum (aref array offset)) (incf offset) (unless (= offset count) (go top))))) (build-checksum-from-bytes low-sum high-sum))) (defun checksum (buffers sum) "Calculate the checksum used by IP, ICMP, UDP, and TCP: The ones-complement of the the ones-complement sum of the data taken 16 bits at a time. This routine is optimized to checksum a list of BUFFERS; use checksum-1 to checksum a single buffer. SUM is the initial sum passed in. For TCP, UDP, use psuedo-header-checksum to calculate this." (let ((odd-flag nil)) (dolist (array buffers) (let* ((count (length array)) (odd (oddp count))) (setq sum (select-processor (:lambda ;Microcode! (compiler:%ip-checksum array sum count odd-flag)) ((:falcon :explorer :cadr) (%ip-checksum array sum count odd-flag))) ) (setq odd-flag (if odd-flag (not odd) odd)))) (logxor #xffff sum))) (defsubst checksum-1 (array sum count) (logxor #xffff (select-processor (:lambda ;Microcode! (compiler:%ip-checksum array sum count nil)) ((:falcon :explorer :cadr) (%ip-checksum array sum count nil))) )) (defun pseudo-header-checksum (ip length) "Calculate the pseudo-header checksum for UDP and TCP based on IP source and destination addresses, IP protocol number, and total data length." (let ((high-sum (+ (aref ip 12) (aref ip 14) ;high bytes of source address... (aref ip 16) (aref ip 18) ;high bytes of destination address... (ash length -8))) ;and high byte of length (low-sum (+ (aref ip 13) (aref ip 15) ;low bytes of source address (aref ip 17) (aref ip 19) ;low bytes of destination address (aref ip 9) ;protocol number... (logand length #xff)))) ;and low byte of length (build-checksum-from-bytes low-sum high-sum))) ;;; IP header building functions (defun make-ip-header (&key (ttl 60) (may-fragment t) (protocol 0) source destination (precedence :routine) (delay :normal) (throughput :normal) (reliability :normal) security route stream timestamp &aux option-length length ih) (declare (special security route stream timestamp)) (when precedence (check-type precedence #.(make-key-list *ih-precedence-alist*))) (when delay (check-type delay #.(make-key-list *ih-delay-alist*))) (when throughput (check-type throughput #.(make-key-list *ih-throughput-alist*))) (when reliability (check-type reliability #.(make-key-list *ih-reliability-alist*))) (assert (<= (setq option-length (find-option-length)) max-option-length) (security stream route timestamp) "Total option length > ~D: security = ~D stream = ~D route = ~D timestamp = ~D" max-option-length (security-option-length security) (stream-option-length stream) (route-option-length route) (timestamp-option-length timestamp)) (setq length (+ ih-option-offset option-length)) (if source (setq source (validate-address source)) (setq source (ip-default-address *ip-stream*))) (when destination (setq destination (validate-address destination))) ;; All is well. Adjust the route by putting ultimate destination at end. (cond ((member (car route) '(:loose :strict)) ;;A route is specified (unless destination ;;If no destination, we will remove an address from the route (decf option-length 4) (decf length 4)) (psetq destination (second route) route (append (list (first route)) (cddr route) (and destination (list destination))))) ((null destination) ;;No route and no destination -- assume will be set later with set-destination-address (setq destination 0))) ;;Now that we've verified all the parameters, build the header (setq ih (get-ip-header)) (setf (fill-pointer ih) length) (setf (ih-version ih) ip-version-number) (setf (ih-ihl ih) (ceiling length 4)) (setf (ih-tos ih) 0) (setf (ih-precedence ih) (cdr (assoc precedence *ih-precedence-alist* :test #'eq))) (setf (ih-delay ih) (cdr (assoc delay *ih-delay-alist* :test #'eq))) (setf (ih-throughput ih) (cdr (assoc throughput *ih-throughput-alist* :test #'eq))) (setf (ih-reliability ih) (cdr (assoc reliability *ih-reliability-alist* :test #'eq))) (setf (ih-length ih) length) (setf (ih-identification ih) 0) (setf (ih-flags ih) (if may-fragment 0 df-flag)) (setf (ih-fragment-offset ih) 0) (setf (ih-ttl ih) ttl) (setf (ih-protocol ih) protocol) (setf (ih-checksum ih) 0) (setf (ih-source-address ih) source) (setf (ih-dest-address ih) destination) (when (plusp option-length) (make-ip-options ih :security security :stream stream :route route :timestamp timestamp)) ih) (defun validate-address (address) "Given something that parses into an Internet address, return the address. Keeps prompting user until a valid Internet address is found" (let ((parsed-address nil)) (assert (setq parsed-address (parse-internet-address address)) (address) "~S is not a valid Internet address specification" address) parsed-address)) (defun find-option-length () (declare (special security stream route timestamp)) (when security (check-ih-security)) (when route (check-ih-route)) (when stream (check-type stream (unsigned-byte 16))) (when timestamp (check-ih-timestamp)) (* (ceiling (+ (security-option-length security) (stream-option-length stream) (route-option-length route) (timestamp-option-length timestamp)) 4) 4)) (defun security-option-length (security) (if security 11 0)) (defun stream-option-length (stream) (if stream 4 0)) (defun route-option-length (route) (if route (case (car route) ((:loose :strict) (+ 3 (* 4 (length (cdr route))))) (:record (+ 3 (* 4 (cadr route))))) 0)) (defun timestamp-option-length (timestamp) (if timestamp (case (car timestamp) (:ts-only (+ 4 (* 4 (cadr timestamp)))) (:ts-address (+ 4 (* 8 (cadr timestamp)))) (:ts-specified-address (+ 4 (* 8 (length (cdr timestamp)))))) 0)) (defun check-ih-security () (declare (special security)) (check-type (first security) #.(make-key-list *ih-security-alist*)) (check-type (second security) (unsigned-byte 16)) (check-type (third security) (unsigned-byte 16)) (check-type (fourth security) (unsigned-byte 24)) t) (defun check-ih-route () (declare (special route)) (cond ((null route) t) ((listp route) (check-type (first route) (member :loose :strict :record)) (case (first route) ((:loose :strict) (assert (<= (length (cdr route)) 9) ((cdr route)) "More than 9 addresses in route") (do* ((route-list (copy-list (cdr route))) (rest route-list (cdr rest))) ((null rest) (setf (cdr route) route-list)) (setf (car rest) (validate-address (car rest))))) (:record (check-type (second route) (integer 1 9)))) t) (t nil))) (defun check-ih-timestamp () (declare (special timestamp)) (cond ((null timestamp) t) ((listp timestamp) (check-type (first timestamp) #.(make-key-list *ih-timestamp-type-alist*)) (case (first timestamp) (:ts-only (check-type (second timestamp) (integer 1 9))) (:ts-address (check-type (second timestamp) (integer 1 4))) (:ts-specified-address (assert (<= (length (cdr timestamp)) 4) ((cdr timestamp)) "More than 4 addresses in timestamp") (do* ((timestamp-list (copy-list (cdr timestamp))) (rest timestamp-list (cdr rest))) ((null rest) (setf (cdr timestamp) timestamp-list)) (setf (car rest) (validate-address (car rest)))))) t) (t nil))) (defun make-ip-options (ih &key security stream route timestamp) (when (or security stream route timestamp) (let ((offset ih-option-offset)) (when security (setq offset (make-security-option ih security offset))) (when stream (setq offset (make-stream-option ih stream offset))) (when route (setq offset (make-route-option ih route offset))) (when timestamp (setq offset (make-timestamp-option ih timestamp offset))) (do () ((zerop (rem offset 4))) (incf offset (store-one-byte-in-option ih-opt-end ih offset)))))) (defun make-security-option (bytes security offset) (incf offset (store-one-byte-in-option ih-opt-sec bytes offset)) (incf offset (store-one-byte-in-option 11 bytes offset)) (incf offset (store-two-bytes-in-option (cdr (assoc (first security) *ih-security-alist* :test #'eq)) bytes offset)) (incf offset (store-two-bytes-in-option (second security) bytes offset)) (incf offset (store-two-bytes-in-option (third security) bytes offset)) (incf offset (store-three-bytes-in-option (fourth security) bytes offset)) offset) (defun make-stream-option (bytes stream offset) (incf offset (store-one-byte-in-option ih-opt-stream bytes offset)) (incf offset (store-one-byte-in-option 4 bytes offset)) (incf offset (store-two-bytes-in-option stream bytes offset)) offset) (defun make-route-option (bytes route offset &aux type length) (case (car route) (:loose (setq type ih-opt-lsrr length (+ 3 (* 4 (length (cdr route)))))) (:strict (setq type ih-opt-ssrr length (+ 3 (* 4 (length (cdr route)))))) (:record (setq type ih-opt-rr length (+ 3 (* 4 (cadr route)))))) (incf offset (store-one-byte-in-option type bytes offset)) (incf offset (store-one-byte-in-option length bytes offset)) (incf offset (store-one-byte-in-option 4 bytes offset)) (case (car route) ((:loose :strict) (dolist (address (cdr route)) (incf offset (store-four-bytes-in-option address bytes offset)))) (:record (dotimes (i (* 4 (cadr route))) (incf offset (store-one-byte-in-option 0 bytes offset))))) offset) (defun make-timestamp-option (bytes timestamp offset &aux length) (case (car timestamp) (:ts-only (setq length (+ 4 (* 4 (cadr timestamp))))) (:ts-address (setq length (+ 4 (* 8 (cadr timestamp))))) (:ts-specified-address (setq length (+ 4 (* 8 (length (cdr timestamp))))))) (incf offset (store-one-byte-in-option ih-opt-ts bytes offset)) (incf offset (store-one-byte-in-option length bytes offset)) (incf offset (store-one-byte-in-option 5 bytes offset)) (incf offset (store-one-byte-in-option (cdr (assoc (car timestamp) *ih-timestamp-type-alist* :test #'eq)) bytes offset)) (case (car timestamp) ((:ts-only :ts-address) (dotimes (i (- length 4)) (incf offset (store-one-byte-in-option 0 bytes offset)))) (:ts-specified-address (dolist (address (cdr timestamp)) (incf offset (store-four-bytes-in-option address bytes offset)) (dotimes (i 4) (incf offset (store-one-byte-in-option 0 bytes offset)))))) offset) (defun store-one-byte-in-option (byte array index) (setf (aref array index) byte) 1) (defun store-two-bytes-in-option (val array index) (store-one-byte-in-option (ldb (byte 8 8) val) array index) (store-one-byte-in-option (ldb (byte 8 0) val) array (1+ index)) 2) (defun store-three-bytes-in-option (val array index) (store-one-byte-in-option (ldb (byte 8 16) val) array index) (store-one-byte-in-option (ldb (byte 8 8) val) array (1+ index)) (store-one-byte-in-option (ldb (byte 8 0) val) array (+ index 2)) 3) (defun store-four-bytes-in-option (val array index) (store-one-byte-in-option (ldb (byte 8 24) val) array index) (store-one-byte-in-option (ldb (byte 8 16) val) array (1+ index)) (store-one-byte-in-option (ldb (byte 8 8) val) array (+ index 2)) (store-one-byte-in-option (ldb (byte 8 0) val) array (+ index 3)) 4) ;;; User interface to send packets (defun send-ip-packet (stream buffers header &optional int-pkt identifier &aux gateway interface address (length 0)) "Send IP packet(s) on behalf of a transport protocol given an IP header and a list of buffers" (declare (values fragments-sent identifier)) (check-type stream ip-transport-protocol) (when (null buffers) (error "No buffers")) (unless (listp buffers) (setq buffers (ncons buffers))) (check-type header (satisfies ip-header-p)) (dolist (b buffers) (check-type b (satisfies byte-array-or-string-p)) (incf length (length b))) (cond (identifier (check-type identifier (unsigned-byte 16))) (t (setq identifier (tp-next-identification stream)) (setf (tp-next-identification stream) (mod (1+ identifier) #xffff)))) ;;first, set the fields the user has no control over (setf (ih-version header) ip-version-number) (setf (ih-length header) (ih-ihl-bytes header)) (setf (ih-flags header) (logand (ih-flags header) df-flag)) (setf (ih-fragment-offset header) 0) (setf (ih-protocol header) (tp-type stream)) (setf (ih-checksum header) 0) (unwind-protect (cond ((not (check-user-header header)) (values nil :header)) ((not (multiple-value-setq (gateway interface) (route (ih-dest-address header)))) (values nil :route)) ((not (multiple-value-setq (address interface) (send *ip-stream* :translate-address gateway interface))) (values nil :arp)) (t (send-ip-packet-on-interface header buffers interface (prog1 int-pkt (setq int-pkt nil)) identifier length address))) (when int-pkt (free-packet int-pkt)))) (defun broadcast-ip-packet (stream buffers header &optional remote-network function &aux identifier gateway interface (length 0) (sent 0)) "Send IP packet(s) on behalf of a transport protocol given an IP header and a list of buffers" (declare (values fragments-sent identifier)) (check-type stream ip-transport-protocol) (when (null buffers) (error "No buffers")) (unless (listp buffers) (setq buffers (ncons buffers))) (check-type header (satisfies ip-header-p)) (dolist (b buffers) (check-type b (satisfies byte-array-or-string-p)) (incf length (length b))) (setq identifier (tp-next-identification stream)) (setf (tp-next-identification stream) (mod (1+ identifier) #xffff)) ;;first, set the fields the user has no control over (setf (ih-version header) ip-version-number) (setf (ih-length header) (ih-ihl-bytes header)) (setf (ih-flags header) (logand (ih-flags header) df-flag)) (setf (ih-fragment-offset header) 0) (setf (ih-protocol header) (tp-type stream)) (setf (ih-checksum header) 0) (when (check-user-header header) (if remote-network (when (multiple-value-setq (gateway interface) (route (ip-broadcast-address-from-address remote-network))) (setq sent (or (broadcast-ip-packet-on-interface header buffers interface identifier length function) 0))) (dolist (interface net:*network-interfaces*) (incf sent (or (broadcast-ip-packet-on-interface header buffers interface identifier length function) 0))))) (values sent identifier)) (defun broadcast-ip-packet-on-interface (header buffers interface identifier length &optional function &aux (frags 0)) (cond ((net:ni-broadcast-address interface) (let ((remote-network (cadr (assoc :internet (net:ni-address-alist interface))))) (when remote-network (set-destination-address header (ip-broadcast-address-from-address remote-network)) (when function (funcall function header buffers)) (send-ip-packet-on-interface header buffers interface nil identifier length (net:ni-broadcast-address interface))))) (t (dolist (at (net:ni-address-translations interface)) (when (eq (net:at-protocol at) :internet) (set-destination-address header (ip-broadcast-address-from-address (net:at-protocol-address at))) (when function (funcall function header buffers)) (incf frags (send-ip-packet-on-interface header buffers interface nil identifier length (net:at-hardware-address at))))) frags))) (defun send-ip-packet-on-interface (header buffers interface int-pkt identifier length address) (declare (values fragments-sent identifier)) (do* ((max-data (net:ni-maximum-data-length interface)) (data-left length (- data-left count)) (header-length (header-length header t)) (offset 0 (+ offset count)) (count (min (- max-data header-length) data-left) (min (- max-data header-length) data-left)) (fragments-sent 0 (1+ fragments-sent))) ((zerop data-left) (values fragments-sent identifier)) (when (< count data-left) ;fragmentation needed (unless (zerop (ih-flags-df header)) ; DF set? (return (values 0 nil))) ; yes -- can't send (setq count (* 8 (truncate count 8)))) ;fragment -- 8 byte chunks (unless (send *ip-stream* :send (build-ip-fragment header buffers offset count interface (prog1 int-pkt (setq int-pkt nil)) identifier) (+ header-length count) interface address) ;;Send failed -- break out of loop (return (values fragments-sent identifier))) (when (and (> data-left count) (zerop fragments-sent)) (setq header-length (header-length header nil))))) (defun build-ip-fragment (header buffers offset count interface &optional int-pkt identifier &aux packet) "Creates an IP fragment from user HEADER and data BUFFERS. OFFSET and COUNT determine the portion of the buffer included in this fragment. If OFFSET is 0 and COUNT equals the buffer's fill-pointer, the whole buffer fits." (setq int-pkt (if int-pkt (original-array int-pkt) (allocate-packet))) (let* ((header (net:ni-sent-header-length interface)) (trailer (net:ni-sent-trailer-length interface)) (total-bytes (- (* 2 (array-length int-pkt)) header trailer))) (setq packet (make-array total-bytes :element-type '(unsigned-byte 8) :displaced-to int-pkt :displaced-index-offset header :fill-pointer 0))) (setq offset (* 8 (truncate offset 8))) ;align to 8 byte boundary ;;First, copy in the IP header (copy-ip-header header ;copy from user header packet ;to packet (zerop offset)) ;first-fragment-p (when identifier (setf (ih-identification packet) identifier)) (setf (ih-fragment-offset packet) (truncate offset 8)) ;8-byte boundary (unless (= (+ offset count) (apply '+ (mapcar 'length buffers))) (setf (ih-flags-mf packet) 1)) (setf (ih-length packet) (+ (ih-ihl-bytes packet) count)) (store-header-checksum packet) ;;set the fill pointer of the packet to reflect size of ip packet (setf (fill-pointer packet) (+ (ih-ihl-bytes packet) count)) (setf (fill-pointer int-pkt) (ceiling (fill-pointer packet) 2)) ;;Copy proper portion of user's data buffers into packet. (do* ((b buffers (cdr b)) ;(car b) is buffer we are looking at (doff (ih-ihl-bytes packet)) ;destination (packet) offset in bytes (start 0 (+ start length)) ;offset of start of current buffer (length 0)) ;length of current buffer ((or (null b) (zerop count))) ;quit when run out of buffers or data left to copy (setq length (length (car b))) (when (and (<= start offset) (<= offset (+ start length))) (let* ((off (- offset start)) (len (min count (- length off)))) (copy-array-portion (car b) off (+ off len) packet doff (+ doff len)) (decf count len) (incf offset len) (incf doff len)))) (unless (zerop count) (cerror "continue" "Couldn't copy enough data from supplied buffers")) ;;return the int-pkt we built a packet in int-pkt) (defun copy-ip-header (from to first-fragment-p) (let* ((length (ih-ihl-bytes from))) ;length in bytes (cond ((or first-fragment-p (= length ih-option-offset)) (copy-array-portion from 0 length to 0 length)) (t (copy-array-portion from 0 ih-option-offset to 0 ih-option-offset) (do* ((source-offset ih-option-offset) (dest-offset ih-option-offset) olength option) ((>= source-offset length) (setf (ih-ihl to) (ceiling dest-offset 4)) (do () ((zerop (rem dest-offset 4))) (setf (aref to dest-offset) ih-opt-end) (incf dest-offset))) (case (setq option (aref from source-offset)) ((#.ih-opt-end #.ih-opt-nop) (incf source-offset 1)) (otherwise (setq olength (aref from (1+ source-offset))) (unless (zerop (logand option #o200)) (copy-array-portion from source-offset (+ source-offset olength) to dest-offset (+ dest-offset olength)) (incf dest-offset olength)) (incf source-offset olength))))))) to) (defun set-destination-address (header address) "Sets the ultimate destination address in the supplied IP header" (let* ((length (ih-ihl-bytes header))) (setq address (validate-address address)) (when (> length ih-option-offset) (do ((offset ih-option-offset)) ((>= offset length)) (case (aref header offset) ((#.ih-opt-end #.ih-opt-nop) (incf offset 1)) ((#.ih-opt-ssrr #.ih-opt-lsrr) (let* ((length (aref header (1+ offset))) (count (truncate length 4))) (store-four-bytes-in-option address header (+ offset 3 (* 4 (1- count)))) (return-from set-destination-address header))) (otherwise (incf offset (aref header (1+ offset))))))) ;;No options, or no source route (setf (ih-dest-address header) address) header)) (defun set-precedence (header precedence) "Sets the precedence in an IP header" (setf (ih-precedence header) precedence)) (defun ip-route-option-present (header) "Return T if an IP routing option appears in the header" "Sets the ultimate destination address in the supplied IP header" (do ((length (ih-ihl-bytes header)) (offset ih-option-offset)) ((>= offset length) nil) (case (aref header offset) ((#.ih-opt-end #.ih-opt-nop) (incf offset 1)) ((#.ih-opt-ssrr #.ih-opt-lsrr) (return t)) (otherwise (incf offset (aref header (1+ offset))))))) (defun reverse-route (header) "Given an IP header with (possibly) a recorded route, reverse the route, allowing the header to be used to reach the original sender" (let* ((source (ih-source-address header)) (destination (ih-dest-address header)) (length (ih-ihl-bytes header))) (when (> length ih-option-offset) (do ((offset ih-option-offset)) ((>= offset length)) (case (aref header offset) ((#.ih-opt-end #.ih-opt-nop) (incf offset 1)) ((#.ih-opt-rr #.ih-opt-ssrr #.ih-opt-lsrr) (let* ((length (aref header (1+ offset))) (count (truncate length 4)) route) (push source route) (do ((i count (1- i)) (off (+ offset 3) (+ 4 off))) ((zerop i)) (push (get-four-bytes-from-option header off) route)) (incf offset 2) (incf offset (store-one-byte-in-option 4 header offset)) (setq source (pop route)) (do ((i count (1- i))) ((zerop i)) (incf offset (store-four-bytes-in-option (pop route) header offset))))) (otherwise (incf offset (aref header (1+ offset))))))) (setf (ih-source-address header) destination) (setf (ih-dest-address header) source)) header) (defun find-security-and-precedence (header) "Given an IP header, return the security and precedence options from it" (declare (values precedence security compartment handling tcc)) (let ((precedence (ih-precedence header)) (security 0) (compartment 0) (handling 0) (tcc 0) (length (ih-ihl-bytes header))) (when (> length ih-option-offset) (do ((offset ih-option-offset)) ((>= offset length)) (case (aref header offset) ((#.ih-opt-end #.ih-opt-nop) (incf offset 1)) (#.ih-opt-sec (setq security (get-two-bytes-from-option header (+ 2 offset))) (setq compartment (get-two-bytes-from-option header (+ 4 offset))) (setq handling (get-two-bytes-from-option header (+ 6 offset))) (setq tcc (get-three-bytes-from-option header (+ 8 offset))) (return t)) (otherwise (incf offset (aref header (1+ offset))))))) (values precedence security compartment handling tcc))) (defun header-length (header first-fragment-p) "Given an IP header, return header length in bytes. first-fragment-p is used in calculating length of options -- not all are copied in subsequent fragments" (let ((length (ih-ihl-bytes header))) (if (or first-fragment-p (= length ih-option-offset)) length (do* ((result ih-option-offset) (offset ih-option-offset) option) ((>= offset length) (* 4 (ceiling result 4))) (case (setq option (aref header offset)) ((#.ih-opt-end #.ih-opt-nop) (incf offset 1)) (otherwise (unless (zerop (logand option #o200)) (incf result (aref header (1+ offset)))) (incf offset (aref header (1+ offset))))))))) (defun check-user-header (ih) (and (>= (ih-ihl ih) 5) (member (ih-source-address ih) (ip-addresses *ip-stream*)) (or (= (ih-ihl-bytes ih) ih-option-offset) (check-user-options ih)))) (defun check-user-options (ih) "Checks the options ina user-supplied IP header and returns t if all is well" (let ((length (ih-ihl-bytes ih))) (or (= length ih-option-offset) (do ((offset ih-option-offset) (ok t) olength) ((or (not ok) (>= offset length)) ok) (multiple-value-setq (olength ok) (check-user-option ih offset)) (if ok (incf offset olength)))))) (defun check-user-option (opt offset) "Checks an IP header option and returns two values: option length, option-ok" (declare (values length option-ok-p)) (case (aref opt offset) (#.ih-opt-end (values 1 t)) (#.ih-opt-nop (values 1 t)) (#.ih-opt-sec (check-user-security opt offset)) (#.ih-opt-stream (values 4 t)) (#.ih-opt-ts (check-user-timestamp opt offset)) ((#.ih-opt-lsrr #.ih-opt-ssrr #.ih-opt-rr) (check-user-route opt offset)) (otherwise (values (aref opt (1+ offset)) nil)))) (defun check-user-security (opt offset) (let ((length (aref opt (1+ offset)))) (values length (and (= length 11) (rassoc (get-two-bytes-from-option opt (+ offset 2)) *ih-security-alist*))))) (defun check-user-timestamp (opt offset) (let ((length (aref opt (1+ offset))) (pointer (aref opt (+ offset 2)))) (values length (and (<= length (- (array-length opt) offset)) (rassoc (ldb (byte 4 0) (aref opt (+ offset 3))) *ih-timestamp-type-alist*) ;;make sure pointer in valid range. ;;Could insist it be 5, i.e. at start of route (<= 5 pointer (1+ length)))))) (defun check-user-route (opt offset) (let ((length (aref opt (1+ offset))) (pointer (aref opt (+ offset 2)))) (values length (and (<= length (- (array-length opt) offset)) ;;make sure pointer in valid range. ;;Could insist it be 4, i.e. at start of route (<= 4 pointer (1+ length)) (zerop (rem pointer 4)))))) (defun store-header-checksum (ip &optional force) (setf (ih-checksum ip) 0) (and (not force) ;If we are not forcing checksum to be calculated, (not (ip-local-checksums *ip-stream*)) ; local checksums are disabled (local-host-p (ih-dest-address ip)) ; and this is a local packet (return-from store-header-checksum t)) ; Then don't bother with checksum (setf (ih-checksum ip) (checksum-1 ip 0 (ih-ihl-bytes ip)))) ;;; IP received packet handling (defun check-header-checksum (ip) (when (and (not (ip-local-checksums *ip-stream*)) (local-host-p (ih-source-address ip))) (return-from check-header-checksum t)) (let ((sum (checksum-1 ip 0 (ih-ihl-bytes ip)))) (values (zerop sum) sum))) (defun check-net-header (int-pkt) "Check the IP header and return t if bad, with offset of badness" (declare (values not-ok pointer)) (cond ((/= (ih-version int-pkt) ip-version-number) (values t 0)) ((< (ih-ihl int-pkt) 5) (values t 0)) ((< (ih-length int-pkt) (ih-ihl-bytes int-pkt)) (values t 3)) (t nil))) (defun process-packet-options (packet) "Process the IP header options. Return t and pointer if bad option encountered" (declare (values not-ok pointer)) (let ((length (ih-ihl-bytes packet))) (when (> length ih-option-offset) (do ((offset ih-option-offset) (ok t) olength pointer) ((or (not ok) (>= offset length)) (values (not ok) pointer)) (multiple-value-setq (olength ok pointer) (process-ih-option packet offset)) (if ok (incf offset olength)))))) (defun process-ih-option (opt offset) "Process one IP header option and return three values: length, option-ok-p, pointer to badness" (declare (values length option-ok-p pointer)) (case (aref opt offset) (#.ih-opt-end (values 1 t nil)) (#.ih-opt-nop (values 1 t nil)) (#.ih-opt-sec (values 11 t nil)) (#.ih-opt-stream (values 4 t nil)) (#.ih-opt-ts (process-ih-timestamp opt offset)) ((#.ih-opt-lsrr #.ih-opt-ssrr #.ih-opt-rr) (process-ih-route opt offset)) (otherwise (values (aref opt (1+ offset)) nil offset)))) (defun process-ih-timestamp (opt offset) "Process an IP Timestamp option and return two values: length, option-ok-p" (declare (values length option-ok-p)) (let* ((length (aref opt (1+ offset))) (pointer (aref opt (+ 2 offset))) (index (1- pointer)) (flags (aref opt (+ 3 offset)))) (when (> pointer length) (incf (ldb (byte 4 4) (aref opt (+ 3 offset)))) ;increment overflow byte (return-from process-ih-timestamp (values length t))) (case (car (rassoc (ldb (byte 4 0) flags) *ih-timestamp-type-alist*)) (:ts-only (when (< (- length index) 4) ;error -- not enough room (return-from process-ih-timestamp (values length nil (1+ offset)))) (store-four-bytes-in-option (milliseconds-since-midnight-gmt) opt (+ offset index)) (incf (aref opt (+ 2 offset)) 4)) (:ts-address (when (< (- length index) 8) ;error -- not enough room (return-from process-ih-timestamp (values length nil (1+ offset)))) (store-four-bytes-in-option (ip-default-address *ip-stream*) opt (+ offset index)) (store-four-bytes-in-option (milliseconds-since-midnight-gmt) opt (+ 4 offset index)) (incf (aref opt (+ 2 offset)) 8)) (:ts-specified-address (when (member (get-four-bytes-from-option opt index) (ip-addresses *ip-stream*)) (incf index 4) (when (< (- length index) 4) ;error -- not enough room (return-from process-ih-timestamp (values length nil (1+ offset)))) (store-four-bytes-in-option (milliseconds-since-midnight-gmt) opt (+ offset index)) (incf (aref opt (+ 2 offset)) 8))) (otherwise (return-from process-ih-timestamp (values length nil (+ 3 offset))))) (values length t))) (defun process-ih-route (opt offset &aux address) "Process an IP routing option and return two values: length, option-ok-p" (declare (values length option-ok-p)) (let* ((option (aref opt offset)) (length (aref opt (1+ offset))) (pointer (aref opt (+ 2 offset))) (index (+ offset (1- pointer)))) (when (and (= option ih-opt-rr) (< (- length pointer) 3)) ;;No room to record address (exhausted, or < 4 bytes) (return-from process-ih-route (values length nil (1+ offset)))) (when (> pointer length) ;source route exhausted? ;;If the address in the destination field has been reached (when (or (= option ih-opt-rr) (member (ih-dest-address opt) (ip-addresses *ip-stream*))) ;;Replace the destination address with the next source address (unless (= option ih-opt-rr) (setf (ih-dest-address opt) (get-four-bytes-from-option opt index))) ;;Find my internet address on the packet's next network (multiple-value-bind (ignore interface) (route (ih-dest-address opt)) (unless interface (icmp:icmp :destination-unreachable opt icmp:icmp-source-route-failed) (return-from process-ih-route (values length t))) (setq address (cadr (assoc :internet (net:ni-address-alist interface))))) ;;And store our address in the recorded route (store-four-bytes-in-option address opt index)) ;;Point to next address in source route (incf (aref opt (+ 2 offset)) 4)) (values length t))) ;;; FRAGMENT REASSEMBLY (defun pass-up-packet (int-pkt broadcast-p interface &aux buffer header local-p) (let ((stream (cdr (assoc (ih-protocol int-pkt) (ip-protocols *ip-stream*) :test #'eq)))) (when (and stream (tp-enabled stream)) (multiple-value-setq (buffer header) (bind-fragment int-pkt stream)) (setq local-p (local-host-p (ih-source-address header))) (when buffer (when broadcast-p (incf (tp-broadcast-packets-received stream)) (unless (tp-broadcast-allowed-p stream) (return-from pass-up-packet t))) (send stream :packet-arrival buffer header local-p broadcast-p interface)) t))) (defun bind-fragment (int-pkt stream &aux count buffer header) (declare (values buffer header)) (setq count (- (ih-length int-pkt) (ih-ihl-bytes int-pkt))) (cond ((or (not (zerop (ih-fragment-offset int-pkt))) (not (zerop (ih-flags-mf int-pkt)))) (reassemble-fragments int-pkt stream count)) ((setq buffer (find-receive-buffer stream int-pkt)) (setq header (copy-ip-header int-pkt (get-ip-header) t)) (setf (fill-pointer header) (ih-ihl-bytes int-pkt)) (copy-array-portion int-pkt (ih-ihl-bytes int-pkt) (ih-length int-pkt) buffer 0 count) (setf (fill-pointer buffer) count) (values buffer header)) (t (incf (tp-packets-received-discarded stream)) (incf (tp-bytes-received-discarded stream) count) (icmp:icmp :source-quench int-pkt) nil))) (defstruct (fragment (:conc-name "FRAG-") (:print-function (lambda (frag stream ignore) (sys:printing-random-object (frag stream :type :no-pointer) (format stream "~A(~X)" (and (frag-source frag) (canonical-ip (frag-source frag))) (frag-id frag)))))) id ;fragment id source ;source address dest ;destination address buffer ;reassembly buffer header ;IP header time ;fragment expiration holes ;hole list ) ;;;a hole is just a dotted pair -- start and end offsets (defmacro hole-start (hole) `(car ,hole)) (defmacro hole-end (hole) `(cdr ,hole)) (defun find-receive-buffer (stream int-pkt &aux count) "Free expired fragments and find a receive buffer to hold datagram in int-pkt" (setq count (- (ih-length int-pkt) (ih-ihl-bytes int-pkt))) ;; move expired fragments to buffer list (when (tp-fragment-list stream) (let ((now (global:time)) (id (ih-identification int-pkt)) (source (ih-source-address int-pkt)) (dest (ih-dest-address int-pkt))) (dolist (frag (tp-fragment-list stream)) (cond ((and (= id (frag-id frag)) (= source (frag-source frag)) (= dest (frag-dest frag))) (delete-fragment frag stream)) ((time-lessp (frag-time frag) now) (incf (ip-fragment-timeouts *ip-stream*)) (when (frag-header frag) (icmp:icmp :time-exceeded (frag-header frag) icmp:icmp-fragment-timeout)) (delete-fragment frag stream)))))) ;; find first buffer with at least COUNT bytes (dolist (b (tp-receive-buffers stream)) (when (>= (array-length b) count) (setf (tp-receive-buffers stream) (delete b (tp-receive-buffers stream) :test #'eq)) (return b)))) (defun delete-fragment (frag stream) (setf (tp-fragment-list stream) (delete frag (tp-fragment-list stream) :test #'eq)) (when (frag-header frag) (free-ip-header (frag-header frag))) (push (frag-buffer frag) (tp-receive-buffers stream))) (defun reassemble-fragments (int-pkt stream count &aux frag buffer) (declare (values buffer header)) (let* ((id (ih-identification int-pkt)) (source (ih-source-address int-pkt)) (dest (ih-dest-address int-pkt)) (expiration (time-increment (global:time) (* 60 (ih-ttl int-pkt))))) ;;find matching fragment; if none, create one and initialize it (unless (setq frag (dolist (f (tp-fragment-list stream)) (and (= id (frag-id f)) (= source (frag-source f)) (= dest (frag-dest f)) (return f)))) ;; count is length of current fragment. Want a buffer big enough to hold ;; entire datagram, but total length not known until last fragment. Sigh. (unless (setq buffer (find-receive-buffer stream int-pkt)) (incf (tp-packets-received-discarded stream)) (incf (tp-bytes-received-discarded stream) count) (return-from reassemble-fragments nil)) (setq frag (make-fragment :id id :source source :dest dest :buffer buffer :time expiration)) (push (cons 0 (1- (array-length buffer))) (frag-holes frag)) (push frag (tp-fragment-list stream))) ;;copy in data, clearing holes. (unless (copy-fragment-data int-pkt frag) (delete-fragment frag stream) (incf (tp-packets-received-discarded stream)) (incf (tp-bytes-received-discarded stream) count) (return-from reassemble-fragments nil)) ;;update fragment lifetime (if (time-lessp (frag-time frag) expiration) (setf (frag-time frag) expiration)) ;;If first fragment, save IP header (when (zerop (ih-fragment-offset int-pkt)) (let ((header (copy-ip-header int-pkt (get-ip-header) t))) (setf (fill-pointer header) (ih-ihl-bytes header)) (setf (frag-header frag) header))) ;;If no more holes, pass up complete datagram (unless (frag-holes frag) (let ((header (frag-header frag)) (buffer (frag-buffer frag))) (setf (tp-fragment-list stream) (delete frag (tp-fragment-list stream) :test #'eq)) (setf (ih-length header) (+ (ih-ihl-bytes header) (fill-pointer buffer))) (setf (ih-flags-mf header) 0) (values buffer header))))) (defun copy-fragment-data (int-pkt frag) "Copy data from INT-PKT to correct part of buffer in FRAG. Adjust the holes in FRAG and set the fill-pointer of the buffer if total data length now known. Return t if whole fragment fit, nil if not copied because would be truncated." (let* ((start (* 8 (ih-fragment-offset int-pkt))) (length (- (ih-length int-pkt) (ih-ihl-bytes int-pkt))) (end (1- (+ start length)))) (when (< end (array-length (frag-buffer frag))) ;;For all the holes, copy in portion of buffer that is in hole (do* ((result nil) (holes (frag-holes frag) (cdr holes)) (hole (car holes) (car holes))) ((null holes) (setf (frag-holes frag) (nreverse result))) (if (and (<= (hole-start hole) end) (>= (hole-end hole) start)) ;;fragment overlaps this hole (let* ((first (max (hole-start hole) start)) (count (- (1+ (min (hole-end hole) end)) first)) (source-first (+ (ih-ihl-bytes int-pkt) (- first start)))) (copy-array-portion int-pkt source-first (+ source-first count) (frag-buffer frag) first (+ first count)) (if (<= start (hole-start hole)) ;;fragment overlaps front of hole -- truncate front of hole (if (zerop (ih-flags-mf int-pkt)) ;if last fragment, (setq holes nil) ;forget this and remaining holes (when (< end (hole-end hole)) ;unless overlaps end (setf (hole-start hole) (1+ end)) ;just truncate front (push hole result))) ;;else, doesn't overlap front (let (new-hole) (if (< end (hole-end hole)) (if (zerop (ih-flags-mf int-pkt)) ;doesn't overlap end... (setq holes nil) ;but last fragment; forget rest ;;not last fragment, so split hole in two (setq new-hole (cons (1+ end) (hole-end hole))))) (setf (hole-end hole) (1- start)) ;trim first hole (push hole result) ;save it (and new-hole (push new-hole result))))) (push hole result))) ;;If last fragment, set fill pointer to total data length (when (zerop (ih-flags-mf int-pkt)) (setf (fill-pointer (frag-buffer frag)) (1+ end))) ;;And return t because entire fragment fit into buffer t))) ;;; IP routing table (defstruct (route-entry (:conc-name "RE-") (:type :list)) network ;network or host gateway ;gateway to get to it interface ;network interface to get to that gateway tos ;type-of-service age ;expiration time for this entry ) (defvar *route-table* nil "List mapping network numbers to (gateway interface)") (defvar *default-network-numbers* nil "The network numbers of directly connected networks") (defvar *default-gateway* nil "The default gateway") (defvar *default-interface* nil "The interface to reach the default gateway") (defvar *network-list* nil "Association List of Networks and Subnet Masks") (defun list-route-table () (format t "~&Known Network~16TSubnet Mask") (dolist (elem *network-list*) (format t "~&~A~16T~A" (canonical-ip (car elem)) (canonical-ip (cdr elem)))) (format t "~&~%Network~16TGateway~32TTOS~40TExpires~50TInterface") (let ((now (zl:time))) (dolist (elem *route-table*) (let ((net (re-network elem)) (ni (re-interface elem))) (format t "~&~A~16T~A~32T~A~40T~A~50T~A ~A ~S" (if (zerop net) "DEFAULT" (canonical-ip net)) (canonical-ip (re-gateway elem)) (re-tos elem) (and (re-age elem) (time-difference (re-age elem) now)) (net:ni-interface ni) (net:ni-keyword ni) (net:ni-tag ni)))))) (defvar icmp:*icmp-redirect-expiration* (* 5 60 60) "Interval in ticks for ICMP redirections to expire. If NIL, they never expire.") (defun add-gateway (network gateway &optional interface tos expires &aux parsed-network parsed-gateway) (and (null interface) (null (setq interface *default-interface*)) (error "No interface specified in ADD-GATEWAY and no default interface is known.")) (assert (setq parsed-network (parse-internet-address network)) (network) "Bad internet address specified for NETWORK:~A" network) (assert (setq parsed-gateway (parse-internet-address gateway)) (gateway) "Bad internet address specified for GATEWAY: ~A" gateway) (without-interrupts (remove-gateway parsed-network tos) (when (zerop parsed-network) (setq *default-gateway* parsed-gateway) (setq *default-interface* interface)) (push (make-route-entry :network parsed-network :gateway parsed-gateway :interface interface :tos tos :age (and expires (time-increment (zl:time) expires))) *route-table*) (when expires (reset-ip-background-wakeup-time)))) (defun remove-gateway (network &optional tos &aux parsed-network) (assert (setq parsed-network (parse-internet-address network)) (network) "Bad internet address specified for NETWORK: ~A" network) (without-interrupts (when (and (zerop parsed-network) (null tos)) (setq *default-gateway* nil) (setq *default-interface* nil)) (do* ((list *route-table* (cdr list)) (item (car list) (car list))) ((null list)) (when (and (= parsed-network (re-network item)) (eql tos (re-tos item))) (setq *route-table* (delete item *route-table*)) (when (re-age item) (reset-ip-background-wakeup-time)) (return))))) ;;; The (:DEFAULT-INTERNET-ROUTING ) site option. ;;; If is an atom then it is the default gateway. Probably a very common ;;; case is that there is only one gateway, and this will suffice. ;;; Otherwise the is a list, of ( ). ;;; Hosts and gateways may be given with symbolic names (e.g. 'ALADDIN or "ALADDIN"), ;;; as an integer, or as a dotted decimal address (e.g. '192.25.3.4 or "192.25.3.4".) ;;; The may also be a address, which is like a host address ;;; except that the lower bits are zero. e.g. "192.25.3.0" (for a class-C address). ;;; = 0 means the default gateway. (defun initialize-route-table (default-internet-address) (when default-internet-address (setq *default-gateway* nil) (setq *default-interface* nil) (when (consp default-internet-address) ;;If given a list of addresses, assume the first is the default (setq default-internet-address (first default-internet-address))) (let ((r (global:get-site-option :default-internet-routing)) (default-interface nil)) (setq *route-table* nil) ;;Add entries for directly connected networks (setq *default-network-numbers* nil) (dolist (ni *network-interfaces*) (let ((my-address (second (assoc :internet (net:ni-address-alist ni) :test #'eq)))) (when my-address ;If we have an Internet address on this network... (let* ((ip-on-ni (assoc :internet (net:ni-network-alist ni) :test #'eq)) (my-network (second ip-on-ni)) (mask (third ip-on-ni))) (when my-network ;If this directly connects us to another network (when (= my-address default-internet-address) ;If this interface has my default address... (setq default-interface ni)) ;make it my default interface (let ((network-number (logand mask my-network))) (push (cons network-number mask) *default-network-numbers*) (add-gateway network-number my-address ni))))))) ;;Add entries for gateways listed in site files (when default-interface ;If we are not currently enabled for IP, nothing to do... (cond ((null r)) ((atom r) (let ((parsed-address (parse-internet-address r))) (if parsed-address (add-gateway 0 parsed-address default-interface) (tv:careful-notify nil t "Bad Internet Address in :default-internet-routing site option: ~S" r)))) ((listp r) (dolist (elem r) (let ((parsed-network (parse-internet-address (first elem))) (parsed-gateway (parse-internet-address (second elem)))) (if (and parsed-network parsed-gateway) (add-gateway parsed-network parsed-gateway default-interface) (tv:careful-notify nil t "Bad entry in :default-internet-routing site option: ~S" elem))))) (t nil)))) (let ((op-address nil)) (cond ((null si:*ethernet-hardware-controller*)) ;No network interface? Too bad... ((eq si:*ethernet-hardware-controller* si:*my-op*)) ;We talk directly to ethernet? Good... ((setq op-address (net:find-network-address-for-other-processor si:*ethernet-hardware-controller* :internet)) (add-gateway 0 op-address si:share-interface)) (t (tv:careful-notify nil t "The si:*ethernet-hardware-controller* has no Internet Address")))))) (defun setup-my-internet-address () "Reads :network-names site option and sets up *network-list*. Return two values: a list of our Internet addresses and a list of corresponding Subnet Masks" (declare (values address-list subnet-mask-list)) (flet ((find-subnet-mask (address) ;;given address and *network-list*, return subnet-mask (dolist (elt *network-list* (ip-subnet-mask address)) (let ((mask (cdr elt))) (when (= (logand (car elt) mask) (logand address mask)) (return mask)))))) (let* ((network-addresses (send si:local-host :network-addresses)) (internet-addresses (getf network-addresses :internet)) (network-names (global:get-site-option :network-names)) (subnet-masks nil)) (setq *network-list* nil) (dolist (network network-names) (dolist (domain (second network)) (when (eq (first domain) :internet) (let* ((network-number (parse-internet-address (second domain))) (subnet-mask (if (third domain) (parse-internet-address (third domain)) (ip-subnet-mask network-number)))) (push (cons network-number subnet-mask) *network-list*))))) (setq subnet-masks (mapcar #'find-subnet-mask internet-addresses)) (let ((old-internet-address (and *ip-stream* (ip-enabled *ip-stream*) (first (ip-addresses *ip-stream*)))) (new-internet-address (first internet-addresses))) (cond ((eql old-internet-address new-internet-address) ;;No change (no IP before and none now, or had same IP address) -- nothing to do ) ((null old-internet-address) ;;Didn't used to have an IP address but now we do. Can't do anything here, as this function ;;can be called either from (net:configure) or the site initialization list. In the first ;;case, IP will be started. In the second, the user must do a (net:configure). ) ((null new-internet-address) ;;Used to have an IP address but now we don't -- disable IP (send *ip-stream* :close)) (t ;;We used to have an IP address and we still do -- but it has changed. We must change the ;;addresses in the Network Interfaces so that ARP will work right (dolist (ni net:*network-interfaces*) (unless (eq (net:ni-interface ni) :loopback) (delete-from-alist :internet (net:ni-address-alist ni)) (push (list :internet new-internet-address) (net:ni-address-alist ni)))) (setf (ip-addresses *ip-stream*) (substitute new-internet-address old-internet-address (ip-addresses *ip-stream*))) ;;Must also reset all the transport protocols -- existing connections are to the old address (dolist (tp (ip-protocols *ip-stream*)) (send (cdr tp) :reset))))) (values internet-addresses subnet-masks)))) (add-initialization "Initialize IP routing table" '(initialize-route-table (setup-my-internet-address)) '(:site :normal)) (defun route (address &optional tos) "Given an internet address, choose the gateway to send a packet to" (declare (values gateway interface local-p)) ;;If this is one of our addresses, return loopback interface (when (member address (ip-addresses *ip-stream*)) (return-from route (values address *loopback-interface* t))) ;;If this is the address of host on same backplane, return share interface (and si:share-interface (arp:get-address-info address :internet nil si:share-interface nil) (return-from route (values address si:share-interface t))) ;;If address on directly connected network, send directly (dolist (interface *network-interfaces*) (let* ((ip-on-ni (assoc :internet (net:ni-network-alist interface) :test #'eq)) (my-network (second ip-on-ni)) (mask (third ip-on-ni))) (when ip-on-ni (cond ((net:ni-point-to-point interface) ;;search address cache (when (arp:get-address-info address :internet nil interface nil) ;;The address is accessible on point-to-point link (return-from route (values address interface nil)))) ((and mask (= (logand mask address) (logand mask my-network))) ;;We are directly connected to that network. Send the packet directly (return-from route (values address interface nil))))))) ;;Else, search routing table (let ((network-number (ip-network-number-from-address address))) (dolist (x *network-list*) ;;For all known networks.... (when (= (car x) (logand (cdr x) address)) ;;If this is one of them, pick out the real network number under subnet mask (return (setq network-number (car x))))) (do* ((list *route-table* (cdr list)) (item (car list) (car list)) (host-entry nil) ;Entry found for this host and any TOS (network-entry) ;Entry found for this network and this TOS (default-entry nil)) ;Entry found for this network and any TOS ((null list) (let ((win (or host-entry network-entry default-entry))) (when win (return-from route (values (re-gateway win) (re-interface win) nil))))) (cond ((= (re-network item) address) ;Host specific entry matches (cond ((eql tos (re-tos item)) ;TOS matches (return-from route (values (re-gateway item) (re-interface item) nil))) ((null (re-tos item)) ;TOS doesn't match, but this is generic (setq host-entry item)))) ((= (re-network item) network-number) ;Network entry matches (cond ((eql tos (re-tos item)) ;TOS matches (setq network-entry item)) ((null (re-tos item)) ;TOS doesn't match but this is generic (setq default-entry item))))))) ;;If all else fails, return default gateway (values *default-gateway* *default-interface* nil)) ;;;Background process -- route table update, packet forwarding, etc. (defvar *background-wakeup-time* nil "If non-NIL, the time when the background process should next wake up") (defvar *ip-background-process* (make-process "IP Background Process" :warm-boot-action 'ignore :priority 25 :arrest-reasons '(:not-running))) (defvar *maximum-queued-packets* 10 "Max number of packets queued for forwarding") (defvar *current-queued-packets* 0 "Current number of packets queued for forwarding") (defvar *queued-packet-fifo* (make-fifo) "Fifo of packets queued for forwarding") (defun initialize-ip-background-process () (without-interrupts (do ((elt (pop-fifo *queued-packet-fifo*) (pop-fifo *queued-packet-fifo*))) ((null elt)) (free-packet (first elt)))) (send *ip-background-process* :preset 'ip-background-process) (send *ip-background-process* :reset) (send *ip-background-process* :run-reason :enable) (send *ip-background-process* :revoke-arrest-reason :not-running)) (defun reset-ip-background-wakeup-time () (let ((wakeup-time nil)) (without-interrupts (dolist (item *route-table*) (let ((age (re-age item))) (when (and age (or (null wakeup-time) (time-lessp age wakeup-time))) (setq wakeup-time age)))) (setq *background-wakeup-time* wakeup-time)))) (defun queue-packet-for-forwarding (int-pkt interface destination-list) ;;Packets can be queued for two reasons: ;; They require fragmentation ;; They are being broadcast to more than one destination ;;Either case could block the network receiver waiting for an int-pkt, so the ;;background process (which doesn't mind being blocked) does the forwarding. (without-interrupts (cond ((>= *current-queued-packets* *maximum-queued-packets*) ;;Simple-minded criterion to avoid using up all int-pkts. (incf (ip-cant-forward *ip-stream*))) (t (incf (ip-packets-queued *ip-stream*)) (incf *current-queued-packets*) (push-fifo (list int-pkt interface destination-list) *queued-packet-fifo*))))) (defun ip-background-process () (do ((now nil nil)) (nil) (process-wait "Wakeup Time" #'(lambda () (and (ip-enabled *ip-stream*) (or (not (fifo-empty-p *queued-packet-fifo*)) (and *background-wakeup-time* (not (time-lessp (setq now (zl:time)) *background-wakeup-time*))))))) ;;Forward any queued packets. (do ((elt (pop-fifo *queued-packet-fifo*) (pop-fifo *queued-packet-fifo*))) ((null elt)) (let* ((int-pkt (first elt)) (interface (second elt)) (destination-list (third elt)) (header (make-array 60 :element-type '(unsigned-byte 8) :displaced-to int-pkt)) (header-length (ih-ihl-bytes header)) (length (- (ih-length header) header-length)) (buffer (make-array length :element-type '(unsigned-byte 8) :displaced-to int-pkt :displaced-index-offset header-length))) (decf *current-queued-packets*) (incf (ip-packets-forwarded *ip-stream*)) (dolist (address destination-list) (send-ip-packet-on-interface header (ncons buffer) interface nil (ih-identification header) length address)) (free-packet int-pkt))) ;;Discard expired route entries (without-interrupts (do* ((result nil) (list *route-table* (cdr list)) (item (car list) (car list))) ((null list) (setq *route-table* (nreverse result))) (let ((age (re-age item))) (when (or (null age) ;Permanent entry (time-lessp now age)) ;Transient entry but not expired (push item result))))) (reset-ip-background-wakeup-time))) (defun forward-packet (int-pkt interface source &aux destination gateway dest-interface) "Forward a packet. Returns NIL if succeeded, the packet if failed. It is the caller's responsibility to free the packet in the latter case" (cond ((not (plusp (decf (ih-ttl int-pkt)))) ;;Time-To-Live expired -- send ICMP message and drop packet (icmp:icmp :time-exceeded int-pkt icmp:icmp-ttl-exceeded) (free-packet int-pkt)) ((and (multiple-value-setq (gateway dest-interface) (route (ih-dest-address int-pkt))) (setq destination (send *ip-stream* :translate-address gateway dest-interface (ih-source-address int-pkt)))) (cond ((and (not (ip-gateway *ip-stream*)) ;We are not IP gateway (not (eq interface si:share-interface)) ; and not FROM share interface (not (eq dest-interface si:share-interface))) ; and not TO share interface ;;Destination is a processor not on this backplane and we are not being a gateway (incf (ip-not-for-me *ip-stream*)) (icmp:icmp :destination-unreachable int-pkt icmp:icmp-host-unreachable) (free-packet int-pkt)) (t ;;Successful routing and address translation -- send to the gateway (unless (ip-route-option-present int-pkt) (when (eq interface si:share-interface) ;;If packet is FROM share-interface, conclude that we are that processor's gateway ;;and set *processor-forwarding-alist* to indicate that. (let ((elt (assoc source net:*processor-forwarding-alist*))) (when elt (setf (cdr elt) t)))) (when (eq interface dest-interface) (icmp:icmp :redirect int-pkt (if (eq interface si:share-interface) icmp:icmp-redirect-host icmp:icmp-redirect-network) gateway))) (let ((count (ih-length int-pkt))) (if (> count (net:ni-maximum-data-length dest-interface)) (queue-packet-for-forwarding int-pkt dest-interface (ncons destination)) (progn (incf (ip-packets-forwarded *ip-stream*)) (store-header-checksum int-pkt t) (send *ip-stream* :send int-pkt count dest-interface destination))))))) (t ;;Either couldn't route to a gateway, or couldn't find gateway's link-level address (incf (ip-no-forwarding-address *ip-stream*)) (when (null gateway) ;We couldn't route this (icmp:icmp :destination-unreachable int-pkt icmp:icmp-host-unreachable)) (free-packet int-pkt)))) (defun pass-up-broadcast-packet (stream int-pkt interface source) "Deals with a broadcast packet from ethernet or from share interface." (cond ((ip-broadcast-address-p (ih-dest-address int-pkt)) (incf (ip-ip-broadcast-packets stream)) (pass-up-packet int-pkt t interface) (pass-broadcast-packet-to-other-processors int-pkt interface source)) ((ip-self-address-p (ih-dest-address int-pkt)) ;;Bug -- Berkeley 4.2 uses self-address as broadcast-address (incf (ip-self-address-packets stream)) (pass-up-packet int-pkt t interface) (pass-broadcast-packet-to-other-processors int-pkt interface source)) (t ;;Not really a broadcast packet. (free-packet int-pkt)))) (defun pass-broadcast-packet-to-other-processors (int-pkt interface source) (unwind-protect (when (eq si:*my-op* si:*ethernet-hardware-controller*) ;If I own the Ethernet hardware... (cond ((eq interface net:*loopback-interface*)) ;Don't forward looped-back packets ((null si:*other-processors*)) ;If there are no other processors, ignore ((eq interface si:share-interface) ;Packets from other processors go onto ethernet ;;If packet came from Share interface, forward out Ethernet interface (when (cdr (assoc source net:*processor-forwarding-alist*)) ;;Came from a processor we are being a gateway for (forward-broadcast-packet (prog1 int-pkt (setq int-pkt nil)) (find-ethernet-interface)))) (t ;Packet from Ethernet ;;If packet came from Ethernet, forward out Share interface (forward-broadcast-packet (prog1 int-pkt (setq int-pkt nil)) si:share-interface)))) (and int-pkt (free-packet int-pkt)))) (defun find-ethernet-interface () (let* ((address (second (assoc :internet (net:ni-address-alist si:share-interface)))) (network (ip-network-number-from-address address))) (dolist (ni net:*network-interfaces*) (unless (eq ni si:share-interface) (when (eql network (ip-network-number-from-address (second (assoc :internet (net:ni-network-alist ni))))) (return ni)))))) (defun forward-broadcast-packet (int-pkt interface &aux destination) (cond ((not (plusp (decf (ih-ttl int-pkt)))) ;;TTL expired. No ICMP message sent for broadcast packets... (free-packet int-pkt)) ((eq interface si:share-interface) ;;Give copy to each processor on backplane with internet address (let ((destination-list nil) (max-data (net:ni-maximum-data-length interface)) (count (ih-length int-pkt))) (dolist (at (net:ni-address-translations si:share-interface)) (let ((op (net:at-hardware-address at))) (when (and (eq :internet (net:at-protocol at)) (cdr (assoc op net:*processor-forwarding-alist*))) ;;If processor has an Internet address AND we are its gateway, save address (push op destination-list)))) (cond ((null destination-list) ;Nobody on backplane with Internet address (free-packet int-pkt)) ((> count max-data) ;Fragmentation needed (queue-packet-for-forwarding int-pkt interface destination-list)) ((null (cdr destination-list)) ;Single other processor with Internet address (incf (ip-packets-forwarded *ip-stream*)) (store-header-checksum int-pkt) (send *ip-stream* :send int-pkt count interface (car destination-list))) (t ;More than one process with Internet address (queue-packet-for-forwarding int-pkt interface destination-list))))) ((setq destination (net:ni-broadcast-address interface)) ;;Network does have a broadcast address... (let ((max-data (net:ni-maximum-data-length interface)) (count (ih-length int-pkt))) (cond ((> count max-data) (queue-packet-for-forwarding int-pkt interface (ncons destination))) (t (incf (ip-packets-forwarded *ip-stream*)) (store-header-checksum int-pkt t) (send *ip-stream* :send int-pkt count interface destination))))) (t (free-packet int-pkt)))) ;;; IP UTILITY FUNCTIONS (defun local-host-p (address) (values (or (member address (ip-addresses *ip-stream*)) (and si:share-interface (arp:get-address-info address :internet nil si:share-interface nil))))) (defun mss (address) "Given an internet address, find the network interface packets will be sent on and return its MSS" (declare (values mss local-p transmission-time)) (multiple-value-bind (ignore interface local-p) (route address) (if interface (let ((mss (net:ni-maximum-data-length interface))) (values mss local-p (send interface :send-if-handles :time-to-transmit mss))) (values 0 nil 0)))) (defun ip-network-number-from-address (adr) "Given an IP address, return the network number" (cond ((= (ldb (byte 1 31) adr) 0) (dpb (ldb (byte 8 24) adr) (byte 8 24) 0)) ((= (ldb (byte 2 30) adr) 2) (dpb (ldb (byte 16 16) adr) (byte 16 16) 0)) ((= (ldb (byte 3 29) adr) 6) (dpb (ldb (byte 24 8) adr) (byte 24 8) 0)) (t adr))) (global:define-site-variable *broken-berkeley-unix-broadcast-address-p* :broken-berkeley-unix-broadcast-address-p "T to use broken broadcast address used by Berkeley 4.2 systems") (defun ip-broadcast-address-from-address (adr) "Given an IP address, return the network number" (let ((host (if *broken-berkeley-unix-broadcast-address-p* 0 #xffffff))) (cond ((= (ldb (byte 1 31) adr) 0) (dpb (ldb (byte 8 24) adr) (byte 8 24) host)) ((= (ldb (byte 2 30) adr) 2) (dpb (ldb (byte 16 16) adr) (byte 16 16) host)) ((= (ldb (byte 3 29) adr) 6) (dpb (ldb (byte 24 8) adr) (byte 24 8) host)) (t adr)))) (defun ip-network-number-and-mask (adr) "Given an IP address, return the network number and mask assuming no subnets" (declare (values network mask)) (cond ((= (ldb (byte 1 31) adr) 0) (values (dpb (ldb (byte 8 24) adr) (byte 8 24) 0) (dpb -1 (byte 8 24) 0))) ((= (ldb (byte 2 30) adr) 2) (values (dpb (ldb (byte 16 16) adr) (byte 16 16) 0) (dpb -1 (byte 16 16) 0))) ((= (ldb (byte 3 29) adr) 6) (values (dpb (ldb (byte 24 8) adr) (byte 24 8) 0) (dpb -1 (byte 24 8) 0))) (t (values adr #xffffffff)))) (defun ip-subnet-mask (adr) "Given an IP address, return the mask assuming no subnets" (cond ((= (ldb (byte 1 31) adr) 0) (dpb -1 (byte 8 24) 0)) ((= (ldb (byte 2 30) adr) 2) (dpb -1 (byte 16 16) 0)) ((= (ldb (byte 3 29) adr) 6) (dpb -1 (byte 24 8) 0)) (t #xffffffff))) (defun ip-host-number-from-address (adr) "Given an IP address, return the host number" (cond ((= (ldb (byte 1 31) adr) 0) (ldb (byte 24 0) adr)) ((= (ldb (byte 2 30) adr) 2) (ldb (byte 16 0) adr)) ((= (ldb (byte 3 29) adr) 6) (ldb (byte 8 0) adr)) (t adr))) (defun ip-broadcast-address-p (adr) "Return T if ADR is an IP broadcast address (all ones, or all ones in host)" (or (= adr #xffffffff) (dolist (ni *network-interfaces*) (let* ((elt (assoc :internet (net:ni-network-alist ni) :test #'eq)) (mask (third elt))) (and elt ;if :internet enabled mask ;and this interface has a network number (= (logand mask adr) (logand mask (second elt))) ;and address on this network (= (logandc1 mask adr) (logandc1 mask #xffffffff)) (return t)))) (multiple-value-bind (ignore mask) (ip-network-number-and-mask adr) (= (logandc1 mask adr) (logandc1 mask #xffffffff))))) (defun ip-self-address-p (adr) "Return T if ADR is IP 'self' address (all zeros, or all zeros in host)" (or (zerop adr) (zerop (ip-host-number-from-address adr)))) (defun ip-null-network-p (adr) "Return T if network field of ADR is all zeros" (not (ldb-test (byte 8 24) adr))) (defun ip-address-from-network-and-host (network host) (+ network (if (= (ldb (byte 1 31) network) 0) (ldb (byte 24 0) host) (if (= (ldb (byte 2 30) network) 2) (ldb (byte 16 0) host) (if (= (ldb (byte 3 29) network) 6) (ldb (byte 8 0) host) 0))))) (defun parse-internet-address (address &aux host name) (declare (values address host-object)) (labels ((glom-address (string) (let* ((first (string-search-char #\. string)) (second (and first (string-search-char #\. string (1+ first)))) (third (and second (string-search-char #\. string (1+ second))))) (if first ;If at least one dot, must be three of them... (when (and second third) (let ((one (parse-integer string :start 0 :end first :junk-allowed t)) (two (parse-integer string :start (1+ first) :end second :junk-allowed t)) (three (parse-integer string :start (1+ second) :end third :junk-allowed t)) (four (parse-integer string :start (1+ third) :junk-allowed t))) (when (and one (not (minusp one)) (< one 256) two (not (minusp two)) (< two 256) three (not (minusp three)) (< three 256) four (not (minusp four)) (< four 256)) (dpb one (byte 8 24) (dpb two (byte 8 16) (dpb three (byte 8 8) four)))))) (parse-integer string :junk-allowed t))))) ;If no dots, address given as an integer (let ((parsed-address (cond ((null address) nil) ((numberp address) address) ((or (and (symbolp address) (setq name (symbol-name address))) (and (stringp address) (setq name address))) (setq host (si:parse-host name t nil)) (if host (send host :network-address :internet t) (glom-address name))) ((typep address 'si:host) (setq host address) (send address :network-address :internet t)) (t nil)))) (when parsed-address (values parsed-address (or host (si:get-host-from-address parsed-address :internet))))))) ;;; If given a number, this always returns something that PARSE-INTERNET-ADDRESS would make into that number. (defun host-short-name (host) "Return a brief name for the specified host." (multiple-value-bind (number object) (parse-internet-address host) (cond (object (send object :short-name)) (number (canonical-ip number)) (t host)))) (defun (:property :internet si:smart-address-chooser) (addresses) (or (dolist (x addresses) (when (member x *default-network-numbers* :test #'(lambda (a b) (= (logand a (cdr b)) (car b)))) (return x))) (first addresses))) (defun new-host-validation-function (host system-type address) (let ((parsed-address (parse-internet-address address))) (cond ((stringp host) ;;Try an ICMP Echo 6 times -- allow for ARP failures, lost packets, etc... (do ((tries 0 (1+ tries))) ((= tries 6) (error "No response from address ~A" (canonical-ip parsed-address))) (when (icmp:ping parsed-address) (return))) (si:define-host host :host-names `(,host) :system-type system-type :internet `(,parsed-address)) (setq host (si:parse-host host)) (when (member parsed-address (ip-addresses *ip-stream*)) (setq si:local-host host)) host) (t (when address (unless (member parsed-address (send host :internet-addresses)) (error "~A is not a valid Internet address for ~A" address host))) host)))) (setf (get 'si:new-host-validation-function :internet) 'ip:new-host-validation-function) ;;; network-protocol interface (defun ip-length (int-pkt offset) (swap-two-bytes (aref int-pkt (+ offset 1)))) (defun canonical-ip (ip-addr &optional (delimiter #\.)) "Return canonical string for numeric Internet Address." (check-type ip-addr (unsigned-byte 32)) (format nil "~D~C~D~C~D~C~D" (ldb (byte 8 24) ip-addr) delimiter (ldb (byte 8 16) ip-addr) delimiter (ldb (byte 8 8) ip-addr) delimiter (ldb (byte 8 0) ip-addr) )) (defun ip-special-addresses (address interface) "Special address translations for IP. All zeros is SELF, all ones is BROADCAST. This applies to either entire address, or host-number (under subnet mask) -- see RFC 950" (let ((network-mask (third (assoc :internet (net:ni-network-alist interface) :test #'eq)))) (cond ((or (zerop address) (and network-mask (zerop (logandc1 network-mask address)))) (net:ni-address interface)) ((or (= address #xffffffff) (and network-mask (= (logandc1 network-mask address) (logandc1 network-mask #xffffffff)))) (net:ni-broadcast-address interface))))) (defun receive-ip-packet (int-pkt interface stream source destination broadcast-p &aux not-ok pointer) (declare (ignore destination)) (let* ((packet (original-array int-pkt)) (header (net:ni-rcvd-header-length interface)) (trailer (net:ni-rcvd-trailer-length interface)) (total-bytes (+ header trailer))) (setq int-pkt (make-array (- (* 2 (array-length packet)) total-bytes) :element-type '(unsigned-byte 8) :displaced-to packet :displaced-index-offset header :fill-pointer (- (* 2 (fill-pointer packet)) total-bytes)))) (unwind-protect (cond ((not (check-header-checksum int-pkt)) (incf (ip-checksum-failures stream))) ((multiple-value-setq (not-ok pointer) (check-net-header int-pkt)) (incf (ip-bad-header stream)) (unless broadcast-p (icmp:icmp :parameter-problem int-pkt pointer))) ((multiple-value-setq (not-ok pointer) (process-packet-options int-pkt)) (incf (ip-bad-options stream)) (unless broadcast-p (icmp:icmp :parameter-problem int-pkt pointer))) ((zerop (ih-ttl int-pkt)) (incf (ip-ttl-expired stream)) (unless broadcast-p (icmp:icmp :time-exceeded int-pkt icmp:icmp-ttl-exceeded))) (broadcast-p ;;Hardware broadcast packet. Don't forward, but pass up if protocol is enabled for such. ;;I.e. UDP, ICMP: yes, TCP: no. (incf (ip-hardware-broadcast-packets stream)) (pass-up-broadcast-packet stream (prog1 int-pkt (setq int-pkt nil)) interface source)) ((ip-broadcast-address-p (ih-dest-address int-pkt)) ;;IP broadcast, but not hardware broadcast -- presumably came over point-to-point link (pass-up-broadcast-packet stream (prog1 int-pkt (setq int-pkt nil)) interface source)) ((ip-self-address-p (ih-dest-address int-pkt)) ;;Self-address packet, not broadcast. Broken Berkeley 4.2 forces us to treat this like ;;a broadcast packet. Someday, when 4.2 is completely dead, do REAL self-address stuff (pass-up-broadcast-packet stream (prog1 int-pkt (setq int-pkt nil)) interface source)) ((not (member (ih-dest-address int-pkt) (ip-addresses stream))) (forward-packet (prog1 int-pkt (setq int-pkt nil)) interface source)) ((pass-up-packet int-pkt nil interface)) (t (icmp:icmp :destination-unreachable int-pkt icmp:icmp-protocol-unreachable) (pushnew (ih-protocol int-pkt) (ip-protocols-not-understood stream)) (incf (ip-unknown-protocol-packets stream)))) (and int-pkt (free-packet int-pkt)))) ;;;CONFIGURATION (defun setup-ip () (multiple-value-bind (internet-address-list subnet-mask-list) (setup-my-internet-address) (when internet-address-list (let ((my-internet-address (first internet-address-list)) (my-subnet-mask (first subnet-mask-list))) ;;;***Ideally, there should be a way to match multiple internet addresses with ;;;***multiple network interfaces -- Internet Gateway functions! (setq *ip-stream* (make-ip-network-protocol :keyword :internet :address-length 4 :big-endian-address-p t :interrupt-function 'receive-ip-packet :address-printer 'canonical-ip :get-header-function 'make-ip-header :free-header-function 'free-ip-header :send-packet-function 'send-ip-packet :broadcast-packet-function 'broadcast-ip-packet :special-address-function 'ip-special-addresses :packet-length-function 'ip-length :gauge-name "IP" :default-address my-internet-address )) (send *ip-stream* :open my-internet-address my-subnet-mask)) (initialize-route-table internet-address-list) (initialize-ip-background-process) (send *ip-stream* :enable) t)))