X-Git-Url: https://the.earth.li/gitweb/?p=mqtt-arp.git;a=blobdiff_plain;f=mqtt-arp.c;h=2836366b40391dfa6d9373e8fb9e316268f91d28;hp=9d4887012b8bcb826730878698ae7481bff318f2;hb=939e344284056025f919de31e56ac4e7a7ea71e2;hpb=01910e71be0abe5b89670704fc698da9dd0d29af diff --git a/mqtt-arp.c b/mqtt-arp.c index 9d48870..2836366 100644 --- a/mqtt-arp.c +++ b/mqtt-arp.c @@ -16,6 +16,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include +#include +#include +#include #include #include #include @@ -31,12 +35,19 @@ #include -#define MQTT_HOST "mqtt-host" -#define MQTT_PORT 8883 -#define MQTT_USERNAME "username" -#define MQTT_PASSWORD "password" -#define MQTT_TOPIC "location/by-mac" -#define LOCATION "home" +/* Defaults. All overridable from command line. */ +#define MQTT_HOST "mqtt-host" +#define MQTT_PORT 8883 +#define MQTT_TOPIC "location/by-mac" +#define LOCATION "home" +#define CONFIG_FILE "/etc/mqtt-arp.conf" + +/* How often (in seconds) to report that we see a device */ +#define REPORT_INTERVAL (2 * 60) +/* How long to wait without seeing a device before reporting it's gone */ +#define EXPIRY_TIME (10 * 60) +/* Maximum number of MAC addresses to watch for */ +#define MAX_MACS 8 struct mac_entry { bool valid; @@ -45,13 +56,24 @@ struct mac_entry { time_t last_reported; }; +struct ma_config { + char *mqtt_host; + int mqtt_port; + char *mqtt_username; + char *mqtt_password; + char *mqtt_topic; + char *location; + char *capath; + struct mac_entry macs[MAX_MACS]; +}; + bool debug = false; +bool want_shutdown = false; -struct mac_entry macs[] = { - { true, { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 }, 0, 0 }, - { true, { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }, 0, 0 }, - { false } -}; +void shutdown_request(int signal) +{ + want_shutdown = true; +} bool mac_compare(uint8_t *a, uint8_t *b) { @@ -69,7 +91,8 @@ bool mac_compare(uint8_t *a, uint8_t *b) return true; } -int mqtt_mac_presence(struct mosquitto *mosq, uint8_t *mac, bool present) +int mqtt_mac_presence(struct ma_config *config, struct mosquitto *mosq, + uint8_t *mac, bool present) { char topic[128]; int ret; @@ -79,25 +102,25 @@ int mqtt_mac_presence(struct mosquitto *mosq, uint8_t *mac, bool present) t = time(NULL); i = 0; - while (macs[i].valid) { - if (mac_compare(mac, macs[i].mac)) + while (i < MAX_MACS && config->macs[i].valid) { + if (mac_compare(mac, config->macs[i].mac)) break; i++; } - if (!macs[i].valid) + if (i >= MAX_MACS || !config->macs[i].valid) return 0; - macs[i].last_seen = t; + config->macs[i].last_seen = t; /* Report no more often than every 2 minutes */ - if (present && macs[i].last_reported + 60 * 2 > t) + if (present && config->macs[i].last_reported + REPORT_INTERVAL > t) return 0; - macs[i].last_reported = t; + config->macs[i].last_reported = t; snprintf(topic, sizeof(topic), "%s/%02X:%02X:%02X:%02X:%02X:%02X", - MQTT_TOPIC, + config->mqtt_topic, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); if (debug) @@ -105,7 +128,8 @@ int mqtt_mac_presence(struct mosquitto *mosq, uint8_t *mac, bool present) if (present) ret = mosquitto_publish(mosq, NULL, topic, - strlen(LOCATION), LOCATION, 0, 0); + strlen(config->location), config->location, + 0, 0); else ret = mosquitto_publish(mosq, NULL, topic, strlen("unknown"), "unknown", 0, 0); @@ -113,7 +137,7 @@ int mqtt_mac_presence(struct mosquitto *mosq, uint8_t *mac, bool present) return ret; } -void prune_macs(struct mosquitto *mosq) +void prune_macs(struct ma_config *config, struct mosquitto *mosq) { time_t t; int i; @@ -121,12 +145,14 @@ void prune_macs(struct mosquitto *mosq) t = time(NULL); i = 0; - while (macs[i].valid) { - /* Expire after 5 minutes */ - if (macs[i].last_seen && macs[i].last_seen + 60 * 5 < t) { - mqtt_mac_presence(mosq, macs[i].mac, false); - macs[i].last_seen = 0; - macs[i].last_reported = 0; + while (i < MAX_MACS && config->macs[i].valid) { + /* Expire if we haven't seen MAC in EXPIRY_TIME */ + if (config->macs[i].last_seen && + config->macs[i].last_seen + EXPIRY_TIME < t) { + mqtt_mac_presence(config, mosq, + config->macs[i].mac, false); + config->macs[i].last_seen = 0; + config->macs[i].last_reported = 0; } i++; } @@ -139,64 +165,19 @@ void mosq_log_callback(struct mosquitto *mosq, void *userdata, int level, printf("%i:%s\n", level, str); } -int main(int argc, char *argv[]) +void main_loop(struct ma_config *config, struct mosquitto *mosq, int sock) { - int ret, sock; - struct sockaddr_nl group_addr; - struct nlmsghdr *hdr; uint8_t buf[4096]; - ssize_t received; + uint8_t *data; + struct nlmsghdr *hdr; struct ndmsg *nd; struct nlattr *attr; - uint8_t *data; + ssize_t received; time_t t; - struct mosquitto *mosq; - - bzero(&group_addr, sizeof(group_addr)); - group_addr.nl_family = AF_NETLINK; - group_addr.nl_pid = getpid(); - group_addr.nl_groups = RTMGRP_NEIGH; - - sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (sock < 0) { - perror("Couldn't open netlink socket"); - exit(EXIT_FAILURE); - } - - if (bind(sock, (struct sockaddr *) &group_addr, - sizeof(group_addr)) < 0) { - perror("Failed to bind to netlink socket"); - exit(EXIT_FAILURE); - } - - mosquitto_lib_init(); - mosq = mosquitto_new("mqtt-arp", true, NULL); - if (!mosq) { - printf("Couldn't allocate mosquitto structure\n"); - exit(EXIT_FAILURE); - } - - mosquitto_log_callback_set(mosq, mosq_log_callback); - - mosquitto_username_pw_set(mosq, MQTT_USERNAME, MQTT_PASSWORD); - mosquitto_tls_set(mosq, "/etc/ssl/certs/ca-certificates.crt", - NULL, NULL, NULL, NULL); - - ret = mosquitto_connect(mosq, MQTT_HOST, MQTT_PORT, 60); - if (ret) { - printf("Unable to connect to MQTT server.\n"); - exit(EXIT_FAILURE); - } - - ret = mosquitto_loop_start(mosq); - if (ret) { - printf("Unable to start Mosquitto loop.\n"); - exit(EXIT_FAILURE); - } hdr = (struct nlmsghdr *) buf; nd = (struct ndmsg *) (hdr + 1); - while (1) { + while (!want_shutdown) { received = recv(sock, buf, sizeof(buf), 0); if (debug) { t = time(NULL); @@ -219,22 +200,247 @@ int main(int argc, char *argv[]) nd->ndm_type); } attr = (struct nlattr *) (nd + 1); - while (attr->nla_len > 0) { - data = (((uint8_t *) attr) + 4); + while (((uint8_t *) attr - buf) < hdr->nlmsg_len) { + data = (((uint8_t *) attr) + NLA_HDRLEN); if (attr->nla_type == NDA_LLADDR && nd->ndm_state == NUD_REACHABLE) { - mqtt_mac_presence(mosq, data, true); + mqtt_mac_presence(config, mosq, + data, true); } - attr = (struct nlattr *) - (((uint8_t *) attr) + attr->nla_len); + attr = (struct nlattr *) (((uint8_t *) attr) + + NLA_ALIGN(attr->nla_len)); } break; case RTM_DELNEIGH: case RTM_GETNEIGH: + break; default: printf("Unknown message type: %d\n", hdr->nlmsg_type); } - prune_macs(mosq); + prune_macs(config, mosq); } + +} + +struct mosquitto *mqtt_init(struct ma_config *config) +{ + struct mosquitto *mosq; + int ret; + + mosquitto_lib_init(); + mosq = mosquitto_new("mqtt-arp", true, NULL); + if (!mosq) { + printf("Couldn't allocate mosquitto structure\n"); + exit(EXIT_FAILURE); + } + + mosquitto_log_callback_set(mosq, mosq_log_callback); + + /* DTRT if username is NULL */ + mosquitto_username_pw_set(mosq, + config->mqtt_username, + config->mqtt_password); + if (config->capath) + mosquitto_tls_set(mosq, config->capath, + NULL, NULL, NULL, NULL); + + ret = mosquitto_connect(mosq, config->mqtt_host, + config->mqtt_port, 60); + if (ret) { + printf("Unable to connect to MQTT server.\n"); + exit(EXIT_FAILURE); + } + + ret = mosquitto_loop_start(mosq); + if (ret) { + printf("Unable to start Mosquitto loop.\n"); + exit(EXIT_FAILURE); + } + + return mosq; +} + +int netlink_init(void) +{ + int sock; + struct sockaddr_nl group_addr; + + bzero(&group_addr, sizeof(group_addr)); + group_addr.nl_family = AF_NETLINK; + group_addr.nl_pid = getpid(); + group_addr.nl_groups = RTMGRP_NEIGH; + + sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock < 0) { + perror("Couldn't open netlink socket"); + exit(EXIT_FAILURE); + } + + if (bind(sock, (struct sockaddr *) &group_addr, + sizeof(group_addr)) < 0) { + perror("Failed to bind to netlink socket"); + exit(EXIT_FAILURE); + } + + return sock; +} + +int read_config(char *file, struct ma_config *config, int *macs) +{ + FILE *f; + char line[256]; + int i; + + f = fopen(file, "r"); + if (f == NULL) + return errno; + +#define INT_OPTION(opt, var) \ + if (strncmp(line, opt " ", sizeof(opt)) == 0) { \ + var = atoi(&line[sizeof(opt)]); \ + } +#define STRING_OPTION(opt, var) \ + if (strncmp(line, opt " ", sizeof(opt)) == 0) { \ + var = strdup(&line[sizeof(opt)]); \ + } + + while (fgets(line, sizeof(line), f) != NULL) { + for (i = strlen(line) - 1; i >= 0 && isspace(line[i]); i--) + line[i] = '\0'; + if (line[0] == '\0' || line[0] == '#') + continue; + + if (strncmp(line, "mac ", 4) == 0) { + if (*macs >= MAX_MACS) { + printf("Can only accept %d MAC addresses to" + " watch for.\n", MAX_MACS); + exit(EXIT_FAILURE); + } + sscanf(&line[4], + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &config->macs[*macs].mac[0], + &config->macs[*macs].mac[1], + &config->macs[*macs].mac[2], + &config->macs[*macs].mac[3], + &config->macs[*macs].mac[4], + &config->macs[*macs].mac[5]); + config->macs[*macs].valid = true; + (*macs)++; + } else + STRING_OPTION("mqtt_host", config->mqtt_host) else + INT_OPTION("mqtt_port", config->mqtt_port) else + STRING_OPTION("mqtt_user", config->mqtt_username) else + STRING_OPTION("mqtt_pass", config->mqtt_password) else + STRING_OPTION("mqtt_topic", config->mqtt_topic) else + STRING_OPTION("location", config->location) else + STRING_OPTION("capath", config->capath) + } + fclose(f); + + return 0; +} + +struct option long_options[] = { + { "capath", required_argument, 0, 'c' }, + { "host", required_argument, 0, 'h' }, + { "location", required_argument, 0, 'l' }, + { "mac", required_argument, 0, 'm' }, + { "password", required_argument, 0, 'P' }, + { "port", required_argument, 0, 'p' }, + { "topic", required_argument, 0, 't' }, + { "username", required_argument, 0, 'u' }, + { "verbose", no_argument, 0, 'v' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + int sock; + struct mosquitto *mosq; + struct ma_config config; + int option_index = 0; + int macs = 0; + char c; + + bzero(&config, sizeof(config)); + config.mqtt_port = MQTT_PORT; + + /* Read config before parsing command line */ + read_config(CONFIG_FILE, &config, &macs); + + while (1) { + c = getopt_long(argc, argv, "c:h:l:m:p:P:t:u:v", + long_options, &option_index); + + if (c == -1) + break; + + switch (c) { + case 'c': + config.capath = optarg; + break; + case 'h': + config.mqtt_host = optarg; + break; + case 'l': + config.location = optarg; + break; + case 'm': + if (macs >= MAX_MACS) { + printf("Can only accept %d MAC addresses to" + " watch for.\n", MAX_MACS); + exit(EXIT_FAILURE); + } + sscanf(optarg, + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &config.macs[macs].mac[0], + &config.macs[macs].mac[1], + &config.macs[macs].mac[2], + &config.macs[macs].mac[3], + &config.macs[macs].mac[4], + &config.macs[macs].mac[5]); + config.macs[macs].valid = true; + macs++; + break; + case 'p': + config.mqtt_port = atoi(optarg); + break; + case 'P': + config.mqtt_password = optarg; + break; + case 't': + config.mqtt_topic = optarg; + break; + case 'u': + config.mqtt_username = optarg; + break; + case 'v': + debug = true; + break; + default: + printf("Unrecognized option: %c\n", c); + exit(EXIT_FAILURE); + } + } + + if (!config.mqtt_host) + config.mqtt_host = MQTT_HOST; + if (!config.mqtt_topic) + config.mqtt_topic = MQTT_TOPIC; + if (!config.location) + config.location = LOCATION; + + signal(SIGTERM, shutdown_request); + + sock = netlink_init(); + mosq = mqtt_init(&config); + + main_loop(&config, mosq, sock); + + mosquitto_disconnect(mosq); + mosquitto_loop_stop(mosq, true); + mosquitto_destroy(mosq); + mosquitto_lib_cleanup(); + close(sock); }