524 lines
13 KiB
C
524 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.
|
|
*/
|
|
|
|
|
|
#include "ffd.h"
|
|
#include "neigh.h"
|
|
#include "netif.h"
|
|
#include "packet.h"
|
|
#include "tlv.h"
|
|
#include "tlv_types.h"
|
|
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <netpacket/packet.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
|
const eth_addr_t ffd_addr = {{0x03, 0x00, 0x00, 0x00, 0x0f, 0xfd}};
|
|
|
|
|
|
static char *mesh = "bat0";
|
|
|
|
int sockfd;
|
|
struct timespec now;
|
|
|
|
ffd_node_id_t self;
|
|
|
|
ffd_iface_t *iface_list = NULL;
|
|
|
|
ffd_announce_t *announce_list = NULL;
|
|
|
|
|
|
static inline bool use_netif(const char *ifname) {
|
|
char *if_mesh = netif_get_mesh(ifname);
|
|
bool ret = (if_mesh && !strcmp(if_mesh, mesh));
|
|
free(if_mesh);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void update_time(void) {
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
}
|
|
|
|
static bool check_config(void) {
|
|
if (!netif_is_mesh(mesh)) {
|
|
fprintf(stderr, "error: configured interface is no mesh\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool init_self(void) {
|
|
eth_addr_t primary_addr = netif_mesh_get_primary_addr(mesh);
|
|
if (is_eth_addr_unspec(&primary_addr))
|
|
return false;
|
|
|
|
self.id[0] = primary_addr.d[0]^0x02;
|
|
self.id[1] = primary_addr.d[1];
|
|
self.id[2] = primary_addr.d[2];
|
|
self.id[3] = 0xff;
|
|
self.id[4] = 0xfe;
|
|
self.id[5] = primary_addr.d[3];
|
|
self.id[6] = primary_addr.d[4];
|
|
self.id[7] = primary_addr.d[5];
|
|
|
|
ffd_announce_t *announce = announce_list = calloc(1, sizeof(ffd_announce_t));
|
|
|
|
announce->node = self;
|
|
announce->type = 1;
|
|
announce->key = 1337;
|
|
|
|
announce->nexthop_list = announce->selected = calloc(1, sizeof(ffd_nexthop_t));
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool init_socket(void) {
|
|
sockfd = socket(AF_PACKET, SOCK_DGRAM, htons(FFD_PROTO));
|
|
if (sockfd < 0) {
|
|
fprintf(stderr, "error: socket: %m\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline ffd_iface_t* get_iface(unsigned ifindex) {
|
|
ffd_iface_t *iface;
|
|
for (iface = iface_list; iface; iface = iface->next) {
|
|
if (iface->ifindex == ifindex)
|
|
return iface;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void update_netif(const char *ifname, unsigned ifindex, void *arg) {
|
|
if (!use_netif(ifname))
|
|
return;
|
|
|
|
/* join multicast group */
|
|
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.d, ETH_ALEN);
|
|
if (setsockopt(sockfd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr)) && errno != EADDRINUSE)
|
|
fprintf(stderr, "warning: setsockopt: %m\n");
|
|
|
|
ffd_iface_t *iface = get_iface(ifindex);
|
|
|
|
if (!iface) {
|
|
/* new iface */
|
|
iface = malloc(sizeof(ffd_iface_t));
|
|
iface->next = iface_list;
|
|
iface_list = iface;
|
|
|
|
iface->seqno = 0;
|
|
iface->neigh_list = NULL;
|
|
}
|
|
|
|
iface->ifindex = ifindex;
|
|
strncpy(iface->name, ifname, IF_NAMESIZE);
|
|
iface->type = netif_get_type(ifname);
|
|
iface->addr = netif_get_eth_addr(ifname);
|
|
}
|
|
|
|
static void update_netifs(void) {
|
|
ffd_iface_t *iface, **cur;
|
|
|
|
for (iface = iface_list; iface; iface = iface->next)
|
|
iface->type = IF_UNSPEC;
|
|
|
|
netif_foreach(update_netif, NULL);
|
|
|
|
cur = &iface_list;
|
|
while (*cur) {
|
|
iface = *cur;
|
|
|
|
if (iface->type == IF_UNSPEC) {
|
|
*cur = iface->next;
|
|
|
|
ffd_neigh_free_list(iface->neigh_list);
|
|
free(iface);
|
|
}
|
|
else {
|
|
cur = &iface->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct _handle_tlv_arg_t {
|
|
ffd_iface_t *iface;
|
|
const eth_addr_t *addr;
|
|
|
|
ffd_node_id_t node_id;
|
|
} handle_tlv_arg_t;
|
|
|
|
static inline ffd_neigh_t* get_tlv_neigh(handle_tlv_arg_t *arg) {
|
|
return ffd_neigh_get(arg->iface, arg->addr);
|
|
}
|
|
|
|
static void handle_tlv_ack_req(const ffd_tlv_ack_req_t *tlv_req, size_t len, handle_tlv_arg_t *arg) {
|
|
if (len < sizeof(ffd_tlv_ack_req_t)) {
|
|
fprintf(stderr, "warn: received short acknowledement request TLV.\n");
|
|
return;
|
|
}
|
|
|
|
ffd_neigh_t *neigh = get_tlv_neigh(arg);
|
|
|
|
ffd_send_ack(arg->iface, neigh, ntohs(tlv_req->nonce));
|
|
}
|
|
|
|
static void handle_tlv_hello(const ffd_tlv_hello_t *tlv_hello, size_t len, handle_tlv_arg_t *arg) {
|
|
if (len < sizeof(ffd_tlv_hello_t)) {
|
|
fprintf(stderr, "warn: received short hello TLV.\n");
|
|
return;
|
|
}
|
|
|
|
fprintf(stderr, "debug: received hello with seqno %u.\n", ntohs(tlv_hello->seqno));
|
|
|
|
ffd_neigh_t *neigh = get_tlv_neigh(arg);
|
|
|
|
uint16_t seqno = ntohs(tlv_hello->seqno);
|
|
|
|
if (neigh->last_hello.tv_sec) {
|
|
int timediff = timespec_diff(&now, &neigh->last_hello)/10;
|
|
uint16_t seqexp = neigh->last_seqno + (timediff - neigh->hello_interval/2)/neigh->hello_interval;
|
|
|
|
/* cast to int16_t to ensure correct handling of seqno wrapping */
|
|
if (abs((int16_t)(seqno - seqexp)) > 16) {
|
|
fprintf(stderr, "info: neighbour was reset.\n");
|
|
neigh->hello_log = 0;
|
|
}
|
|
else {
|
|
int16_t seqdiff = seqno - neigh->last_seqno;
|
|
if (seqdiff <= 0) {
|
|
fprintf(stderr, "debug: seqno already seen, ignoring.\n");
|
|
return;
|
|
}
|
|
|
|
neigh->hello_log <<= seqdiff;
|
|
}
|
|
}
|
|
else {
|
|
fprintf(stderr, "info: received hello from new neighbour.\n");
|
|
}
|
|
|
|
neigh->hello_log |= 1;
|
|
neigh->hello_interval = ntohs(tlv_hello->interval);
|
|
neigh->last_seqno = seqno;
|
|
neigh->last_hello = now;
|
|
|
|
if (neigh->hello_log == 1) /* new or reset neighbour */
|
|
ffd_neigh_reset(arg->iface, neigh);
|
|
|
|
fprintf(stderr, "debug: accepted hello, log %04x, rxcost is %u, cost is %u now.\n", neigh->hello_log, ffd_neigh_get_rxcost(neigh), ffd_neigh_get_cost(neigh));
|
|
}
|
|
|
|
static void handle_tlv_ihu(const ffd_tlv_ihu_t *tlv_ihu, size_t len, handle_tlv_arg_t *arg) {
|
|
if (len < sizeof(ffd_tlv_ihu_t)) {
|
|
fprintf(stderr, "warn: received short IHU TLV.\n");
|
|
return;
|
|
}
|
|
|
|
if (tlv_ihu->ae == ADDR_ENC_ETH) {
|
|
if (len < sizeof(ffd_tlv_ihu_t)+sizeof(eth_addr_t)) {
|
|
fprintf(stderr, "warn: received short IHU TLV.\n");
|
|
return;
|
|
}
|
|
|
|
if (memcmp(tlv_ihu->address, &arg->iface->addr, sizeof(eth_addr_t)) != 0)
|
|
return;
|
|
}
|
|
else if (tlv_ihu->ae != ADDR_ENC_UNSPEC) {
|
|
return;
|
|
}
|
|
|
|
ffd_neigh_t *neigh = get_tlv_neigh(arg);
|
|
neigh->last_ihu = now;
|
|
neigh->txcost = ntohs(tlv_ihu->rxcost);
|
|
|
|
fprintf(stderr, "debug: accepted IHU, txcost is %u, cost is %u now.\n", neigh->txcost, ffd_neigh_get_cost(neigh));
|
|
}
|
|
|
|
static void handle_tlv_node_id(const ffd_tlv_node_id_t *tlv_node_id, size_t len, handle_tlv_arg_t *arg) {
|
|
if (len < sizeof(ffd_tlv_node_id_t)) {
|
|
fprintf(stderr, "warn: received short node id TLV.\n");
|
|
return;
|
|
}
|
|
|
|
arg->node_id = tlv_node_id->id;
|
|
}
|
|
|
|
static ffd_announce_t* get_announce(const ffd_node_id_t *node, uint16_t type, uint16_t key) {
|
|
ffd_announce_t *announce;
|
|
for (announce = announce_list; announce; announce = announce->next) {
|
|
if (ffd_are_node_ids_equal(&announce->node, node)
|
|
&& announce->type == type
|
|
&& announce->key == key)
|
|
return announce;
|
|
}
|
|
|
|
/* not found */
|
|
announce = ffd_announce_new();
|
|
announce->node = *node;
|
|
announce->type = type;
|
|
announce->key = key;
|
|
|
|
return announce;
|
|
}
|
|
|
|
static ffd_nexthop_t* find_nexthop(const ffd_announce_t *announce, ffd_neigh_t *neigh) {
|
|
ffd_nexthop_t *nexthop;
|
|
for (nexthop = announce->nexthop_list; nexthop; nexthop = nexthop->next) {
|
|
if (nexthop->neigh == neigh)
|
|
return nexthop;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ffd_nexthop_t* new_nexthop(ffd_announce_t *announce, ffd_neigh_t *neigh) {
|
|
ffd_nexthop_t *nexthop = calloc(1, sizeof(ffd_nexthop_t));
|
|
nexthop->neigh = neigh;
|
|
|
|
nexthop->next = announce->nexthop_list;
|
|
announce->nexthop_list = nexthop;
|
|
|
|
return nexthop;
|
|
}
|
|
|
|
static void handle_tlv_update(const ffd_tlv_update_t *tlv_update, size_t len, handle_tlv_arg_t *arg) {
|
|
if (len < sizeof(ffd_tlv_update_t)) {
|
|
fprintf(stderr, "warn: received short update TLV.\n");
|
|
return;
|
|
}
|
|
|
|
if (ffd_is_node_id_unspec(&arg->node_id)) {
|
|
fprintf(stderr, "warn: received update TLV without node id TLV.\n");
|
|
return;
|
|
}
|
|
|
|
if (ffd_are_node_ids_equal(&arg->node_id, &self)) {
|
|
fprintf(stderr, "debug: update source is myself.\n");
|
|
return;
|
|
}
|
|
|
|
fprintf(stderr, "debug: update received from %04x%04x.\n", ntohl(*(uint32_t*)arg->node_id.id), ntohl(*(uint32_t*)(arg->node_id.id+4)));
|
|
|
|
ffd_announce_t *announce = get_announce(&arg->node_id, ntohs(tlv_update->type), ntohs(tlv_update->key));
|
|
ffd_metric_seqno_t ms = { ntohs(tlv_update->metric), ntohs(tlv_update->seqno) };
|
|
bool feasible = ffd_is_feasible(announce, ms);
|
|
|
|
ffd_neigh_t *neigh = get_tlv_neigh(arg);
|
|
ffd_nexthop_t *nexthop = find_nexthop(announce, neigh);
|
|
|
|
if (!nexthop) {
|
|
if (feasible && tlv_update->metric != 0xffff /* no need to ntohs */)
|
|
nexthop = new_nexthop(announce, neigh);
|
|
}
|
|
else {
|
|
if (!feasible && nexthop == announce->selected)
|
|
nexthop = NULL;
|
|
}
|
|
|
|
if ((tlv_update->flags & FFD_UPDATE_WITH_DATA) && !announce->data) {
|
|
if (len > sizeof(ffd_tlv_update_t)) {
|
|
announce->len = len - sizeof(ffd_tlv_update_t);
|
|
announce->data = malloc(announce->len);
|
|
memcpy(announce->data, tlv_update->data, announce->len);
|
|
}
|
|
else {
|
|
announce->len = 0xff;
|
|
|
|
/* request data */
|
|
if (nexthop)
|
|
ffd_send_announce_request(arg->iface, neigh, announce->node, announce->type, announce->key, true);
|
|
}
|
|
}
|
|
|
|
if (!nexthop)
|
|
return;
|
|
|
|
fprintf(stderr, "debug: the update was accepted.\n");
|
|
|
|
ffd_announce_update(announce, nexthop, ms, ntohs(tlv_update->interval));
|
|
}
|
|
|
|
static void handle_tlv_announce_req(const ffd_tlv_announce_req_t *tlv_req, size_t len, handle_tlv_arg_t *arg) {
|
|
if (len < sizeof(ffd_tlv_announce_req_t)) {
|
|
fprintf(stderr, "warn: received short announce request TLV.\n");
|
|
return;
|
|
}
|
|
|
|
ffd_announce_t *announce = NULL;
|
|
|
|
if (!ffd_is_node_id_unspec(&tlv_req->node)) {
|
|
announce = NULL;
|
|
}
|
|
|
|
ffd_send_update(arg->iface, get_tlv_neigh(arg), announce, tlv_req->flags & FFD_UPDATE_WITH_DATA);
|
|
}
|
|
|
|
static void handle_tlv(ffd_tlv_type_t type, const void *data, size_t len, void *arg) {
|
|
switch (type) {
|
|
case TLV_ACK_REQ:
|
|
handle_tlv_ack_req(data, len, arg);
|
|
return;
|
|
|
|
case TLV_ACK:
|
|
/* we don't send ack reqs */
|
|
return;
|
|
|
|
case TLV_HELLO:
|
|
handle_tlv_hello(data, len, arg);
|
|
return;
|
|
|
|
case TLV_IHU:
|
|
handle_tlv_ihu(data, len, arg);
|
|
return;
|
|
|
|
case TLV_NODE_ID:
|
|
handle_tlv_node_id(data, len, arg);
|
|
return;
|
|
|
|
case TLV_UPDATE:
|
|
handle_tlv_update(data, len, arg);
|
|
return;
|
|
|
|
case TLV_ANNOUNCE_REQ:
|
|
handle_tlv_announce_req(data, len, arg);
|
|
return;
|
|
|
|
default:
|
|
fprintf(stderr, "debug: received unknown TLV %u on %s.\n", type, ((handle_tlv_arg_t*)arg)->iface->name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void receive_packet(void) {
|
|
ffd_packet_t *packet = alloca(sizeof(ffd_packet_t)+FFD_PACKET_MAX);
|
|
struct sockaddr_ll from;
|
|
socklen_t fromlen = sizeof(from);
|
|
|
|
ssize_t readlen = recvfrom(sockfd, packet, sizeof(ffd_packet_t)+FFD_PACKET_MAX, 0, (struct sockaddr*)&from, &fromlen);
|
|
|
|
if (readlen < 0) {
|
|
fprintf(stderr, "error: recvfrom: %m\n");
|
|
return;
|
|
}
|
|
|
|
if (from.sll_halen != ETH_ALEN) {
|
|
fprintf(stderr, "error: receive_packet: strange address length\n");
|
|
return;
|
|
}
|
|
|
|
if (readlen < sizeof(ffd_packet_t) || readlen < sizeof(ffd_packet_t)+ntohs(packet->len)) {
|
|
fprintf(stderr, "debug: received short packet.\n");
|
|
return;
|
|
}
|
|
|
|
handle_tlv_arg_t arg = { .iface = get_iface(from.sll_ifindex), .addr = (const eth_addr_t*)&from.sll_addr };
|
|
|
|
if (!arg.iface) {
|
|
fprintf(stderr, "debug: received packet on unknown interface.\n");
|
|
return;
|
|
}
|
|
|
|
if (!ffd_tlv_parse(packet, handle_tlv, &arg)) {
|
|
fprintf(stderr, "debug: received invalid packet.\n");
|
|
}
|
|
}
|
|
|
|
static void send_updates(void) {
|
|
ffd_iface_t *iface;
|
|
for (iface = iface_list; iface; iface = iface->next) {
|
|
ffd_send_update(iface, NULL, NULL, false);
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
if (!check_config())
|
|
return 1;
|
|
|
|
if (!init_self())
|
|
return 1;
|
|
|
|
if (!init_socket())
|
|
return 1;
|
|
|
|
update_time();
|
|
|
|
struct timespec next_hello = now;
|
|
struct timespec next_update = now;
|
|
|
|
while (true) {
|
|
update_netifs();
|
|
|
|
int hello_timeout = timespec_diff(&next_hello, &now);
|
|
int update_timeout = timespec_diff(&next_update, &now);
|
|
|
|
if (hello_timeout <= 0) {
|
|
ffd_send_hellos();
|
|
|
|
add_interval(&next_hello, FFD_HELLO_INTERVAL);
|
|
continue;
|
|
}
|
|
|
|
if (update_timeout <= 0) {
|
|
fprintf(stderr, "Sending periodic update.\n");
|
|
send_updates();
|
|
|
|
add_interval(&next_update, FFD_UPDATE_INTERVAL);
|
|
continue;
|
|
}
|
|
|
|
struct pollfd fds[1];
|
|
|
|
fds[0].fd = sockfd;
|
|
fds[0].events = POLLIN;
|
|
|
|
poll(fds, 1, min(hello_timeout, update_timeout));
|
|
|
|
update_time();
|
|
|
|
if (fds[0].revents & POLLIN)
|
|
receive_packet();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|