392 lines
10 KiB
C
392 lines
10 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 "packet.h"
|
|
#include "queue.h"
|
|
#include "tlv.h"
|
|
#include "tlv_types.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <netpacket/packet.h>
|
|
|
|
#include <sys/socket.h>
|
|
#include <sys/uio.h>
|
|
|
|
|
|
static bool send_eth(const eth_addr_t *addr, unsigned ifindex, const void *buf, size_t len) {
|
|
static const uint8_t zeros[46] = {0};
|
|
|
|
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, addr->d, ETH_ALEN);
|
|
|
|
struct iovec vec[2] = {
|
|
{ .iov_base = (void*)buf, .iov_len = len },
|
|
{ .iov_base = (void*)zeros, .iov_len = sizeof(zeros) - len },
|
|
};
|
|
|
|
struct msghdr msg;
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.msg_name = &sa;
|
|
msg.msg_namelen = sizeof(sa);
|
|
msg.msg_iov = vec;
|
|
msg.msg_iovlen = (len < 46) ? 2 : 1;
|
|
|
|
|
|
while (sendmsg(sockfd, &msg, 0) < 0) {
|
|
if (errno != EINTR)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static inline bool send_neigh(const ffd_neigh_t *neigh, const ffd_packet_t *packet) {
|
|
if (!neigh->iface)
|
|
return false;
|
|
|
|
if (!send_eth(&neigh->addr, neigh->iface->ifindex, packet, sizeof(ffd_packet_t)+ntohs(packet->len))) {
|
|
fprintf(stderr, "send_eth: %m\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline bool send_ifindex(unsigned ifindex, const ffd_packet_t *packet) {
|
|
return send_eth(&ffd_addr, ifindex, packet, sizeof(ffd_packet_t)+ntohs(packet->len));
|
|
}
|
|
|
|
static inline bool send_iface(const ffd_iface_t *iface, const ffd_packet_t *packet) {
|
|
if (!send_ifindex(iface->ifindex, packet)) {
|
|
fprintf(stderr, "send_eth: %m\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline void send_broadcast(const ffd_packet_t *packet) {
|
|
ffd_iface_t *iface;
|
|
for (iface = iface_list; iface; iface = iface->next)
|
|
send_iface(iface, packet);
|
|
}
|
|
|
|
static inline void send_any(const ffd_iface_t *iface, const ffd_neigh_t *neigh, const ffd_packet_t *packet) {
|
|
if (neigh)
|
|
send_neigh(neigh, packet);
|
|
else if(iface)
|
|
send_iface(iface, packet);
|
|
else
|
|
send_broadcast(packet);
|
|
}
|
|
|
|
|
|
typedef struct _retransmit_arg {
|
|
unsigned iface;
|
|
ffd_neigh_t *neigh;
|
|
ffd_packet_t *packet;
|
|
|
|
unsigned ref;
|
|
} retransmit_arg;
|
|
|
|
static void retransmit_cb(const struct timespec *timeout, void *argp) {
|
|
retransmit_arg *arg = argp;
|
|
|
|
fprintf(stderr, "debug: doing retransmit\n");
|
|
|
|
if (arg->neigh)
|
|
send_neigh(arg->neigh, arg->packet);
|
|
else if(arg->iface)
|
|
send_ifindex(arg->iface, arg->packet);
|
|
else
|
|
send_broadcast(arg->packet);
|
|
|
|
if (!--arg->ref) {
|
|
if (arg->neigh)
|
|
ffd_neigh_unref(arg->neigh);
|
|
|
|
free(arg->packet);
|
|
free(arg);
|
|
}
|
|
}
|
|
|
|
static void send_retransmit(ffd_iface_t *iface, ffd_neigh_t *neigh, ffd_packet_t *packet) {
|
|
if (neigh)
|
|
ffd_neigh_ref(neigh);
|
|
|
|
retransmit_arg *arg = malloc(sizeof(retransmit_arg));
|
|
arg->iface = iface ? iface->ifindex : 0;
|
|
arg->neigh = neigh;
|
|
arg->packet = packet;
|
|
arg->ref = 5;
|
|
|
|
ffd_queue_put_delayed(&retransmits, retransmit_cb, &now, 100, arg);
|
|
ffd_queue_put_delayed(&retransmits, retransmit_cb, &now, 75, arg);
|
|
ffd_queue_put_delayed(&retransmits, retransmit_cb, &now, 50, arg);
|
|
ffd_queue_put_delayed(&retransmits, retransmit_cb, &now, 25, arg);
|
|
ffd_queue_put_delayed(&retransmits, retransmit_cb, &now, 0, arg);
|
|
}
|
|
|
|
|
|
void ffd_send_ack(ffd_neigh_t *neigh, uint16_t nonce) {
|
|
ffd_packet_t *packet = alloca(sizeof(ffd_packet_t)+FFD_PACKET_MAX);
|
|
|
|
packet->version_magic = htons(FFD_VERSION_MAGIC);
|
|
packet->len = 0;
|
|
|
|
ffd_tlv_ack_t *ack = ffd_tlv_add(packet, FFD_PACKET_MAX, TLV_ACK, sizeof(ffd_tlv_ack_t));
|
|
if (!ack)
|
|
return;
|
|
|
|
ack->nonce = htons(nonce);
|
|
|
|
send_neigh(neigh, packet);
|
|
}
|
|
|
|
static void add_ihus(ffd_packet_t *packet, size_t max_len, const ffd_iface_t *iface) {
|
|
const ffd_neigh_t *neigh;
|
|
|
|
for (neigh = iface->neigh_list; neigh; neigh = neigh->next) {
|
|
ffd_tlv_ihu_t *ihu = ffd_tlv_add(packet, FFD_PACKET_MAX, TLV_IHU, sizeof(ffd_tlv_ihu_t)+sizeof(eth_addr_t));
|
|
if (!ihu)
|
|
return;
|
|
|
|
ihu->ae = ADDR_ENC_ETH;
|
|
ihu->reserved = 0;
|
|
ihu->rxcost = htons(ffd_neigh_get_rxcost(neigh));
|
|
ihu->interval = htons(FFD_IHU_INTERVAL);
|
|
memcpy(ihu->address, &neigh->addr, sizeof(eth_addr_t));
|
|
}
|
|
}
|
|
|
|
void ffd_send_hellos(void) {
|
|
ffd_packet_t *packet = alloca(sizeof(ffd_packet_t)+FFD_PACKET_MAX);
|
|
|
|
packet->version_magic = htons(FFD_VERSION_MAGIC);
|
|
packet->len = 0;
|
|
|
|
ffd_tlv_hello_t *hello = ffd_tlv_add(packet, FFD_PACKET_MAX, TLV_HELLO, sizeof(ffd_tlv_hello_t));
|
|
if (!hello)
|
|
return;
|
|
|
|
hello->reserved = 0;
|
|
hello->interval = htons(FFD_HELLO_INTERVAL);
|
|
|
|
uint16_t len = packet->len;
|
|
|
|
ffd_iface_t *iface;
|
|
for (iface = iface_list; iface; iface = iface->next) {
|
|
hello->seqno = htons(iface->seqno++);
|
|
|
|
packet->len = len;
|
|
|
|
add_ihus(packet, FFD_PACKET_MAX, iface);
|
|
|
|
send_iface(iface, packet);
|
|
}
|
|
}
|
|
|
|
static bool add_node_id(ffd_packet_t *packet, size_t max_len, ffd_node_id_t node_id) {
|
|
ffd_tlv_node_id_t *tlv = ffd_tlv_add(packet, FFD_PACKET_MAX, TLV_NODE_ID, sizeof(ffd_tlv_node_id_t));
|
|
if (!tlv)
|
|
return false;
|
|
|
|
tlv->id = node_id;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool add_update(ffd_packet_t *packet, size_t max_len, ffd_node_id_t *node_id, ffd_announce_t *announce, bool with_data, bool targetted) {
|
|
if (announce->len && !announce->data) {
|
|
/* incomplete announce, handle like non-existant announce */
|
|
return true;
|
|
}
|
|
|
|
uint16_t len = packet->len;
|
|
|
|
if (!node_id || !ffd_are_node_ids_equal(node_id, &announce->node)) {
|
|
if (!add_node_id(packet, max_len, announce->node))
|
|
return false;
|
|
|
|
if (node_id)
|
|
*node_id = announce->node;
|
|
}
|
|
|
|
ffd_tlv_update_t *update = ffd_tlv_add(packet, FFD_PACKET_MAX, TLV_UPDATE, sizeof(ffd_tlv_update_t)+announce->len);
|
|
if (!update) {
|
|
packet->len = len;
|
|
return false;
|
|
}
|
|
|
|
update->flags = 0;
|
|
update->reserved = 0;
|
|
update->interval = htons(FFD_UPDATE_INTERVAL);
|
|
update->seqno = htons(announce->metric.seqno);
|
|
update->metric = htons(announce->metric.metric);
|
|
update->type = htons(announce->type);
|
|
update->key = htons(announce->key);
|
|
|
|
if (announce->len && announce->metric.metric != 0xff) {
|
|
update->flags |= FFD_UPDATE_WITH_DATA;
|
|
|
|
if (with_data) {
|
|
memcpy(update->data, announce->data, announce->len);
|
|
}
|
|
}
|
|
|
|
if (ffd_is_metric_better(announce->metric, announce->feasibility_distance))
|
|
announce->feasibility_distance = announce->metric;
|
|
|
|
if (!targetted)
|
|
announce->last_metric = announce->metric.metric;
|
|
|
|
return true;
|
|
}
|
|
|
|
void ffd_send_update(ffd_iface_t *iface, ffd_neigh_t *neigh, ffd_announce_t *announce, bool urgent, bool with_data) {
|
|
ffd_packet_t *packet;
|
|
if (urgent)
|
|
packet = malloc(sizeof(ffd_packet_t)+FFD_PACKET_MAX);
|
|
else
|
|
packet = alloca(sizeof(ffd_packet_t)+FFD_PACKET_MAX);
|
|
|
|
packet->version_magic = htons(FFD_VERSION_MAGIC);
|
|
packet->len = 0;
|
|
|
|
if (announce) {
|
|
add_update(packet, FFD_PACKET_MAX, NULL, announce, with_data, iface || neigh);
|
|
}
|
|
else {
|
|
ffd_node_id_t node_id = FFD_NODE_ID_UNSPEC;
|
|
|
|
ffd_announce_t *a;
|
|
for (a = announce_list; a; a = a->next) {
|
|
if (!add_update(packet, FFD_PACKET_MAX, &node_id, a, with_data, iface || neigh)) {
|
|
if (urgent) {
|
|
send_retransmit(iface, neigh, packet);
|
|
packet = malloc(sizeof(ffd_packet_t)+FFD_PACKET_MAX);
|
|
}
|
|
else {
|
|
send_any(iface, neigh, packet);
|
|
}
|
|
|
|
node_id = FFD_NODE_ID_UNSPEC;
|
|
packet->len = 0;
|
|
|
|
if (!add_update(packet, FFD_PACKET_MAX, &node_id, a, with_data, iface || neigh)) {
|
|
fprintf(stderr, "error: add_update failed\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (urgent) {
|
|
if (packet->len)
|
|
send_retransmit(iface, neigh, packet);
|
|
else
|
|
free(packet);
|
|
}
|
|
else {
|
|
if (packet->len)
|
|
send_any(iface, neigh, packet);
|
|
}
|
|
}
|
|
|
|
void ffd_send_retract(ffd_neigh_t *neigh, ffd_node_id_t node, uint16_t type, uint16_t key) {
|
|
ffd_packet_t *packet = alloca(sizeof(ffd_packet_t)+FFD_PACKET_MAX);
|
|
|
|
packet->version_magic = htons(FFD_VERSION_MAGIC);
|
|
packet->len = 0;
|
|
|
|
if (!add_node_id(packet, FFD_PACKET_MAX, node))
|
|
return;
|
|
|
|
ffd_tlv_update_t *req = ffd_tlv_add(packet, FFD_PACKET_MAX, TLV_UPDATE, sizeof(ffd_tlv_update_t));
|
|
if (!req)
|
|
return;
|
|
|
|
req->flags = 0;
|
|
req->reserved = 0;
|
|
req->interval = htons(FFD_UPDATE_INTERVAL);
|
|
req->seqno = 0;
|
|
req->metric = 0xffff; /* no need to htons */
|
|
req->type = htons(type);
|
|
req->key = htons(key);
|
|
|
|
send_neigh(neigh, packet);
|
|
}
|
|
|
|
void ffd_send_announce_request(ffd_iface_t *iface, ffd_neigh_t *neigh, ffd_node_id_t node, uint16_t type, uint16_t key, bool with_data) {
|
|
ffd_packet_t *packet = alloca(sizeof(ffd_packet_t)+FFD_PACKET_MAX);
|
|
|
|
packet->version_magic = htons(FFD_VERSION_MAGIC);
|
|
packet->len = 0;
|
|
|
|
ffd_tlv_announce_req_t *req = ffd_tlv_add(packet, FFD_PACKET_MAX, TLV_ANNOUNCE_REQ, sizeof(ffd_tlv_announce_req_t));
|
|
if (!req)
|
|
return;
|
|
|
|
req->node = node;
|
|
req->flags = 0;
|
|
req->reserved = 0;
|
|
req->type = htons(type);
|
|
req->key = htons(key);
|
|
|
|
if (with_data)
|
|
req->flags |= FFD_UPDATE_WITH_DATA;
|
|
|
|
send_any(iface, neigh, packet);
|
|
}
|
|
|
|
void ffd_send_seqno_request(ffd_neigh_t *neigh, ffd_announce_t *announce, uint16_t seqno) {
|
|
ffd_packet_t *packet = alloca(sizeof(ffd_packet_t)+FFD_PACKET_MAX);
|
|
|
|
packet->version_magic = htons(FFD_VERSION_MAGIC);
|
|
packet->len = 0;
|
|
|
|
ffd_tlv_seqno_req_t *req = ffd_tlv_add(packet, FFD_PACKET_MAX, TLV_SEQNO_REQ, sizeof(ffd_tlv_seqno_req_t));
|
|
if (!req)
|
|
return;
|
|
|
|
req->seqno = htons(seqno);
|
|
req->node = announce->node;
|
|
req->type = htons(announce->type);
|
|
req->key = htons(announce->key);
|
|
|
|
send_any(NULL, neigh, packet);
|
|
}
|