2 * mqtt-arp.c - Watch the Linux ARP table to report device presence via MQTT
4 * Copyright 2018 Jonathan McDowell <noodles@earth.li>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 #include <sys/types.h>
26 #include <sys/socket.h>
29 #include <linux/netlink.h>
30 #include <linux/rtnetlink.h>
32 #include <mosquitto.h>
34 #define MQTT_HOST "mqtt-host"
35 #define MQTT_PORT 8883
36 #define MQTT_USERNAME "username"
37 #define MQTT_PASSWORD "password"
38 #define MQTT_TOPIC "location/by-mac"
39 #define LOCATION "home"
50 struct mac_entry macs[] = {
51 { true, { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 }, 0, 0 },
52 { true, { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }, 0, 0 },
56 bool mac_compare(uint8_t *a, uint8_t *b)
60 for (i = 0; i < 6; i++)
65 printf("Matched: %02x:%02x:%02x:%02x:%02x:%02x\n",
72 int mqtt_mac_presence(struct mosquitto *mosq, uint8_t *mac, bool present)
82 while (macs[i].valid) {
83 if (mac_compare(mac, macs[i].mac))
91 macs[i].last_seen = t;
92 /* Report no more often than every 2 minutes */
93 if (present && macs[i].last_reported + 60 * 2 > t)
96 macs[i].last_reported = t;
98 snprintf(topic, sizeof(topic),
99 "%s/%02X:%02X:%02X:%02X:%02X:%02X",
101 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
104 printf("Publishing to %s\n", topic);
107 ret = mosquitto_publish(mosq, NULL, topic,
108 strlen(LOCATION), LOCATION, 0, 0);
110 ret = mosquitto_publish(mosq, NULL, topic,
111 strlen("unknown"), "unknown", 0, 0);
116 void prune_macs(struct mosquitto *mosq)
124 while (macs[i].valid) {
125 /* Expire after 5 minutes */
126 if (macs[i].last_seen && macs[i].last_seen + 60 * 5 < t) {
127 mqtt_mac_presence(mosq, macs[i].mac, false);
128 macs[i].last_seen = 0;
129 macs[i].last_reported = 0;
135 void mosq_log_callback(struct mosquitto *mosq, void *userdata, int level,
139 printf("%i:%s\n", level, str);
142 int main(int argc, char *argv[])
145 struct sockaddr_nl group_addr;
146 struct nlmsghdr *hdr;
153 struct mosquitto *mosq;
155 bzero(&group_addr, sizeof(group_addr));
156 group_addr.nl_family = AF_NETLINK;
157 group_addr.nl_pid = getpid();
158 group_addr.nl_groups = RTMGRP_NEIGH;
160 sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
162 perror("Couldn't open netlink socket");
166 if (bind(sock, (struct sockaddr *) &group_addr,
167 sizeof(group_addr)) < 0) {
168 perror("Failed to bind to netlink socket");
172 mosquitto_lib_init();
173 mosq = mosquitto_new("mqtt-arp", true, NULL);
175 printf("Couldn't allocate mosquitto structure\n");
179 mosquitto_log_callback_set(mosq, mosq_log_callback);
181 mosquitto_username_pw_set(mosq, MQTT_USERNAME, MQTT_PASSWORD);
182 mosquitto_tls_set(mosq, "/etc/ssl/certs/ca-certificates.crt",
183 NULL, NULL, NULL, NULL);
185 ret = mosquitto_connect(mosq, MQTT_HOST, MQTT_PORT, 60);
187 printf("Unable to connect to MQTT server.\n");
191 ret = mosquitto_loop_start(mosq);
193 printf("Unable to start Mosquitto loop.\n");
197 hdr = (struct nlmsghdr *) buf;
198 nd = (struct ndmsg *) (hdr + 1);
200 received = recv(sock, buf, sizeof(buf), 0);
203 printf("%sReceived %zd bytes:\n", ctime(&t), received);
204 printf(" Len: %d, type: %d, flags: %x, "
205 "seq: %d, pid: %d\n",
206 hdr->nlmsg_len, hdr->nlmsg_type,
207 hdr->nlmsg_flags, hdr->nlmsg_seq,
210 switch (hdr->nlmsg_type) {
213 printf(" Family: %d, interface: %d, "
214 "state: %x, flags: %x, type: %x\n",
215 nd->ndm_family, /* AF_INET etc */
217 nd->ndm_state, /* NUD_REACHABLE etc */
221 attr = (struct nlattr *) (nd + 1);
222 while (attr->nla_len > 0) {
223 data = (((uint8_t *) attr) + 4);
224 if (attr->nla_type == NDA_LLADDR &&
225 nd->ndm_state == NUD_REACHABLE) {
226 mqtt_mac_presence(mosq, data, true);
228 attr = (struct nlattr *)
229 (((uint8_t *) attr) + attr->nla_len);
235 printf("Unknown message type: %d\n", hdr->nlmsg_type);