=^.^=

Installing addrwatch on FreeBSD/OPNsense

karma

addrwatch is a tool for monitoring the ARP traffic on a subnet. From its GitHub page:

This is a tool similar to arpwatch. It main purpose is to monitor network and log discovered ethernet/ip pairings.

Main features of addrwatch:

  • IPv4 and IPv6 address monitoring
  • Monitoring multiple network interfaces with one daemon
  • Monitoring of VLAN tagged (802.1Q) packets.
  • Output to stdout, plain text file, syslog, sqlite3 db, MySQL db
  • IP address usage history preserving output/logging
  • Addrwatch is extremely useful in networks with IPv6 autoconfiguration (RFC4862) enabled. It allows to track IPv6 addresses of hosts using IPv6 privacy extensions (RFC4941).

The main difference between arpwatch and addrwatch is the format of output files.

Arpwatch stores only current state of the network ethernet/ip pairings and allows to send email notification when a pairing change occurs. This is fine for small and rather static networks. In arpwatch case all the history of pairings is saved only in administrators mailbox. When arpwatch is used for monitoring dozen or more networks it becomes hard to keep track of the historic address usage information.

I chose to install addrwatch on ISP routers to monitor for:

  • ARP spoofing - clients may change their MAC to evade access control mechanisms, accounting, request new leases or otherwise obscure their identity. It is noteworthy that it is the default behaviour of modern Android and iOS devices to randomize their MAC address upon connecting to a new WiFi network; however it is the default behaviour that they will retain this association and present the same address upon reconnection.
  • ARP poisoning - the quintessential layer 2 MitM attack, allowing an adversary to fully intercept (optionally forwarding) all traffic to and from a different, legitimate host (i.e. the victim's gateway) for inspection, injection and/or exfiltration.
  • Malfunctioning devices - on occasion I have seen embedded devices go haywire, spewing broadcast storms of ARP responses with randomized source MAC addresses. This can overwhelm a router/bridge/switch's ARP table with bogus entries resulting in a DoS at worst and cache starvation at best.
  • CPE equipment changes - detecting a change in hardware at the customer's premises (i.e. a new router's installation)
  • Unauthorized IP address changes - where a client may have accidentally or intentionally forced a new layer 3 address that was not assigned to them. This could indicate a router's loss of configuration (reversion to DHCP) or the subversion of access controls, bandwidth allocation or accounting.

On OPNsense one can install addrwatch from the community repo via pkg:

pkg install addrwatch

...or compile it from source via ports:

opnsense-code ports cd /usr/ports/net/addrwatch pkg install autoconf pkgconf automake make make install

As covered in the previous article, Create an Unprivileged User Account on FreeBSD/OPNsense, it is best practice to run daemons under their own user account. To create an unprivileged user account for addrwatch run:
adduser -d /nonexistent -D -u 0 -w no -s /usr/sbin/nologin Username: addrwatch Full name: addrwatch Daemon ...

Although the man page that ships with the latest version of addrwatch indicates that it is possible to pipe logs into a standard syslog interface with the -l or --syslog flags it appears this capability has been dropped. Therefore, unless you intend to take advantage solely of the mysql or sqlite logging abilities, we will need somewhere to store our log files (-o or --output):
mkdir /var/log/addrwatch chown addrwatch:addrwatch /var/log/addrwatch

Next it's necessary to create a syshook script to ensure the daemon isEdit started at bootup. Per the syshook documentation, we will prepend the script's file name with 50-: /usr/local/etc/rc.syshook.d/start/50-addrwatch and populate it thus:
#!/bin/sh ADDRWATCH_EXEC="/usr/local/bin/addrwatch" ADDRWATCH_USER="addrwatch" # See https://foxpa.ws/unprivileged-user-on-freebsd ADDRWATCH_LOGFILE="/var/log/addrwatch/addrwatch.log" ADDRWATCH_PIDFILE="/var/run/addrwatch.pid" ADDRWATCH_RATELIMIT="-r 0" # [-r (num)] | >0 = seconds within which duplicate events shall be discarded | 0/[empty] = record all events | -1 = suppress duplicates indefinitely ADDRWATCH_LISTEN_INTERFACES="xn1" ADDRWATCH_PARAMS="" $ADDRWATCH_EXEC -d -u $ADDRWATCH_USER -o $ADDRWATCH_LOGFILE -p $ADDRWATCH_PIDFILE $ADDRWATCH_RATELIMIT $ADDRWATCH_PARAMS $ADDRWATCH_LISTEN_INTERFACES

Note that you will want to populate the ADDRWATCH_LISTEN_INTERFACES variable with a space delimited list of interfaces to listen on. In this situation, where I am listening from a gateway, I want to only hear ARP traffic from my LAN interface(s). In this instance, that is my second interface provided by the Xen paravirtualized netfront driver, xn1. Your interface name(s) will likely be different and can be obtained by running ifconfig. Leaving this variable blank will result in addrwatch listening on only the first available non-loop interface, which would be fine for example on a network analysis node or workstation in hostile territory possessing only one interface.

Let's make a stop script to mirror this configuration at /usr/local/etc/rc.syshook.d/stop/50-addrwatch
#!/bin/sh /bin/pkill -KILL addrwatch exit 0

Do not forget to set the execute bits on these scripts:
chmod +x /usr/local/etc/rc.syshook.d/start/50-addrwatch chmod +x /usr/local/etc/rc.syshook.d/stop/50-addrwatch

Optionally, we can add configd actions which can later be called up by the configctl addrwatch action command, via the WebGUI backend in a custom plugin or front end as a cron job. Create /usr/local/opnsense/service/conf/actions.d/actions_addrwatch.conf:
[start] command:/usr/local/etc/rc.syshook.d/start/50-addrwatch type:script message:starting addrwatch description:Start addrwatch [stop] command:/usr/local/etc/rc.syshook.d/stop/50-addrwatch type:script message:stopping addrwatch description:Stop addrwatch [restart] command:/usr/local/etc/rc.syshook.d/stop/50-addrwatch; /usr/local/etc/rc.syshook.d/start/50-addrwatch type:script message:restarting addrwatch description:Restart addrwatch service [reset] command:kill -9 `cat /var/run/addrwatch.pid`; rm /var/run/addrwatch.pid type:script message:resetting addrwatch description:Kills addrwatch by PID (if still running) and removes PID file [status] command:tail /var/log/addrwatch/addrwatch.log type:script_output message:displaying tail of addrwatch logfile description:Shows the last lines of addrwatch's logfile

Now it's necessary to create a newsyslog configuration to ensure that the log file is rotated, as on a busy network with ratelimiting set low or disabled we will quickly run out of storage. Create /usr/local/etc/newsyslog.conf.d/addrwatch.conf:
# configuration file for newsyslog for addrwatch # # see newsyslog.conf(5) for details # # logfilename [owner:group] mode count size when flags [/pid_file] [sig_num] /var/log/addrwatch/addrwatch.log addrwatch:addrwatch 644 7 1024 * JX /var/run/addrwatch.pid

In my situation I quickly realized that the meat-and-potatoes I was looking for would easily be drowned out with ratelimiting set too low. At the same time, with ratelimiting set too high I would be missing important details like flip-flopping. I chose to run two instances side-by-side, logging to separate locations so I can rapidly identify unusual behaviour then optionally refer to the detailed record. This was accomplished by simply duplicating the syshook script and newsyslog configuration, setting the ADDRWATCH_RATELIMIT to disabled (0) in one and indefinite (-1) in the other.

/usr/local/etc/rc.syshook.d/start/50-addrchange:
#!/bin/sh ADDRWATCH_EXEC="/usr/local/bin/addrwatch" ADDRWATCH_USER="addrwatch" # See https://foxpa.ws/unprivileged-user-on-freebsd ADDRWATCH_LOGFILE="/var/log/addrwatch/addrchange.log" ADDRWATCH_PIDFILE="/var/run/addrchange.pid" ADDRWATCH_RATELIMIT="-r -1" # [-r (num)] | >0 = seconds within which duplicate events shall be discarded | 0/[empty] = record all events | -1 = suppress duplicates indefinitely ADDRWATCH_LISTEN_INTERFACES="xn1" ADDRWATCH_PARAMS="" $ADDRWATCH_EXEC -d -u $ADDRWATCH_USER -o $ADDRWATCH_LOGFILE -p $ADDRWATCH_PIDFILE $ADDRWATCH_RATELIMIT $ADDRWATCH_PARAMS $ADDRWATCH_LISTEN_INTERFACES

It's important to ensure that the ADDRWATCH_PIDFILE is at a different location from the other instance, as newsyslog will use the PID written to this location as the target of a SIGHUP signal, which causes addrwatch to reload the output file (releasing its lock and directing output to the new inode after it has been rotated out).

/usr/local/etc/newsyslog.conf.d/addrchange.conf:
# configuration file for newsyslog for addrwatch # # see newsyslog.conf(5) for details #It's important to ensure that the ADDRWATCH_PIDFILE is at a different location, as newsyslog will use the PID written to this location as the target of a SIGHUP signal, which causes addrwatch to reload the output file (releasing its lock and directing output to the new inode after it has been rotated out). # logfilename [owner:group] mode count size when flags [/pid_file] [sig_num] /var/log/addrwatch/addrchange.log addrwatch:addrwatch 644 7 1024 * JX /var/run/addrchange.pid

Comments

There are no comments for this item.