syndicate-rkt/syndicate-examples/netstack/ip.rkt

263 lines
10 KiB
Racket

;;; SPDX-License-Identifier: LGPL-3.0-or-later
;;; SPDX-FileCopyrightText: Copyright © 2010-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
#lang syndicate
(provide (struct-out ip-packet)
ip-address->hostname
ip-string->ip-address
apply-netmask
ip-address-in-subnet?
query-local-ip-addresses
broadcast-ip-address
spawn-ip-driver)
(require racket/set)
(require (only-in racket/string string-split))
(require bitsyntax)
(require "dump-bytes.rkt")
(require "configuration.rkt")
(require "checksum.rkt")
(require/activate syndicate/drivers/timer)
(require/activate "ethernet.rkt")
(require/activate "arp.rkt")
(message-struct ip-packet
(source-interface ;; string for an ethernet interface, or #f for local interfaces
source
destination
protocol
options
body
;; TODO: more fields
))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (ip-address->hostname bs)
(bit-string-case bs
([ a b c d ] (format "~a.~a.~a.~a" a b c d))))
(define (ip-string->ip-address str)
(list->bytes (map string->number (string-split str "."))))
(define (apply-netmask addr netmask)
(bit-string-case addr
([ (n :: integer bytes 4) ]
(bit-string ((bitwise-and n (arithmetic-shift #x-100000000 (- netmask)))
:: integer bytes 4)))))
(define (ip-address-in-subnet? addr network netmask)
(equal? (apply-netmask network netmask)
(apply-netmask addr netmask)))
(define broadcast-ip-address (bytes 255 255 255 255))
(define (query-local-ip-addresses)
(query-set local-ips (host-route $addr _ _) addr))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (spawn-ip-driver)
(spawn #:name 'ip-driver
(during/spawn (host-route $my-address $netmask $interface-name)
(assert (route-up (host-route my-address netmask interface-name)))
(do-host-route my-address netmask interface-name))
(during/spawn (gateway-route $network $netmask $gateway-addr $interface-name)
(assert (route-up (gateway-route network netmask gateway-addr interface-name)))
(do-gateway-route network netmask gateway-addr interface-name))
(during/spawn (net-route $network-addr $netmask $link)
(assert (route-up (net-route network-addr netmask link)))
(do-net-route network-addr netmask link))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Local IP route
(define (do-host-route my-address netmask interface-name)
(let ((network-addr (apply-netmask my-address netmask)))
(do-normal-ip-route (host-route my-address netmask interface-name)
network-addr
netmask
interface-name))
(assert (arp-assertion IPv4-ethertype my-address interface-name))
(on (message (ip-packet _ $peer-address my-address PROTOCOL-ICMP _ $body))
(bit-string-case body
([ type code (checksum :: integer bytes 2) (rest :: binary) ] ;; TODO: check cksum
(case type
[(8) ;; ECHO (0 is ECHO-REPLY)
(log-info "Ping of ~a from ~a"
(pretty-bytes my-address)
(pretty-bytes peer-address))
(define reply-data0 (bit-string 0
code
(0 :: integer bytes 2) ;; TODO
(rest :: binary)))
(send! (ip-packet #f
my-address
peer-address
PROTOCOL-ICMP
#""
(ip-checksum 2 reply-data0)))]
[else
(log-info "ICMP ~a/~a (cksum ~a) to ~a from ~a:\n~a"
type
code
checksum
(pretty-bytes my-address)
(pretty-bytes peer-address)
(dump-bytes->string rest))]))
(else #f))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Gateway IP route
(define (do-gateway-route network netmask gateway-addr interface-name)
(define the-route (gateway-route network netmask gateway-addr interface-name))
(field [routes (set)])
(query-set* routes (host-route $addr $netmask _) (list addr netmask))
(query-set* routes (gateway-route $addr $netmask _ _) (list addr netmask))
(query-set* routes (net-route $addr $netmask _) (list addr netmask))
(define (covered-by-some-other-route? addr)
(for/or ([r (in-set (routes))])
(match-define (list net msk) r)
(and (positive? msk)
(ip-address-in-subnet? addr net msk))))
(during (ethernet-interface interface-name $gateway-interface-hwaddr)
(field [gateway-hwaddr #f])
(on (asserted (arp-query IPv4-ethertype gateway-addr interface-name $hwaddr))
(when (not (gateway-hwaddr))
(log-info "Discovered gateway ~a at ~a on interface ~a."
(ip-address->hostname gateway-addr)
interface-name
(pretty-bytes hwaddr)))
(gateway-hwaddr hwaddr))
(on (message ($ p (ip-packet _ _ _ _ _ _)))
(when (not (gateway-hwaddr))
(log-warning "Gateway hwaddr for ~a not known, packet dropped."
(ip-address->hostname gateway-addr)))
(when (and (gateway-hwaddr)
(not (equal? (ip-packet-source-interface p) interface-name))
(not (covered-by-some-other-route? (ip-packet-destination p))))
(send! (ethernet-packet interface-name
#f
gateway-interface-hwaddr
(gateway-hwaddr)
IPv4-ethertype
(format-ip-packet p)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; General net route
(define (do-net-route network-addr netmask link)
(do-normal-ip-route (net-route network-addr netmask link) network-addr netmask link))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Normal IP route
(define (do-normal-ip-route the-route network netmask interface-name)
(assert (arp-interface interface-name))
(on (message (ethernet-packet interface-name #t _ _ IPv4-ethertype $body))
(define p (parse-ip-packet interface-name body))
(when p (send! p)))
(during (ethernet-interface interface-name $interface-hwaddr)
(on (message ($ p (ip-packet _ _ _ _ _ _)))
(define destination (ip-packet-destination p))
(when (and (not (equal? (ip-packet-source-interface p) interface-name))
(ip-address-in-subnet? destination network netmask))
;; v Use `spawn` instead of `react` to avoid gratuitous packet
;; reordering.
(spawn (stop-when-timeout 5000
(log-warning "ARP lookup of ~a failed, packet dropped"
(ip-address->hostname destination)))
(stop-when (asserted (arp-query IPv4-ethertype
destination
interface-name
$destination-hwaddr))
(send! (ethernet-packet interface-name
#f
interface-hwaddr
destination-hwaddr
IPv4-ethertype
(format-ip-packet p)))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define IPv4-ethertype #x0800)
(define IP-VERSION 4)
(define IP-MINIMUM-HEADER-LENGTH 5)
(define PROTOCOL-ICMP 1)
(define default-ttl 64)
(define (parse-ip-packet interface-name body)
;; (log-info "IP ~a got body ~a" (pretty-bytes my-address) (pretty-bytes body))
(bit-string-case body
([ (= IP-VERSION :: bits 4)
(header-length :: bits 4)
service-type
(total-length :: bits 16)
(id :: bits 16)
(flags :: bits 3)
(fragment-offset :: bits 13)
ttl
protocol
(header-checksum :: bits 16) ;; TODO: check checksum
(source-ip0 :: binary bits 32)
(destination-ip0 :: binary bits 32)
(rest :: binary) ]
(let* ((source-ip (bit-string->bytes source-ip0))
(destination-ip (bit-string->bytes destination-ip0))
(options-length (* 4 (- header-length IP-MINIMUM-HEADER-LENGTH)))
(data-length (- total-length (* 4 header-length))))
(if (and (>= header-length 5)
(>= (bit-string-byte-count body) (* header-length 4)))
(bit-string-case rest
([ (opts :: binary bytes options-length)
(data :: binary bytes data-length)
(:: binary) ] ;; Very short ethernet packets have a trailer of zeros
(ip-packet interface-name
(bit-string->bytes source-ip)
(bit-string->bytes destination-ip)
protocol
(bit-string->bytes opts)
(bit-string->bytes data))))
#f)))
(else #f)))
(define (format-ip-packet p)
(match-define (ip-packet _ src dst protocol options body) p)
(define header-length ;; TODO: ensure options is a multiple of 4 bytes
(+ IP-MINIMUM-HEADER-LENGTH (quotient (bit-string-byte-count options) 4)))
(define header0 (bit-string (IP-VERSION :: bits 4)
(header-length :: bits 4)
0 ;; TODO: service type
((+ (* header-length 4) (bit-string-byte-count body))
:: bits 16)
(0 :: bits 16) ;; TODO: identifier
(0 :: bits 3) ;; TODO: flags
(0 :: bits 13) ;; TODO: fragments
default-ttl
protocol
(0 :: bits 16)
(src :: binary bits 32)
(dst :: binary bits 32)
(options :: binary)))
(define full-packet (bit-string ((ip-checksum 10 header0) :: binary) (body :: binary)))
full-packet)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(spawn-ip-driver)