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