fixes; first version
[rrq/hourglass.git] / listener.lsp
1 #!/usr/local/bin/newlisp
2 #
3
4 # This program attaches to a tap interface for the purpose of noticing
5 # activity via network traffic. The program serves as a virtual host
6 # that receives duplicated packets, and analyses them to select those
7 # that indicate activity.
8 # Optional arguments:
9 # -t tap = use the given tap rather than "tap0".
10
11 (signal 2 (fn (x) (exit 0)))
12
13 # The following is for Devuan GNU+Linux
14 (constant 'LIBC SITE:libc)
15 (import LIBC "ioctl" "int" "int" "long" "void*" )
16 (import LIBC "perror" "void" "char*" )
17 (import LIBC "ntohl" "int" "int" )
18 (import LIBC "ntohs" "int" "int" )
19 (import LIBC "htons" "int" "int" )
20 (import LIBC "htonl" "int" "int" )
21
22 # Report low level system error and exit
23 (define (die s) (perror s) (exit 1))
24
25 # Utility function to find a command line argument key and optionally
26 # the subsequent value, if a non-nil default value is given.
27 (define (mainarg k (v nil))
28   (let ((a (member k (main-args))))
29     (if (null? a) v (nil? v) true (null? (1 a)) v (a 1))))
30
31 # Set logging mode.
32 # Open the tap named by "-t tapX" on the command line, or "tap0" byt
33 # default. Then make a TUNSETIFF call to initialize it (as
34 # IFF_TAP|IFF_NO_PI).
35 (constant
36  'listener-log-ip SITE:listener.log.ip
37  'IFNAME SITE:listener.tap
38  'PORTS SITE:listener.ports
39  'IFD (open SITE:tundev "u")
40  'ACTNAMEFMT "%d%02d%02d-network.dat"
41  'ACTDIR SITE:listener.activity.dir
42  'ACTFILEFMT (format "%s/%s" ACTDIR ACTNAMEFMT )
43 )
44 (unless (number? IFD) (die "open"))
45 (unless (zero? (ioctl IFD 0x400454ca (pack "s16 u s22" IFNAME 0x1002 "")))
46   (die (string "set " IFNAME)))
47
48 # The TCP ports of interest
49
50 # Set up for optional tracking of IP addresses
51 (define counter:counter nil)
52
53 # This function accumulates packet size per ip+port, for monitored the
54 # ports. This accumulates traffic in both directions.
55 (define (track-data) ; buffer
56   (write-line 2 "track-data")
57   (let ((ips (explode (unpack "bbbb bbbb" ((+ 12 14) buffer)) 4)))
58     (dotimes (i 2)
59       (when (member (ports i) PORTS)
60         (let ((k (string (join (map string (ips i)) ".") "." (ports i))))
61           (counter x (+ (length buffer) (or (counter x) 0))))))))
62
63 (define (track-data-reset)
64   (map delete (symbols counter)))
65
66 (track-data-reset)
67
68 # Mark the minute of t as an active minute. More exactly, it issues a
69 # mark if it now is more than 60 seconds from the last issued mark.
70 # This funcion collates all given ips, and it extends the log line
71 # with the list of ips used during the minute.
72 (setf next-mark 0 packet-count 0)
73      
74 (define (mark-active t) ; buffer
75   (when listener-log-ip (track-data))
76   (inc packet-count)
77   ;(write-line 2 (string (list t packet-count ports (counter))))
78   (when (>= t next-mark)
79     (let ((d (format ACTFILEFMT (0 3 (date-list t))))
80           (c (map string (counter))))
81       (append-file d (string t " " packet-count " " (join c " ") "\n"))
82       (setf next-mark (+ t 60) packet-count 0)
83       (when listener-log-ip (track-data-reset))
84       )))
85
86 # Handle an ARP request. This picks up IP address from the request.
87 # The MAC address is formed from the IP address with 2 before and 2
88 # after.
89 (define (arp-request-handler) ; buffer
90   (letn ((MYIP (unpack "bbbb" (38 buffer))) (MYMAC (flat (list 2 MYIP 2))))
91     (write IFD (pack "bbbbbb bbbbbb u u u b b u bbbbbb bbbb bbbbbb bbbb"
92                      (flat (list (unpack "bbbbbb" (6 buffer))
93                                  MYMAC
94                                  (map htons '(0x0806 0x1 0x0800 ))
95                                  0x06 0x04
96                                  (htons 0x2)
97                                  MYMAC MYIP
98                                  (unpack "bbbbbb bbbb" (22 buffer))
99                                  ))
100                      ))))
101
102 # Handle an ARP packet. It recognizes the ARP command involved, and
103 # dispatches to the associated handler, if any.
104 (define (arp-handler) ; buffer
105   (case (ntohs ((unpack "u" (20 buffer)) 0)) ; ARP command
106     (0x0001 (and arp-request-handler (arp-request-handler)))
107     (true nil) ; ignore
108     ))
109
110 # Handle a TCP packet. It reviews the ports involved, and if any is
111 # among the interesting ports, then it marks activity together with ip
112 # and port of sender and receiver.
113 (define (tcp-handler) ; buffer ihl
114   (let ((ports (map ntohs (unpack "uu" ((+ ihl 14) buffer)))))
115     (when (intersect ports PORTS) (mark-active (date-value)))))
116
117 (define (udp-handler) ; buffer ihl
118   (let ((ports (map ntohs (unpack "uu" ((+ ihl 14) buffer)))))
119     (when PORTS (intersect ports PORTS) (mark-active (date-value)))))
120
121 # Handle an IPv4 packet. It recognises the IPv4 protocol concerned,
122 # and dispatches to the associated handler, if any.
123 (define (ipv4-handler) ; buffer
124   (let ((ihl (* (& 0x0F ((unpack "b" (14 buffer)) 0)) 4)))
125     (case ((unpack "b" (23 buffer)) 0) ; protocol
126       (0x01 (and icmp-handler (icmp-handler)))
127       (0x02 (and igmp-handler (igmp-handler)))
128       (0x04 (and ipip-handler (ipip-handler)))
129       (0x06 (and tcp-handler (tcp-handler)))
130       (0x11 (and udp-handler (udp-handler)))
131       (true nil) ; ignore
132       )
133     ))
134
135 # This function handles an Ethernet packet by recognising the packet
136 # type, and dispatch to the associated handler, if any.
137 (define (handle-packet) ; buffer
138   (when (> n 14)
139     (case (ntohs ((unpack "u" (12 buffer)) 0)) ; Ethertype
140       (0x0806 (and arp-handler (arp-handler)))
141       (0x0800 (and ipv4-handler (ipv4-handler)))
142       (0x86DD (and ipv6-handler (ipv6-handler)))
143       (true nil) ; ignore all else
144       )))
145
146 # Read and handle a packet from the tap. The program handles ARP
147 # requests by emitting an appropriate ARP response, and it handles TCP
148 # packets to certain ports, which are seen as indications of activity.
149 (define (handle-tap)
150   (let ((buffer "")(n nil))
151     (if (setf n (read IFD buffer 8000)) (handle-packet)
152       (begin (write-line 2 (format "IFD error")) (exit 1)))))
153
154 # This function gets invoked prior to the interactive prompt. It'll
155 # listen for data on the tap, and handle that, and also wake up every
156 # second, so as to allow a timer effect to be set up.
157 (define (ioselect s)
158   (letn ((fds (list IFD)) (fdx nil))
159     (until (member 0 (setf fdx (or (net-select fds "r" 10000000) '())))
160       (when fdx (handle-tap))))
161   nil)
162
163 (prompt-event ioselect)
164 (close 0)
165 (while true (ioselect))