2014-06-16 01:16:14 +00:00
|
|
|
#lang racket/base
|
|
|
|
|
2014-06-16 21:52:53 +00:00
|
|
|
(provide (struct-out ip-packet)
|
|
|
|
ip-address->hostname
|
2014-06-19 22:00:37 +00:00
|
|
|
ip-string->ip-address
|
2014-06-19 01:24:47 +00:00
|
|
|
apply-netmask
|
|
|
|
ip-address-in-subnet?
|
|
|
|
gestalt->local-ip-addresses
|
|
|
|
observe-local-ip-addresses-gestalt
|
2014-06-16 21:52:53 +00:00
|
|
|
broadcast-ip-address
|
2014-06-16 01:16:14 +00:00
|
|
|
spawn-ip-driver)
|
|
|
|
|
|
|
|
(require racket/set)
|
|
|
|
(require racket/match)
|
2014-06-19 22:00:37 +00:00
|
|
|
(require (only-in racket/string string-split))
|
2016-04-01 23:53:46 +00:00
|
|
|
(require syndicate-monolithic)
|
|
|
|
(require syndicate-monolithic/drivers/timer)
|
|
|
|
(require syndicate-monolithic/demand-matcher)
|
2014-06-16 01:16:14 +00:00
|
|
|
(require bitsyntax)
|
|
|
|
|
|
|
|
(require "dump-bytes.rkt")
|
2014-06-19 01:24:47 +00:00
|
|
|
(require "configuration.rkt")
|
2014-06-16 01:16:14 +00:00
|
|
|
(require "checksum.rkt")
|
|
|
|
(require "ethernet.rkt")
|
|
|
|
(require "arp.rkt")
|
|
|
|
|
2014-06-19 01:24:47 +00:00
|
|
|
(struct ip-packet (source-interface ;; string for an ethernet interface, or #f for local interfaces
|
|
|
|
source
|
|
|
|
destination
|
|
|
|
protocol
|
|
|
|
options
|
|
|
|
body)
|
|
|
|
#:prefab) ;; TODO: more fields
|
2014-06-16 01:16:14 +00:00
|
|
|
|
2014-06-19 01:24:47 +00:00
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
2014-06-16 01:16:14 +00:00
|
|
|
|
2014-06-16 21:52:53 +00:00
|
|
|
(define (ip-address->hostname bs)
|
|
|
|
(bit-string-case bs
|
|
|
|
([ a b c d ] (format "~a.~a.~a.~a" a b c d))))
|
|
|
|
|
2014-06-19 22:00:37 +00:00
|
|
|
(define (ip-string->ip-address str)
|
|
|
|
(list->bytes (map string->number (string-split str "."))))
|
|
|
|
|
2014-06-19 01:24:47 +00:00
|
|
|
(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)))
|
|
|
|
|
2014-06-16 01:16:14 +00:00
|
|
|
(define broadcast-ip-address (bytes 255 255 255 255))
|
|
|
|
|
2016-03-15 14:55:50 +00:00
|
|
|
(define local-ip-address-projector (host-route (?!) ? ?))
|
2016-01-24 01:11:59 +00:00
|
|
|
(define (gestalt->local-ip-addresses g) (trie-project/set/single g local-ip-address-projector))
|
|
|
|
(define observe-local-ip-addresses-gestalt (subscription (host-route ? ? ?)))
|
2014-06-16 01:16:14 +00:00
|
|
|
|
2014-06-19 01:24:47 +00:00
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
(define (spawn-ip-driver)
|
2014-06-16 01:16:14 +00:00
|
|
|
(list
|
2014-06-19 01:24:47 +00:00
|
|
|
(spawn-demand-matcher (host-route (?!) (?!) (?!))
|
2016-01-24 01:11:59 +00:00
|
|
|
(route-up (host-route (?!) (?!) (?!)))
|
2014-06-19 01:24:47 +00:00
|
|
|
spawn-host-route)
|
2014-06-20 03:10:50 +00:00
|
|
|
(spawn-demand-matcher (gateway-route (?!) (?!) (?!) (?!))
|
2016-01-24 01:11:59 +00:00
|
|
|
(route-up (gateway-route (?!) (?!) (?!) (?!)))
|
2014-06-20 03:10:50 +00:00
|
|
|
spawn-gateway-route)
|
2014-06-19 01:24:47 +00:00
|
|
|
(spawn-demand-matcher (net-route (?!) (?!) (?!))
|
2016-01-24 01:11:59 +00:00
|
|
|
(route-up (net-route (?!) (?!) (?!)))
|
2014-06-19 01:24:47 +00:00
|
|
|
spawn-net-route)))
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Local IP route
|
|
|
|
|
|
|
|
(define (spawn-host-route my-address netmask interface-name)
|
2014-06-17 16:07:05 +00:00
|
|
|
(list
|
2014-06-19 01:24:47 +00:00
|
|
|
(let ((network-addr (apply-netmask my-address netmask)))
|
2016-01-24 01:11:59 +00:00
|
|
|
(spawn-normal-ip-route (host-route my-address netmask interface-name)
|
2014-06-19 01:24:47 +00:00
|
|
|
network-addr
|
|
|
|
netmask
|
|
|
|
interface-name))
|
2014-06-17 16:07:05 +00:00
|
|
|
(spawn (lambda (e s)
|
|
|
|
(match e
|
2016-01-24 01:11:59 +00:00
|
|
|
[(scn (? trie-empty?)) (quit)]
|
|
|
|
[(message (ip-packet _ peer-address _ _ _ body))
|
2014-06-19 01:24:47 +00:00
|
|
|
(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)))
|
2016-01-24 01:11:59 +00:00
|
|
|
(transition s (message (ip-packet #f
|
|
|
|
my-address
|
|
|
|
peer-address
|
|
|
|
PROTOCOL-ICMP
|
|
|
|
#""
|
|
|
|
(ip-checksum 2 reply-data0))))]
|
2014-06-19 01:24:47 +00:00
|
|
|
[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))
|
|
|
|
#f]))
|
|
|
|
(else #f))]
|
2014-06-17 16:07:05 +00:00
|
|
|
[_ #f]))
|
|
|
|
(void)
|
2016-01-24 01:11:59 +00:00
|
|
|
(scn/union (advertisement (ip-packet ? my-address ? PROTOCOL-ICMP ? ?))
|
|
|
|
(subscription (ip-packet ? ? my-address PROTOCOL-ICMP ? ?))
|
|
|
|
(assertion (arp-assertion IPv4-ethertype my-address interface-name))
|
|
|
|
(subscription (host-route my-address netmask interface-name))))))
|
2014-06-19 01:24:47 +00:00
|
|
|
|
2014-06-20 01:57:51 +00:00
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Gateway IP route
|
|
|
|
|
|
|
|
(struct gateway-route-state (routes gateway-interface gateway-hwaddr) #:transparent)
|
2014-06-19 01:24:47 +00:00
|
|
|
|
2014-06-20 03:10:50 +00:00
|
|
|
(define (spawn-gateway-route network netmask gateway-addr interface-name)
|
2016-01-24 01:11:59 +00:00
|
|
|
(define the-route (gateway-route network netmask gateway-addr interface-name))
|
2014-06-20 03:10:50 +00:00
|
|
|
|
2016-03-15 14:55:50 +00:00
|
|
|
(define host-route-projector (host-route (?!) (?!) ?))
|
|
|
|
(define gateway-route-projector (gateway-route (?!) (?!) ? ?))
|
|
|
|
(define net-route-projector (net-route (?!) (?!) ?))
|
2016-01-24 01:17:03 +00:00
|
|
|
(define gateway-arp-projector (arp-query IPv4-ethertype
|
|
|
|
gateway-addr
|
|
|
|
(?! (ethernet-interface interface-name ?))
|
|
|
|
(?!)))
|
2014-06-19 01:24:47 +00:00
|
|
|
|
|
|
|
(define (covered-by-some-other-route? addr routes)
|
|
|
|
(for/or ([r (in-set routes)])
|
|
|
|
(match-define (list net msk) r)
|
|
|
|
(and (positive? msk)
|
|
|
|
(ip-address-in-subnet? addr net msk))))
|
2014-06-17 16:07:05 +00:00
|
|
|
|
2014-06-20 01:57:51 +00:00
|
|
|
(spawn (lambda (e s)
|
2014-06-19 01:24:47 +00:00
|
|
|
(match e
|
2016-01-24 01:11:59 +00:00
|
|
|
[(scn g)
|
2016-03-15 14:55:50 +00:00
|
|
|
(define host-ips+netmasks (trie-project/set #:take 2 g host-route-projector))
|
|
|
|
(define gw-nets+netmasks (trie-project/set #:take 2 g gateway-route-projector))
|
|
|
|
(define net-nets+netmasks (trie-project/set #:take 2 g net-route-projector))
|
2016-01-24 01:17:03 +00:00
|
|
|
(define gw-ip+hwaddr
|
2016-03-15 14:55:50 +00:00
|
|
|
(let ((vs (trie-project/set #:take 2 g gateway-arp-projector)))
|
2016-01-24 01:17:03 +00:00
|
|
|
(and vs (not (set-empty? vs)) (set-first vs))))
|
2014-06-20 03:10:50 +00:00
|
|
|
(when (and gw-ip+hwaddr (not (gateway-route-state-gateway-hwaddr s)))
|
|
|
|
(log-info "Discovered gateway ~a at ~a on interface ~a."
|
|
|
|
(ip-address->hostname gateway-addr)
|
|
|
|
(ethernet-interface-name (car gw-ip+hwaddr))
|
|
|
|
(pretty-bytes (cadr gw-ip+hwaddr))))
|
2016-01-24 01:11:59 +00:00
|
|
|
(if (trie-empty? (project-assertions g (?! the-route)))
|
|
|
|
(quit)
|
|
|
|
(transition (gateway-route-state
|
2016-01-24 02:50:39 +00:00
|
|
|
(set-union host-ips+netmasks
|
2016-01-24 01:11:59 +00:00
|
|
|
gw-nets+netmasks
|
|
|
|
net-nets+netmasks)
|
|
|
|
(and gw-ip+hwaddr (car gw-ip+hwaddr))
|
|
|
|
(and gw-ip+hwaddr (cadr gw-ip+hwaddr)))
|
|
|
|
'()))]
|
|
|
|
[(message (? ip-packet? p))
|
2014-06-20 01:57:51 +00:00
|
|
|
(define gw-if (gateway-route-state-gateway-interface s))
|
|
|
|
(when (not gw-if)
|
2014-06-20 03:17:59 +00:00
|
|
|
(log-warning "Gateway hwaddr for ~a not known, packet dropped."
|
|
|
|
(ip-address->hostname gateway-addr)))
|
2014-06-20 01:57:51 +00:00
|
|
|
(and gw-if
|
|
|
|
(not (equal? (ip-packet-source-interface p) (ethernet-interface-name gw-if)))
|
|
|
|
(not (covered-by-some-other-route? (ip-packet-destination p)
|
|
|
|
(gateway-route-state-routes s)))
|
|
|
|
(transition s
|
2016-01-24 01:11:59 +00:00
|
|
|
(message (ethernet-packet gw-if
|
|
|
|
#f
|
|
|
|
(ethernet-interface-hwaddr gw-if)
|
|
|
|
(gateway-route-state-gateway-hwaddr s)
|
|
|
|
IPv4-ethertype
|
|
|
|
(format-ip-packet p)))))]
|
2014-06-19 01:24:47 +00:00
|
|
|
[_ #f]))
|
2014-06-20 01:57:51 +00:00
|
|
|
(gateway-route-state (set) #f #f)
|
2016-01-24 01:11:59 +00:00
|
|
|
(scn/union (subscription the-route)
|
|
|
|
(assertion (route-up the-route))
|
|
|
|
(subscription (ip-packet ? ? ? ? ? ?))
|
|
|
|
observe-local-ip-addresses-gestalt
|
|
|
|
(subscription (net-route ? ? ?))
|
|
|
|
(subscription (gateway-route ? ? ? ?))
|
|
|
|
(subscription (projection->pattern gateway-arp-projector)))))
|
2014-06-19 01:24:47 +00:00
|
|
|
|
2014-06-20 03:10:50 +00:00
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; General net route
|
|
|
|
|
|
|
|
(define (spawn-net-route network-addr netmask link)
|
2016-01-24 01:11:59 +00:00
|
|
|
(spawn-normal-ip-route (net-route network-addr netmask link) network-addr netmask link))
|
2014-06-20 03:10:50 +00:00
|
|
|
|
2014-06-19 01:24:47 +00:00
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Normal IP route
|
|
|
|
|
2016-01-24 01:11:59 +00:00
|
|
|
(define (spawn-normal-ip-route the-route network netmask interface-name)
|
2014-06-16 01:16:14 +00:00
|
|
|
(spawn (lambda (e s)
|
|
|
|
(match e
|
2016-01-24 01:11:59 +00:00
|
|
|
[(scn (? trie-empty?)) (quit)]
|
|
|
|
[(message (ethernet-packet _ _ _ _ _ body))
|
2014-06-19 01:24:47 +00:00
|
|
|
(define p (parse-ip-packet interface-name body))
|
2016-01-24 01:11:59 +00:00
|
|
|
(and p (transition s (message p)))]
|
|
|
|
[(message (? ip-packet? p))
|
2014-06-19 01:24:47 +00:00
|
|
|
(define destination (ip-packet-destination p))
|
|
|
|
(and (not (equal? (ip-packet-source-interface p) interface-name))
|
|
|
|
(ip-address-in-subnet? destination network netmask)
|
|
|
|
(transition
|
|
|
|
s
|
|
|
|
(lookup-arp destination
|
|
|
|
(ethernet-interface interface-name ?)
|
2016-03-15 14:55:50 +00:00
|
|
|
trie-empty
|
2014-06-19 01:24:47 +00:00
|
|
|
(lambda (interface destination-hwaddr)
|
2016-01-24 01:11:59 +00:00
|
|
|
(message (ethernet-packet interface
|
|
|
|
#f
|
|
|
|
(ethernet-interface-hwaddr interface)
|
|
|
|
destination-hwaddr
|
|
|
|
IPv4-ethertype
|
|
|
|
(format-ip-packet p)))))))]
|
2014-06-16 01:16:14 +00:00
|
|
|
[_ #f]))
|
|
|
|
(void)
|
2016-01-24 01:11:59 +00:00
|
|
|
(scn/union (subscription the-route)
|
|
|
|
(assertion (route-up the-route))
|
|
|
|
(subscription (ethernet-packet-pattern interface-name #t IPv4-ethertype))
|
|
|
|
(assertion (arp-interface interface-name))
|
|
|
|
(subscription (ip-packet ? ? ? ? ? ?)))))
|
2014-06-19 01:24:47 +00:00
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
(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) ]
|
2014-06-19 03:58:25 +00:00
|
|
|
(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))))
|
2014-06-19 01:24:47 +00:00
|
|
|
(if (and (>= header-length 5)
|
|
|
|
(>= (bit-string-byte-count body) (* header-length 4)))
|
|
|
|
(bit-string-case rest
|
|
|
|
([ (opts :: binary bytes options-length)
|
2014-06-19 03:58:25 +00:00
|
|
|
(data :: binary bytes data-length)
|
|
|
|
(:: binary) ] ;; Very short ethernet packets have a trailer of zeros
|
2014-06-19 01:24:47 +00:00
|
|
|
(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)
|
|
|
|
|
|
|
|
(define (lookup-arp ipaddr query-interface-pattern base-gestalt k)
|
2016-01-28 02:46:09 +00:00
|
|
|
(on-claim #:name (string->symbol (format "lookup-arp:~a" (ip-address->hostname ipaddr)))
|
|
|
|
(lambda (_g arp-results)
|
2016-01-24 01:11:59 +00:00
|
|
|
(if (not arp-results)
|
|
|
|
(error 'ip "Someone has published a wildcard arp result")
|
|
|
|
(and (not (set-empty? arp-results))
|
|
|
|
(match (set-first arp-results)
|
|
|
|
[(list interface hwaddr)
|
|
|
|
(log-info "ARP lookup yielded ~a on ~a for ~a"
|
|
|
|
(pretty-bytes hwaddr)
|
|
|
|
(ethernet-interface-name interface)
|
|
|
|
(ip-address->hostname ipaddr))
|
|
|
|
(when (> (set-count arp-results) 1)
|
|
|
|
(log-warning "Ambiguous ARP result for ~a: ~v"
|
|
|
|
(ip-address->hostname ipaddr)
|
|
|
|
arp-results))
|
|
|
|
(k interface hwaddr)]))))
|
|
|
|
base-gestalt
|
|
|
|
(arp-query IPv4-ethertype ipaddr (?! query-interface-pattern) (?!))
|
|
|
|
#:timeout-msec 5000
|
|
|
|
#:on-timeout (lambda ()
|
|
|
|
(log-warning "ARP lookup of ~a failed, packet dropped"
|
|
|
|
(ip-address->hostname ipaddr))
|
|
|
|
'())))
|