]> the.earth.li Git - mqtt-arp.git/blob - mqtt-arp.c
Initial commit
[mqtt-arp.git] / mqtt-arp.c
1 /*
2  * mqtt-arp.c - Watch the Linux ARP table to report device presence via MQTT
3  *
4  * Copyright 2018 Jonathan McDowell <noodles@earth.li>
5  *
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.
10  *
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.
15  *
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/>.
18  */
19 #include <stdbool.h>
20 #include <stdint.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <strings.h>
25 #include <sys/types.h>
26 #include <sys/socket.h>
27 #include <time.h>
28 #include <unistd.h>
29 #include <linux/netlink.h>
30 #include <linux/rtnetlink.h>
31
32 #include <mosquitto.h>
33
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"
40
41 struct mac_entry {
42         bool valid;
43         uint8_t mac[6];
44         time_t last_seen;
45         time_t last_reported;
46 };
47
48 bool debug = false;
49
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 },
53         { false }
54 };
55
56 bool mac_compare(uint8_t *a, uint8_t *b)
57 {
58         int i;
59
60         for (i = 0; i < 6; i++)
61                 if (a[i] != b[i])
62                         return false;
63
64         if (debug)
65                 printf("Matched: %02x:%02x:%02x:%02x:%02x:%02x\n",
66                                 a[0], a[1], a[2],
67                                 a[3], a[4], a[5]);
68
69         return true;
70 }
71
72 int mqtt_mac_presence(struct mosquitto *mosq, uint8_t *mac, bool present)
73 {
74         char topic[128];
75         int ret;
76         time_t t;
77         int i;
78
79         t = time(NULL);
80
81         i = 0;
82         while (macs[i].valid) {
83                 if (mac_compare(mac, macs[i].mac))
84                         break;
85                 i++;
86         }
87
88         if (!macs[i].valid)
89                 return 0;
90
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)
94                 return 0;
95
96         macs[i].last_reported = t;
97
98         snprintf(topic, sizeof(topic),
99                 "%s/%02X:%02X:%02X:%02X:%02X:%02X",
100                 MQTT_TOPIC,
101                 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
102
103         if (debug)
104                 printf("Publishing to %s\n", topic);
105
106         if (present)
107                 ret = mosquitto_publish(mosq, NULL, topic,
108                                 strlen(LOCATION), LOCATION, 0, 0);
109         else
110                 ret = mosquitto_publish(mosq, NULL, topic,
111                                 strlen("unknown"), "unknown", 0, 0);
112
113         return ret;
114 }
115
116 void prune_macs(struct mosquitto *mosq)
117 {
118         time_t t;
119         int i;
120
121         t = time(NULL);
122
123         i = 0;
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;
130                 }
131                 i++;
132         }
133 }
134
135 void mosq_log_callback(struct mosquitto *mosq, void *userdata, int level,
136                 const char *str)
137 {
138         if (debug)
139                 printf("%i:%s\n", level, str);
140 }
141
142 int main(int argc, char *argv[])
143 {
144         int ret, sock;
145         struct sockaddr_nl group_addr;
146         struct nlmsghdr *hdr;
147         uint8_t buf[4096];
148         ssize_t received;
149         struct ndmsg *nd;
150         struct nlattr *attr;
151         uint8_t *data;
152         time_t t;
153         struct mosquitto *mosq;
154
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;
159
160         sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
161         if (sock < 0) {
162                 perror("Couldn't open netlink socket");
163                 exit(EXIT_FAILURE);
164         }
165
166         if (bind(sock, (struct sockaddr *) &group_addr,
167                         sizeof(group_addr)) < 0) {
168                 perror("Failed to bind to netlink socket");
169                 exit(EXIT_FAILURE);
170         }
171
172         mosquitto_lib_init();
173         mosq = mosquitto_new("mqtt-arp", true, NULL);
174         if (!mosq) {
175                 printf("Couldn't allocate mosquitto structure\n");
176                 exit(EXIT_FAILURE);
177         }
178
179         mosquitto_log_callback_set(mosq, mosq_log_callback);
180
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);
184
185         ret = mosquitto_connect(mosq, MQTT_HOST, MQTT_PORT, 60);
186         if (ret) {
187                 printf("Unable to connect to MQTT server.\n");
188                 exit(EXIT_FAILURE);
189         }
190
191         ret = mosquitto_loop_start(mosq);
192         if (ret) {
193                 printf("Unable to start Mosquitto loop.\n");
194                 exit(EXIT_FAILURE);
195         }
196
197         hdr = (struct nlmsghdr *) buf;
198         nd = (struct ndmsg *) (hdr + 1);
199         while (1) {
200                 received = recv(sock, buf, sizeof(buf), 0);
201                 if (debug) {
202                         t = time(NULL);
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,
208                                 hdr->nlmsg_pid);
209                 }
210                 switch (hdr->nlmsg_type) {
211                 case RTM_NEWNEIGH:
212                         if (debug) {
213                                 printf("  Family: %d, interface: %d, "
214                                         "state: %x, flags: %x, type: %x\n",
215                                         nd->ndm_family, /* AF_INET etc */
216                                         nd->ndm_ifindex,
217                                         nd->ndm_state, /* NUD_REACHABLE etc */
218                                         nd->ndm_flags,
219                                         nd->ndm_type);
220                         }
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);
227                                 }
228                                 attr = (struct nlattr *)
229                                         (((uint8_t *) attr) + attr->nla_len);
230                         }
231                         break;
232                 case RTM_DELNEIGH:
233                 case RTM_GETNEIGH:
234                 default:
235                         printf("Unknown message type: %d\n", hdr->nlmsg_type);
236                 }
237
238                 prune_macs(mosq);
239         }
240 }