516 lines
13 KiB
C
516 lines
13 KiB
C
/*
|
|
Copyright (c) 2012, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <alloca.h>
|
|
#include <errno.h>
|
|
#include <libgen.h>
|
|
#include <poll.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <linux/rtnetlink.h>
|
|
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netpacket/packet.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
|
|
#define ANNOUNCE_INTERVAL 10
|
|
#define ANNOUNCE_TIMEOUT (3*ANNOUNCE_INTERVAL)
|
|
#define ANNOUNCE_PRUNE (3*ANNOUNCE_TIMEOUT)
|
|
|
|
#define SYSFS_PATH_MAX 256
|
|
#define SYSFS_CLASS_NET "/sys/class/net"
|
|
|
|
#define FFD_MAGIC 0xffd
|
|
#define FFVISD_VERSION 0
|
|
|
|
#define FFD_PROTO 0xffd
|
|
#define FFD_PORT 0xffd
|
|
|
|
|
|
typedef struct __attribute__((__packed__)) _eth_addr_t {
|
|
uint8_t address[ETH_ALEN];
|
|
} eth_addr_t;
|
|
|
|
typedef struct __attribute__((__packed__)) _ffvisd_ipv6_host_addr_t {
|
|
uint8_t address[8];
|
|
} ffvisd_ipv6_host_addr_t;
|
|
|
|
typedef struct __attribute__((__packed__)) _ffvisd_packet_server_announce_t {
|
|
uint16_t seqno;
|
|
eth_addr_t eth_address;
|
|
ffvisd_ipv6_host_addr_t address;
|
|
} ffvisd_packet_server_announce_t;
|
|
|
|
typedef struct __attribute__((__packed__)) _ffvisd_packet_announce_t {
|
|
uint16_t magic;
|
|
uint16_t version;
|
|
uint16_t csum;
|
|
uint16_t n_servers;
|
|
ffvisd_packet_server_announce_t servers[];
|
|
} ffvisd_packet_announce_t;
|
|
|
|
typedef enum _ffvisd_iftype_t {
|
|
IF_UNKNOWN = 0,
|
|
IF_WIRED,
|
|
IF_WIRELESS,
|
|
IF_VIRTUAL,
|
|
IF_BRIDGE,
|
|
IF_MESH,
|
|
IF_MAX
|
|
} ffvisd_iftype_t;
|
|
|
|
typedef struct _ffvisd_announce_t {
|
|
struct _ffvisd_announce_t *next;
|
|
|
|
struct timespec received;
|
|
ffvisd_packet_server_announce_t announce;
|
|
} ffvisd_announce_t;
|
|
|
|
/*static const char *ffvisd_iftype_names[IF_MAX] = {
|
|
[IF_UNKNOWN] = "unknown",
|
|
[IF_WIRED] = "wired",
|
|
[IF_WIRELESS] = "wireless",
|
|
[IF_VIRTUAL] = "virtual",
|
|
[IF_BRIDGE] = "bridge",
|
|
[IF_MESH] = "mesh",
|
|
};*/
|
|
|
|
|
|
typedef void (*iface_cb)(const char *ifname, unsigned ifindex, void *arg);
|
|
|
|
|
|
static const eth_addr_t eth_addr_unspec = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
|
|
|
static const eth_addr_t ffd_addr = {{0x03, 0x00, 0x00, 0x00, 0x0f, 0xfd}};
|
|
|
|
|
|
static inline bool is_eth_addr_unspec(const eth_addr_t *address) {
|
|
const uint8_t *a = address->address;
|
|
|
|
if (a[0]||a[1]||a[2]||a[3]||a[4]||a[5])
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
|
|
#define ETH_ADDR_IS_UNSPEC(a) (!((a).address[0]||(a).address[0]||(a).address[0]||(a).address[0]||(a).address[0]||(a).address[0]))
|
|
|
|
|
|
static int sockfd;
|
|
static char *mesh = "bat0";
|
|
|
|
|
|
ffvisd_announce_t *announcements = NULL;
|
|
|
|
|
|
/* returns (tp1 - tp2) in milliseconds */
|
|
static inline int timespec_diff(const struct timespec *tp1, const struct timespec *tp2) {
|
|
return ((tp1->tv_sec - tp2->tv_sec))*1000 + (tp1->tv_nsec - tp2->tv_nsec)/1e6;
|
|
}
|
|
|
|
|
|
static bool file_readv(const char *file, const char *format, va_list ap) {
|
|
FILE *f = fopen(file, "r");
|
|
if (!f)
|
|
return false;
|
|
|
|
int ret = vfscanf(f, format, ap);
|
|
|
|
fclose(f);
|
|
|
|
return (ret > 0);
|
|
}
|
|
|
|
static bool iface_file_read(const char *ifname, const char *file, const char *format, ...) {
|
|
char filename[SYSFS_PATH_MAX];
|
|
|
|
snprintf(filename, SYSFS_PATH_MAX, SYSFS_CLASS_NET"/%s/%s", ifname, file);
|
|
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
bool ret = file_readv(filename, format, ap);
|
|
va_end(ap);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool iface_file_exists(const char *ifname, const char *file) {
|
|
char filename[SYSFS_PATH_MAX];
|
|
|
|
snprintf(filename, SYSFS_PATH_MAX, SYSFS_CLASS_NET"/%s/%s", ifname, file);
|
|
|
|
struct stat st;
|
|
return (stat(filename, &st) == 0);
|
|
}
|
|
|
|
static bool iface_is_mesh(const char *ifname) {
|
|
return iface_file_exists(ifname, "mesh");
|
|
}
|
|
|
|
static char* iface_get_mesh(const char *ifname) {
|
|
char *mesh = NULL;
|
|
|
|
if (!iface_file_read(ifname, "batman_adv/mesh_iface", "%as", &mesh) || !strcmp(mesh, "none")) {
|
|
free(mesh);
|
|
return NULL;
|
|
}
|
|
|
|
return mesh;
|
|
}
|
|
|
|
static char* iface_get_bridge(const char *ifname) {
|
|
char filename[SYSFS_PATH_MAX], filename2[SYSFS_PATH_MAX] = {0};
|
|
|
|
snprintf(filename, SYSFS_PATH_MAX, SYSFS_CLASS_NET"/%s/brport/bridge", ifname);
|
|
if (readlink(filename, filename2, sizeof(filename2)) < 0)
|
|
return NULL;
|
|
|
|
return strdup(basename(filename2));
|
|
}
|
|
|
|
static eth_addr_t iface_get_eth_address(const char *ifname) {
|
|
eth_addr_t ret;
|
|
uint8_t *a = ret.address;
|
|
|
|
if (!iface_file_read(ifname, "address", "%x:%x:%x:%x:%x:%x", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5]))
|
|
return eth_addr_unspec;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct in6_addr iface_get_address(unsigned ifindex, int scope) {
|
|
struct msg {
|
|
struct nlmsghdr nh;
|
|
struct ifaddrmsg addr;
|
|
uint8_t attrbuf[16384];
|
|
} msg;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
|
|
int rtnetlink_sk = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
|
|
|
msg.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
|
|
msg.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
|
|
msg.nh.nlmsg_type = RTM_GETADDR;
|
|
|
|
msg.addr.ifa_family = AF_INET6;
|
|
|
|
if(write(rtnetlink_sk, &msg, msg.nh.nlmsg_len) <= 0) {
|
|
fprintf(stderr, "error: write: %m\n");
|
|
close(rtnetlink_sk);
|
|
return in6addr_any;
|
|
}
|
|
|
|
int readlen = read(rtnetlink_sk, &msg, sizeof(msg));
|
|
|
|
if (readlen <= 0) {
|
|
fprintf(stderr, "error: read: %m\n");
|
|
close(rtnetlink_sk);
|
|
return in6addr_any;
|
|
}
|
|
|
|
close(rtnetlink_sk);
|
|
|
|
struct msg *chunk;
|
|
|
|
for (chunk = &msg; readlen > sizeof(struct nlmsghdr);) {
|
|
int len = chunk->nh.nlmsg_len - sizeof(struct nlmsghdr);
|
|
|
|
if (len < sizeof(struct ifaddrmsg) || readlen < len)
|
|
return in6addr_any;
|
|
|
|
if (!NLMSG_OK(&chunk->nh, readlen))
|
|
return in6addr_any;
|
|
|
|
if (chunk->nh.nlmsg_type == RTM_NEWADDR && chunk->addr.ifa_scope == scope && chunk->addr.ifa_index == ifindex) {
|
|
struct rtattr *rta = (struct rtattr *)IFA_RTA(&chunk->addr);
|
|
int rtattrlen = IFA_PAYLOAD(&chunk->nh);
|
|
|
|
for (; RTA_OK(rta, rtattrlen); rta = RTA_NEXT(rta, rtattrlen)) {
|
|
if(rta->rta_type == IFA_ADDRESS && rta->rta_len == RTA_LENGTH(16)) {
|
|
struct in6_addr ret;
|
|
memcpy(ret.s6_addr, RTA_DATA(rta), 16);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
readlen -= NLMSG_ALIGN(chunk->nh.nlmsg_len);
|
|
chunk = (struct msg*)((uint8_t*)chunk + NLMSG_ALIGN(chunk->nh.nlmsg_len));
|
|
}
|
|
|
|
return in6addr_any;
|
|
}
|
|
|
|
static void ffvisd_iface_foreach(iface_cb cb, void *arg) {
|
|
struct if_nameindex *ifaces = if_nameindex();
|
|
|
|
if (!ifaces) {
|
|
fprintf(stderr, "error: if_nameindex: %m\n");
|
|
return;
|
|
}
|
|
|
|
int i;
|
|
for (i = 0; ifaces[i].if_name; i++)
|
|
cb(ifaces[i].if_name, ifaces[i].if_index, arg);
|
|
|
|
if_freenameindex(ifaces);
|
|
}
|
|
|
|
|
|
static void join_mcast(const char *ifname, unsigned ifindex, void *arg) {
|
|
char *if_mesh = iface_get_mesh(ifname);
|
|
|
|
if (if_mesh && !strcmp(if_mesh, mesh)) {
|
|
struct packet_mreq mr;
|
|
memset(&mr, 0, sizeof(mr));
|
|
mr.mr_ifindex = ifindex;
|
|
mr.mr_type = PACKET_MR_MULTICAST;
|
|
mr.mr_alen = ETH_ALEN;
|
|
memcpy(mr.mr_address, ffd_addr.address, ETH_ALEN);
|
|
if (setsockopt(sockfd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr)) && errno != EADDRINUSE)
|
|
fprintf(stderr, "warning: setsockopt: %m\n");
|
|
}
|
|
|
|
free(if_mesh);
|
|
}
|
|
|
|
static void pr_announcement_packet(const ffvisd_packet_server_announce_t *announce) {
|
|
const uint8_t *e = announce->eth_address.address, *a = announce->address.address;
|
|
|
|
printf("seqno: %u, ether: %02x:%02x:%02x:%02x:%02x:%02x, address: fe80::%02x%02x:%02x%02x:%02x%02x:%02x%02x\n",
|
|
announce->seqno,
|
|
e[0], e[1], e[2], e[3], e[4], e[5],
|
|
a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]);
|
|
}
|
|
|
|
static void pr_announcement(const ffvisd_announce_t *announce) {
|
|
if (!announce->received.tv_sec) {
|
|
printf("local, ");
|
|
}
|
|
else {
|
|
struct timespec tv;
|
|
clock_gettime(CLOCK_MONOTONIC, &tv);
|
|
printf("age: %.1fs, ", timespec_diff(&tv, &announce->received)/1000.0);
|
|
}
|
|
|
|
pr_announcement_packet(&announce->announce);
|
|
}
|
|
|
|
static bool check_config() {
|
|
if (!iface_is_mesh(mesh)) {
|
|
fprintf(stderr, "error: configured interface is no mesh\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool init_socket() {
|
|
sockfd = socket(AF_PACKET, SOCK_DGRAM, htons(FFD_PROTO));
|
|
if (sockfd < 0) {
|
|
fprintf(stderr, "error: socket: %m\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool init_announcements() {
|
|
char *ifname = iface_get_bridge(mesh);
|
|
|
|
if (!ifname)
|
|
ifname = strdup(mesh);
|
|
|
|
unsigned ifindex = if_nametoindex(ifname);
|
|
|
|
if (!ifindex) {
|
|
free(ifname);
|
|
return false;
|
|
}
|
|
|
|
ffvisd_announce_t *announce = announcements = calloc(1, sizeof(ffvisd_announce_t));
|
|
|
|
announce->announce.eth_address = iface_get_eth_address(ifname);
|
|
if (is_eth_addr_unspec(&announce->announce.eth_address)) {
|
|
free(ifname);
|
|
return false;
|
|
}
|
|
|
|
struct in6_addr addr = iface_get_address(ifindex, RT_SCOPE_LINK);
|
|
if (IN6_IS_ADDR_UNSPECIFIED(&addr)) {
|
|
free(ifname);
|
|
return false;
|
|
}
|
|
|
|
memcpy(announce->announce.address.address, addr.s6_addr+8, 8);
|
|
|
|
free(ifname);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void maintenance() {
|
|
ffvisd_announce_t **announce;
|
|
|
|
for(announce = &announcements; *announce; announce = &(*announce)->next) {
|
|
if (!(*announce)->received.tv_sec) /* announcement is local */
|
|
(*announce)->announce.seqno++;
|
|
}
|
|
}
|
|
|
|
static void send_announcement(const char *ifname, unsigned ifindex, void *arg) {
|
|
ffvisd_packet_announce_t *packet = arg;
|
|
|
|
struct sockaddr_ll sa;
|
|
memset(&sa, 0, sizeof(sa));
|
|
|
|
sa.sll_family = AF_PACKET;
|
|
sa.sll_protocol = htons(FFD_PROTO);
|
|
sa.sll_ifindex = ifindex;
|
|
sa.sll_halen = ETH_ALEN;
|
|
memcpy(sa.sll_addr, ffd_addr.address, ETH_ALEN);
|
|
|
|
sendto(sockfd, packet, sizeof(ffvisd_packet_announce_t) + ntohs(packet->n_servers)*sizeof(ffvisd_packet_server_announce_t), 0, (struct sockaddr*)&sa, sizeof(sa));
|
|
}
|
|
|
|
static void send_announcements() {
|
|
ffvisd_packet_announce_t *packet = alloca(sizeof(ffvisd_packet_announce_t) + 75*sizeof(ffvisd_packet_server_announce_t));
|
|
|
|
packet->magic = htons(FFD_MAGIC);
|
|
packet->version = htons(FFVISD_VERSION);
|
|
packet->csum = 0;
|
|
|
|
uint16_t n_servers = 0;
|
|
ffvisd_announce_t *announce;
|
|
for(announce = announcements; announce; announce = announce->next) {
|
|
packet->servers[n_servers++] = announce->announce;
|
|
}
|
|
|
|
packet->n_servers = htons(n_servers);
|
|
|
|
ffvisd_iface_foreach(send_announcement, packet);
|
|
}
|
|
|
|
static void receive_announcement() {
|
|
ffvisd_packet_announce_t *packet = alloca(sizeof(ffvisd_packet_announce_t) + 75*sizeof(ffvisd_packet_server_announce_t));
|
|
|
|
int readlen = read(sockfd, packet, sizeof(ffvisd_packet_announce_t) + 75*sizeof(ffvisd_packet_server_announce_t));
|
|
|
|
if (readlen < 0) {
|
|
fprintf(stderr, "error: read: %m\n");
|
|
return;
|
|
}
|
|
|
|
if (readlen < sizeof(ffvisd_packet_announce_t)) {
|
|
fprintf(stderr, "warning: short read");
|
|
return;
|
|
}
|
|
|
|
int n_servers = ntohs(packet->n_servers);
|
|
|
|
if (readlen < sizeof(ffvisd_packet_announce_t) + n_servers*sizeof(ffvisd_packet_server_announce_t)) {
|
|
fprintf(stderr, "warning: short read");
|
|
return;
|
|
}
|
|
|
|
puts("Received announcements:");
|
|
int i;
|
|
for(i = 0; i < n_servers; i++)
|
|
pr_announcement_packet(&packet->servers[i]);
|
|
puts("");
|
|
}
|
|
|
|
int main() {
|
|
if (!check_config())
|
|
return 1;
|
|
|
|
if (!init_socket())
|
|
return 1;
|
|
|
|
if (!init_announcements())
|
|
return 1;
|
|
|
|
struct timespec next_announce;
|
|
clock_gettime(CLOCK_MONOTONIC, &next_announce);
|
|
|
|
while (true) {
|
|
ffvisd_iface_foreach(join_mcast, NULL);
|
|
|
|
maintenance();
|
|
|
|
puts("Dumping announcements...");
|
|
ffvisd_announce_t *announce;
|
|
for(announce = announcements; announce; announce = announce->next)
|
|
pr_announcement(announce);
|
|
puts("");
|
|
|
|
send_announcements();
|
|
|
|
next_announce.tv_sec += ANNOUNCE_INTERVAL;
|
|
|
|
while (true) {
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
int timeout = timespec_diff(&next_announce, &now);
|
|
|
|
if (timeout <= 0)
|
|
break;
|
|
|
|
struct pollfd fds[1];
|
|
|
|
fds[0].fd = sockfd;
|
|
fds[0].events = POLLIN;
|
|
|
|
poll(fds, 1, timeout);
|
|
|
|
if (fds[0].revents & POLLIN)
|
|
receive_announcement();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|