Commit e7a0b106 authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Initial import.

parents
CDEBUGFLAGS = -Os -g -Wall
DEFINES = $(PLATFORM_DEFINES)
CFLAGS = -I/usr/include/libnl3 $(CDEBUGFLAGS) $(DEFINES) $(EXTRA_DEFINES)
LDLIBS = -lnl-genl-3 -lnl-route-3 -lnl-3
SRCS = sroamd.c client.c lease.c ra.c dhcpv4.c interface.c netlink.c \
flood.c prefix.c util.c
OBJS = sroamd.o client.o lease.o ra.o dhcpv4.o interface.o netlink.o \
flood.o prefix.o util.o
sroamd: $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) -o sroamd $(OBJS) $(LDLIBS)
.PHONY: clean
clean:
-rm -f sroamd *.o *~ core TAGS gmon.out
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <arpa/inet.h>
#include <netlink/errno.h>
#include "interface.h"
#include "client.h"
#include "netlink.h"
struct client *clients;
int numclients = 0, maxclients = 0;
struct client *
find_client(const unsigned char *mac)
{
for(int i = 0; i < numclients; i++) {
if(memcmp(clients[i].mac, mac, 6) == 0)
return &clients[i];
}
return NULL;
}
struct client *
add_client(struct interface *interface, const unsigned char *mac)
{
struct client *client;
client = find_client(mac);
if(client != NULL)
return client;
if(maxclients <= numclients) {
int n = maxclients == 0 ? 8 : 2 * maxclients;
struct client *newclients =
realloc(clients, n * sizeof(struct client));
if(newclients != NULL) {
clients = newclients;
maxclients = n;
}
}
if(maxclients <= numclients)
return NULL;
memset(&clients[numclients], 0, sizeof(struct client));
clients[numclients].interface = interface;
memcpy(clients[numclients].mac, mac, 6);
numclients++;
return &clients[numclients - 1];
}
int
flush_client(const unsigned char *mac)
{
int i;
struct client *client;
client = find_client(mac);
if(client == NULL)
return 0;
i = client - clients;
assert(i >= 0 && i < numclients);
update_client_route(client, NULL, 0);
update_client_route(client, NULL, 1);
if(i < numclients - 1)
memmove(clients + i, clients + i + 1, numclients - i - 1);
numclients--;
return 1;
}
static const char zeroes[8] = {0};
void
update_client_routes(const unsigned char *mac,
const unsigned char *addr, int ipv6)
{
for(int i = 0; i < numclients; i++) {
if(memcmp(clients[i].mac, mac, 6) == 0)
update_client_route(&clients[i], addr, ipv6);
}
}
void
client_cleanup()
{
for(int i = 0; i < numclients; i++) {
update_client_route(&clients[i], NULL, 0);
update_client_route(&clients[i], NULL, 1);
}
}
static const char zeroes[8];
int
update_client_route(struct client *client, const unsigned char *addr, int ipv6)
{
int rc;
if(ipv6) {
unsigned char buf[16];
if((addr == NULL && memcmp(client->ipv6, zeroes, 8) == 0) ||
(addr != NULL && memcmp(client->ipv6, addr, 8) == 0))
return 0;
if(memcmp(client->ipv6, zeroes, 8) != 0) {
memcpy(buf, client->ipv6, 8);
memset(buf + 8, 0, 8);
netlink_route(client->interface->ifindex, 0, 1, buf, 64);
memset(client->ipv6, 0, 8);
}
if(addr != NULL) {
memcpy(buf, addr, 8);
memset(buf + 8, 0, 8);
rc = netlink_route(client->interface->ifindex, 1, 1, buf, 64);
if(rc < 0 && rc != -NLE_EXIST) {
nl_perror(rc, "netlink_route");
return rc;
}
memcpy(client->ipv6, addr, 8);
} else {
memcpy(client->ipv6, zeroes, 8);
}
} else {
if((addr == NULL && memcmp(client->ipv4, zeroes, 4) == 0) ||
(addr != NULL && memcmp(client->ipv4, addr, 4) == 0))
return 0;
if(memcmp(client->ipv4, zeroes, 4) != 0) {
netlink_route(client->interface->ifindex, 0, 0, client->ipv4, 32);
memset(client->ipv4, 0, 4);
}
if(addr != NULL) {
rc = netlink_route(client->interface->ifindex, 1, 0, addr, 32);
if(rc < 0 && rc != -NLE_EXIST) {
nl_perror(rc, "netlink_route");
return rc;
}
memcpy(client->ipv4, addr, 4);
} else {
memcpy(client->ipv4, zeroes, 4);
}
}
return 1;
}
struct client {
struct interface *interface;
unsigned char mac[6];
unsigned char ipv4[4];
unsigned char ipv6[8];
};
extern struct client *clients;
extern int numclients, maxclients;
struct client *find_client(const unsigned char *mac);
struct client *add_client(struct interface *interface, const unsigned char *mac);
int flush_client(const unsigned char *mac);
int update_client_route(struct client *client,
const unsigned char *addr, int ipv6);
void update_client_routes(const unsigned char *mac,
const unsigned char *addr, int ipv6);
void client_cleanup(void);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <linux/if_packet.h>
#include "interface.h"
#include "client.h"
#include "lease.h"
#include "flood.h"
#include "util.h"
#include "dhcpv4.h"
#define RENEW_TIME 1600
#define REBIND_TIME 1700
#define LEASE_TIME 1800
int dhcpv4_socket = -1;
static const unsigned char cookie[4] = {99, 130, 83, 99};
static const unsigned char zeroes[6] = {0, 0, 0, 0, 0, 0};
unsigned char dnsv4[16][4];
int numdnsv4 = 0;
static int
setup_dhcpv4_socket(const unsigned char *addr)
{
int s, rc, one = 1;
struct sockaddr_in sin;
if(dhcpv4_socket >= 0) {
close(dhcpv4_socket);
dhcpv4_socket = -1;
}
s = socket(PF_INET, SOCK_DGRAM, 0);
if(s < 0)
return -1;
rc = setsockopt(s, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one));
if(rc < 0)
goto fail;
rc = setsockopt(s, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
if(rc < 0)
goto fail;
rc = fcntl(s, F_GETFD, 0);
if(rc < 0)
goto fail;
rc = fcntl(s, F_SETFD, rc | FD_CLOEXEC);
if(rc < 0)
goto fail;
rc = fcntl(s, F_GETFL, 0);
if(rc < 0)
goto fail;
rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK));
if(rc < 0)
goto fail;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
if(addr != NULL)
memcpy(&sin.sin_addr, addr, 4);
sin.sin_port = htons(67);
rc = bind(s, (struct sockaddr*)&sin, sizeof(sin));
if(rc < 0)
goto fail;
dhcpv4_socket = s;
return s;
fail:
return -1;
}
int
dhcpv4_setup(const unsigned char *addr)
{
return setup_dhcpv4_socket(addr);
}
void
dhcpv4_cleanup()
{
if(dhcpv4_socket >= 0) {
close(dhcpv4_socket);
dhcpv4_socket = -1;
}
}
struct
dhcpv4_request
{
int type;
int broadcast;
unsigned char xid[4];
unsigned char ciaddr[4];
unsigned char yiaddr[4];
unsigned char siaddr[4];
unsigned char giaddr[4];
unsigned char chaddr[16];
unsigned char ip[4];
unsigned char sid[4];
unsigned char *cid;
int cidlen;
unsigned char *uc;
int uclen;
};
static int
dhcpv4_parse(unsigned char *buf, int buflen,
struct dhcpv4_request *ret)
{
int i = 0;
unsigned char
xid[4] = {0},
ciaddr[4] = {0}, yiaddr[4] = {0}, siaddr[4] = {0}, giaddr[4] = {0},
chaddr[16] = {0}, ip[4] = {0}, sid[4] = {0};
unsigned char *cid = NULL, *uc = NULL;
int dhcp_type = -1, broadcast = 0, cidlen = 0, uclen = 0;
if(buflen < 236)
goto fail;
if(buf[0] != 1 || buf[1] != 1 || buf[2] != 6)
goto fail;
i += 4;
memcpy(xid, buf + i, 4);
i += 4;
/* secs */
i += 2;
/* flags */
broadcast = (buf[i] & 0x80) != 0;
i += 2;
/* ciaddr */
memcpy(ciaddr, buf + i, 4);
i += 4;
/* yiaddr */
memcpy(yiaddr, buf + i, 4);
i += 4;
/* siaddr */
memcpy(siaddr, buf + i, 4);
i += 4;
/* giaddr */
memcpy(giaddr, buf + i, 4);
i += 4;
/* chaddr */
memcpy(chaddr, buf + i, 16);
i += 16;
/* sname */
i += 64;
/* file */
i += 128;
if(buflen - i < 4)
goto fail;
if(memcmp(buf + i, cookie, 4) != 0)
goto fail;
i += 4;
while(i < buflen) {
unsigned const char *tlv = buf + i;
int type, bodylen;
if(buflen - i < 1) {
fprintf(stderr, "Received truncated DHCPv4 TLV.\n");
goto fail;
}
type = tlv[0];
if(type == 0) {
i++;
continue;
}
if(type == 255) {
i++;
goto done;
}
if(buflen - i < 2) {
fprintf(stderr, "Received truncated DHCPv4 TLV.\n");
goto fail;
}
bodylen = tlv[1];
if(buflen - i < 2 + bodylen) {
fprintf(stderr, "Received truncated DHCPv4 TLV.\n");
goto fail;
}
switch(type) {
case 50:
if(bodylen != 4)
goto fail;
memcpy(ip, tlv + 2, 4);
break;
case 53:
if(bodylen != 1)
goto fail;
dhcp_type = tlv[2];
break;
case 54:
if(bodylen != 4)
goto fail;
memcpy(sid, tlv + 2, 4);
break;
case 61:
if(cid != NULL)
goto fail;
cid = malloc(bodylen);
if(cid == NULL)
goto fail;
memcpy(cid, tlv + 2, bodylen);
cidlen = bodylen;
break;
case 77:
if(uc != NULL)
goto fail;
uc = malloc(bodylen);
if(uc == NULL)
goto fail;
memcpy(uc, tlv + 2, bodylen);
uclen = bodylen;
break;
}
i += 2 + bodylen;
}
/* Fall through */
fail:
fprintf(stderr, "Failed to parse DHCPv4 packet.\n");
free(cid);
free(uc);
return -1;
done:
ret->type = dhcp_type;
ret->broadcast = broadcast;
memcpy(ret->chaddr, chaddr, 16);
memcpy(ret->xid, xid, 4);
memcpy(ret->ciaddr, ciaddr, 4);
memcpy(ret->yiaddr, yiaddr, 4);
memcpy(ret->siaddr, siaddr, 4);
memcpy(ret->giaddr, giaddr, 4);
memcpy(ret->ip, ip, 4);
memcpy(ret->sid, sid, 4);
ret->cid = cid;
ret->cidlen = cidlen;
ret->uc = uc;
ret->uclen = uclen;
return 1;
}
#define CHECK(_n) if(buflen < i + (_n)) goto fail
#define BYTE(_v) buf[i] = (_v); i++
#define BYTES(_v, _len) memcpy(buf + i, (_v), (_len)); i += (_len)
#define ZEROS(_len) memset(buf + i, 0, (_len)); i += (_len)
#define SHORT(_v) DO_HTONS(buf + i, (_v)); i += 2
#define LONG(_v) DO_HTONL(buf + i, (_v)); i += 4
static int
dhcpv4_send(int s, struct sockaddr_in *to, int dontroute,
int type, const unsigned char *xid,
const unsigned char *chaddr, const unsigned char *myaddr,
const unsigned char *ip, struct interface *interface,
const unsigned char *netmask,
int lease_time)
{
int buflen = 1024;
unsigned char buf[buflen];
int i = 0;
int rc;
debugf("-> DHCPv4 (type %d) %s\n", type, interface->ifname);
MEM_UNDEFINED(buf, buflen);
CHECK(236);
BYTE(2);
BYTE(1);
BYTE(6);
BYTE(0);
BYTES(xid, 4);
SHORT(0);
SHORT(0);
ZEROS(4); /* ciaddr */
if(ip && lease_time >= 20) {
BYTES(ip, 4); /* yiaddr */
} else {
ZEROS(4);
}
BYTES(myaddr, 4); /* siaddr */
ZEROS(4); /* giaddr */
BYTES(chaddr, 16); /* chaddr */
ZEROS(64); /* sname */
ZEROS(128); /* file */
CHECK(4);
BYTES(cookie, 4);
CHECK(3);
BYTE(53); /* DHCP Message Type */
BYTE(1);
BYTE(type);
CHECK(6);
BYTE(54); /* Server Identifier */
BYTE(4);
BYTES(myaddr, 4);
if(lease_time >= 20) {
CHECK(6);
BYTE(51); /* IP Address Lease Time */
BYTE(4);
LONG(lease_time);
CHECK(6);
BYTE(58); /* T1 */
BYTE(4);
LONG(min(RENEW_TIME, lease_time - 10));
CHECK(6);
BYTE(59); /* T2 */
BYTE(4);
LONG(min(REBIND_TIME, lease_time - 15));
}
if(netmask) {
CHECK(6);
BYTE(1);
BYTE(4);
BYTES(netmask, 4);
}
CHECK(6);
BYTE(3);
BYTE(4);
BYTES(myaddr, 4);
if(numdnsv4 > 0) {
CHECK(2 + 4 * numdnsv4);
BYTE(6);
BYTE(4 * numdnsv4);
for(int j = 0; j < numdnsv4; j++) {
BYTES((char*)&dnsv4[j], 4);
}
}
CHECK(1);
BYTE(255);
sock_bindtodevice(dhcpv4_socket, interface);
rc = sendto(dhcpv4_socket, buf, i, dontroute ? MSG_DONTROUTE : 0,
(struct sockaddr*)to, sizeof(*to));
sock_bindtodevice(dhcpv4_socket, NULL);
return rc;
fail:
return -1;
}
int
dhcpv4_receive()
{
int rc, buflen, doit;
const char broadcast_addr[4] = {255, 255, 255, 255};
struct sockaddr_in from, to;
int dontroute = 1;
int bufsiz = 1500;
unsigned char buf[bufsiz];
unsigned char myaddr[4];
unsigned char netmask[4] = {255, 255, 255, 255};
struct dhcpv4_request req;
struct client *client;
struct interface *interface;
int ifindex = -1;
struct iovec iov[1];
struct msghdr msg;
int cmsglen = 100;
char cmsgbuf[cmsglen];
struct cmsghdr *cmsg = (struct cmsghdr*)cmsgbuf;
iov[0].iov_base = buf;
iov[0].iov_len = bufsiz;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &from;
msg.msg_namelen = sizeof(from);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg;
msg.msg_controllen = cmsglen;
rc = recvmsg(dhcpv4_socket, &msg, 0);
if(rc < 0)
return -1;
if(from.sin_family != AF_INET || msg.msg_namelen < sizeof(from))
return -1;
buflen = rc;
cmsg = CMSG_FIRSTHDR(&msg);
while(cmsg != NULL) {
if ((cmsg->cmsg_level == IPPROTO_IP) &&
(cmsg->cmsg_type == IP_PKTINFO)) {
struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
ifindex = info->ipi_ifindex;
break;
}
cmsg = CMSG_NXTHDR(&msg, cmsg);
}
if(ifindex < 0)
return -1;
interface = find_interface(ifindex);
if(interface == NULL)
return -1;
rc = interface_v4(interface, myaddr);
if(rc <= 0) {
return -1;
}
memset(&req, 0, sizeof(req));
rc = dhcpv4_parse(buf, buflen, &req);
if(rc < 0)
return -1;
if(memcmp(req.sid, zeroes, 4) != 0 && memcmp(req.sid, myaddr, 4) != 0)
return 0;
debugf("<- DHCPv4 (type %d) %s\n", req.type, interface->ifname);
memset(&to, 0, sizeof(to));
to.sin_family = AF_INET;
if(memcmp(req.giaddr, zeroes, 4) != 0) {
memcpy(&to.sin_addr, req.giaddr, 4);
dontroute = 0;
} else if(!req.broadcast && memcmp(req.ciaddr, zeroes, 4) != 0) {
memcpy(&to.sin_addr, req.ciaddr, 4);
} else {
memcpy(&to.sin_addr, broadcast_addr, 4);
}
to.sin_port = htons(68);
switch(req.type) {
case 1: /* DHCPDISCOVER */
case 3: { /* DHCPREQUEST */
struct datum *lease = NULL;
const unsigned char *addr;
unsigned char cip[4];
int have_cip;
int remain;
if(req.type == 1)
memcpy(cip, req.ip, 4);
else if(memcmp(req.ciaddr, zeroes, 4) != 0)
memcpy(cip, req.ciaddr, 4);
else
memcpy(cip, req.ip, 4);
have_cip = memcmp(cip, zeroes, 4) != 0;
if(req.type == 3 && !have_cip)
goto nak;
client = update_association(interface, req.chaddr, ASSOCIATION_TIME);
if(client == NULL) {
fprintf(stderr, "Failed to create client.\n");
goto nak;
}
lease = update_lease(req.chaddr, 0,
have_cip ? cip : NULL,
req.type == 1 ? 10 : LEASE_TIME,
&doit);
if(lease == NULL)
goto nak;
addr = lease_address(lease, 0);
if(addr == NULL)
goto nak;
if(req.type == 3 && memcmp(cip, addr, 4) != 0)
goto nak;
update_client_route(client, addr, 0);
remain = req.type == 1 ? LEASE_TIME : datum_remaining(lease);
if(remain < 60)
goto nak;
rc = dhcpv4_send(dhcpv4_socket, &to, dontroute,
req.type == 1 ? 2 : 5, req.xid, req.chaddr, myaddr,
addr, interface, netmask, remain);
if(rc < 0)
perror("dhcpv4_send");
break;
}
case 4: /* DHCPDECLINE */
fprintf(stderr, "Received DHCPDECLINE");
break;
case 7: /* DHCPRELEASE */
fprintf(stderr, "Received DHCPRELEASE");
break;
case 8: /* DHCPINFORM */
rc = dhcpv4_send(dhcpv4_socket, &to, dontroute,
5, req.xid, req.chaddr, myaddr,
NULL, interface, NULL, 0);
if(rc < 0)
perror("dhcpv4_send");
break;
}
goto done;
nak:
/* NAK is always sent to broadcast address, except in relay case. */
if(memcmp(req.giaddr, zeroes, 4) == 0)
memcpy(&to.sin_addr, broadcast_addr, 4);
if(req.type == 3)
dhcpv4_send(dhcpv4_socket, &to, dontroute, 6, req.xid, req.chaddr,
myaddr, req.ip, interface, NULL, 0);
done:
free(req.cid);
free(req.uc);
return 1;
}
int
send_gratuitious_arp(const unsigned char *myaddr, struct interface *interface,
const unsigned char *mac)
{
int buflen = 28;
unsigned char buf[buflen];
struct sockaddr_ll sll;
int rc, s, i = 0;
s = socket(PF_PACKET, SOCK_DGRAM, htons(ETHERTYPE_ARP));
if(s < 0)
return -1;
CHECK(28);
SHORT(ARPHRD_ETHER);
SHORT(ETHERTYPE_IP);
BYTE(6);
BYTE(4);
SHORT(2);
BYTES(interface->mac, 6);
BYTES(myaddr, 4);
BYTES(zeroes, 6);
BYTES(myaddr, 4);
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_protocol = htons(ETHERTYPE_ARP);
sll.sll_ifindex = interface->ifindex;
sll.sll_hatype = htons(ARPHRD_ETHER);
memcpy(&sll.sll_addr, mac, 6);
sll.sll_halen = 6;
debugf("-> ARP Reply.\n");
rc = sendto(s, buf, i, 0, (struct sockaddr*)&sll, sizeof(sll));
close(s);
return rc;
fail:
close(s);
return -1;
}
/*
Copyright (c) 2015 by Juliusz Chroboczek
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
extern unsigned char dnsv4[16][4];
extern int numdnsv4;
extern int dhcpv4_socket;
int dhcpv4_setup(const unsigned char *addr);
int dhcpv4_receive(void);
void dhcpv4_cleanup(void);
int send_gratuitious_arp(const unsigned char *myaddr,
struct interface *interface,
const unsigned char *mac);
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/socket.h>
#define __USE_GNU
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include "interface.h"
#include "lease.h"
#include "flood.h"
#include "util.h"
int flood_socket = -1;
int flood_port = 4444;
struct datum **data = NULL;
int numdata = 0, maxdata = 0;
struct timespec flood_time = {0, 0};
static void
schedule_flood()
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
ts_add_msec(&flood_time, &now, 1);
}
static void (*datum_callback)(struct datum *, int) = NULL;
static int buffer_update(struct neighbour *neigh,
const unsigned char *key, int keylen, int acked);
static int record_unacked(struct neighbour *neigh,
const unsigned char *key, int keylen);
static int flush_unacked(struct neighbour *neigh,
const unsigned char *key, int keylen);
static int
seqno_compare(unsigned short s1, unsigned short s2)
{
if(s1 == s2)
return 0;
else
return ((s2 - s1) & 0x8000) ? 1 : -1;
}
struct datum *
find_datum(const unsigned char *key, int keylen)
{
for(int i = 0; i < numdata; i++) {
if(data[i]->keylen == keylen &&
memcmp(data[i]->datum, key, keylen) == 0)
return data[i];
}
return NULL;
}
struct datum *
update_datum(const unsigned char *key, int keylen,
unsigned short seqno,
const unsigned char *val, int vallen,
int time, int *updated, int *conflict)
{
struct datum *datum;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
for(int i = 0; i < numdata; i++) {
if(data[i]->keylen == keylen &&
memcmp(data[i]->datum, key, keylen) == 0) {
int cmp = seqno_compare(data[i]->seqno, seqno);
if(cmp > 0) {
if(updated != NULL)
*updated = 0;
if(conflict != NULL)
*conflict = 0;
return data[i];
} else if(cmp == 0) {
if(data[i]->vallen == vallen &&
memcmp(datum_val(data[i]), val, vallen) == 0) {
int u = 0;
if(data[i]->time < now.tv_sec + time) {
data[i]->time = now.tv_sec + time;
u = 1;
}
if(updated != NULL)
*updated = u;
if(conflict != NULL)
*conflict = 0;
return data[i];
} else {
/* conflict */
if(data[i]->time > now.tv_sec + time) {
/* we win */
if(updated != NULL)
*updated = 0;
if(conflict != NULL)
*conflict = 1;
return data[i];
}
/* they win */
if(data[i]->vallen != vallen) {
datum =
realloc(data[i], sizeof(struct datum) + keylen + vallen);
if(datum == NULL)
return NULL;
datum->vallen = vallen;
data[i] = datum;
}
data[i]->time = now.tv_sec + time;
if(updated != NULL)
*updated = 1;
if(conflict != NULL)
*conflict = 1;
return data[i];
}
} else {
if(data[i]->vallen != vallen) {
datum =
realloc(data[i], sizeof(struct datum) + keylen + vallen);
if(datum == NULL)
return NULL;
datum->vallen = vallen;
data[i] = datum;
}
data[i]->seqno = seqno;
memcpy(data[i]->datum + keylen, val, vallen);
data[i]->time = now.tv_sec + time;
if(updated != NULL)
*updated = 1;
if(conflict != NULL)
*conflict = 0;
return data[i];
}
}
}
if(maxdata <= numdata) {
int n = maxdata == 0 ? 8 : 2 * maxdata;
struct datum **newdata =
realloc(data, n * sizeof(struct datum*));
if(newdata != NULL) {
data = newdata;
maxdata = n;
}
}
if(maxdata <= numdata)
return NULL;
datum = calloc(1, sizeof(struct datum) + keylen + vallen);
if(datum == NULL) {
if(updated != NULL)
*updated = 0;
if(conflict != NULL)
*conflict = 0;
return NULL;
}
datum->seqno = seqno;
datum->keylen = keylen;
memcpy(datum->datum, key, keylen);
datum->vallen = vallen;
memcpy(datum->datum + keylen, val, vallen);
datum->time = now.tv_sec + time;
data[numdata++] = datum;
if(updated != NULL)
*updated = 1;
if(conflict != NULL)
*conflict = 0;
return datum;
}
void
flush_datum(struct datum *datum)
{
for(int i = 0; i < numdata; i++) {
if(data[i] == datum) {
if(i < numdata - 1)
memmove(data + i, data + i + 1,
(numdata - i - 1) * sizeof(struct datum));
numdata--;
return;
}
}
abort();
}
time_t
datum_remaining(const struct datum *datum)
{
struct timespec now;
time_t t;
clock_gettime(CLOCK_MONOTONIC, &now);
t = datum->time - now.tv_sec;
if(t < 0)
return 0;
return t;
}
int
extend_datum(struct datum *datum, time_t extend)
{
if(extend >= 0) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if(datum->time < now.tv_sec)
datum->seqno = (datum->seqno + 1) & 0xFFFF;
if(datum->time < now.tv_sec + extend) {
datum->time = now.tv_sec + extend;
return 1;
}
}
return 0;
}
struct neighbour *neighbours = NULL;
int numneighbours = 0, maxneighbours = 0;
static int send_dump_request(struct neighbour *neigh);
static int send_dump_reply(struct neighbour *neigh);
int
flood_setup(void (*callback)(struct datum *, int))
{
struct sockaddr_in6 sin6;
int s, rc, saved_errno;
int zero = 0, one = 1;
s = socket(PF_INET6, SOCK_DGRAM, 0);
if(s < 0)
return -1;
rc = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero));
if(rc < 0)
goto fail;
rc = fcntl(s, F_GETFL, 0);
if(rc < 0)
goto fail;
rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK));
if(rc < 0)
goto fail;
rc = fcntl(s, F_GETFD, 0);
if(rc < 0)
goto fail;
rc = fcntl(s, F_SETFD, rc | FD_CLOEXEC);
if(rc < 0)
goto fail;
rc = setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one));
if(rc < 0)
goto fail;
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_port = htons(flood_port);
rc = bind(s, (struct sockaddr*)&sin6, sizeof(sin6));
if(rc < 0)
goto fail;
flood_socket = s;
datum_callback = callback;
periodic_flood();
return 1;
fail:
saved_errno = errno;
close(s);
errno = saved_errno;
return -1;
}
void
flood_cleanup()
{
close(flood_socket);
flood_socket = -1;
}
static void
commit_neighbour(struct neighbour *neigh, int update, int permanent)
{
if(update) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
neigh->time = now.tv_sec;
}
if(permanent)
neigh->permanent = 1;
}
static int
match(const struct sockaddr_in6 *a, const struct sockaddr_in6 *b)
{
return a->sin6_port == b->sin6_port &&
memcmp(&a->sin6_addr, &b->sin6_addr, 16) == 0;
}
struct neighbour *
find_neighbour(struct sockaddr_in6 *sin6, int create, int update, int permanent)
{
for(int i = 0; i < numneighbours; i++) {
if(match(sin6, &neighbours[i].addr)) {
commit_neighbour(&neighbours[i], update, permanent);
return &neighbours[i];
}
}
if(!create)
return NULL;
if(maxneighbours <= numneighbours) {
int n = maxneighbours == 0 ? 8 : 2 * maxneighbours;
struct neighbour *newneighbours =
realloc(neighbours, n * sizeof(struct neighbour));
if(newneighbours != NULL) {
neighbours = newneighbours;
maxneighbours = n;
}
}
if(maxneighbours <= numneighbours)
return NULL;
memset(&neighbours[numneighbours], 0, sizeof(struct neighbour));
memcpy(&neighbours[numneighbours].addr, sin6, sizeof(struct sockaddr_in6));
neighbours[numneighbours].dump_done = 0;
commit_neighbour(&neighbours[numneighbours], update, permanent);
numneighbours++;
return &neighbours[numneighbours-1];
}
void
flush_neighbour(struct neighbour *neigh)
{
int i = neigh - neighbours;
assert(i >= 0 && i < numneighbours);
free(neighbours[i].unacked);
neighbours[i].unacked = NULL;
free(neighbours[i].pktinfo);
neighbours[i].pktinfo = NULL;
if(i < numneighbours - 1)
memmove(neighbours + i, neighbours + i + 1,
(numneighbours - i - 1) * sizeof(struct neighbour));
numneighbours--;
}
static void
parse_packet(struct sockaddr_in6 *from, struct in6_pktinfo *info,
const unsigned char *packet, int packetlen)
{
struct neighbour *neigh;
unsigned int bodylen;
int i;
if(packetlen < 4)
return;
if(packet[0] != 44 || packet[1] != 0)
return;
DO_NTOHS(bodylen, packet + 2);
if(bodylen + 4 > packetlen) {
fprintf(stderr, "Received truncated packet.\n");
return;
}
neigh = find_neighbour(from, 1, 1, 0);
if(neigh == NULL)
return;
if(info != NULL) {
if(neigh->pktinfo != NULL) {
if(memcmp(neigh->pktinfo, info, sizeof(struct in6_pktinfo)) != 0) {
free(neigh->pktinfo);
neigh->pktinfo = NULL;
}
}
if(neigh->pktinfo == NULL)
neigh->pktinfo = malloc(sizeof(struct in6_pktinfo));
if(neigh->pktinfo != NULL)
memcpy(neigh->pktinfo, info, sizeof(struct in6_pktinfo));
}
i = 0;
while(i < bodylen) {
const unsigned char *tlv = packet + 4 + i;
int len;
if(tlv[0] == 0) {
i++;
continue;
}
if(i + 1 > bodylen)
return;
len = tlv[1];
if(i + len + 2 > bodylen)
return;
switch(tlv[0]) {
case 1:
debugf("<- PAD1\n");
break;
case 2: {
struct datum *datum;
unsigned char keylen;
unsigned short seqno;
unsigned int time;
int ack, doit, conflict;
if(len < 2) {
debugf("Truncated DATUM.\n");
goto skip;
}
ack = !!(tlv[2] & 0x80);
DO_NTOHS(seqno, tlv + 3);
DO_NTOHL(time, tlv + 5);
keylen = tlv[9];
if(len < keylen + 8) {
debugf("Truncated DATUM.\n");
goto skip;
}
debugf("<- DATUM %d (%d) %ld%s\n",
keylen <= 0 ? -1 : (int)tlv[10], keylen, (long)time,
ack ? " (ack)" : "");
datum = find_datum(tlv+10, keylen);
if(datum != NULL && seqno >= datum->seqno) {
flush_unacked(neigh, tlv+10, keylen);
}
datum = update_datum(tlv + 10, keylen, seqno,
tlv + 10 + keylen, len - keylen - 8,
time, &doit, &conflict);
if(doit && datum_callback != NULL)
datum_callback(datum, conflict);
if(doit || ack)
flood(datum, neigh, ack, doit);
}
break;
case 3:
debugf("<- DUMP\n");
send_dump_reply(neigh);
for(int i = 0; i < numneighbours; i++) {
for(int j = 0; j < numdata; j++)
record_unacked(&neighbours[i],
datum_key(data[j]), data[j]->keylen);
}
schedule_flood();
break;
case 4:
debugf("<- DUMP-ACK\n");
neigh->dump_done = 1;
break;
default:
debugf("Unknown TLV %d\n", tlv[0]);
}
skip:
i += 2 + len;
}
flush_updates(neigh, 1);
}
int
flood_listen(void)
{
struct sockaddr_in6 from;
struct in6_pktinfo *info;
unsigned char buf[4096];
struct iovec iov[1];
struct msghdr msg;
int cmsglen = 100;
char cmsgbuf[cmsglen];
struct cmsghdr *cmsg = (struct cmsghdr*)cmsgbuf;
int rc;
iov[0].iov_base = buf;
iov[0].iov_len = 4096;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &from;
msg.msg_namelen = sizeof(from);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg;
msg.msg_controllen = cmsglen;
rc = recvmsg(flood_socket, &msg, 0);
if(rc < 0)
return rc;
info = NULL;
cmsg = CMSG_FIRSTHDR(&msg);
while(cmsg != NULL) {
if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
(cmsg->cmsg_type == IPV6_PKTINFO)) {
info = (struct in6_pktinfo*)CMSG_DATA(cmsg);
break;
}
cmsg = CMSG_NXTHDR(&msg, cmsg);
}
if(info == NULL) {
errno = EINVAL;
return -1;
}
parse_packet(&from, info, buf, rc);
return 1;
}
static int
send_neighbour(struct neighbour *neigh, unsigned char *buf, int buflen)
{
struct timespec now;
struct msghdr msg;
struct iovec iov[1];
struct cmsghdr *cmsg;
union {
struct cmsghdr hdr;
char buf[CMSG_SPACE(sizeof(struct in6_pktinfo))];
} u;
clock_gettime(CLOCK_MONOTONIC, &now);
neigh->send_time = now.tv_sec;
iov[0].iov_base = buf;
iov[0].iov_len = buflen;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (struct sockaddr*)&neigh->addr;
msg.msg_namelen = sizeof(neigh->addr);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
if(neigh->pktinfo != NULL) {
memset(u.buf, 0, sizeof(u.buf));
msg.msg_control = u.buf;
msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
memcpy(CMSG_DATA(cmsg), neigh->pktinfo, sizeof(struct in6_pktinfo));
}
return sendmsg(flood_socket, &msg, 0);
}
static int
send_dump_request(struct neighbour *neigh)
{
unsigned char buf[6] = {44, 0, 0, 2, 3, 0};
debugf("-> DUMP\n");
return send_neighbour(neigh, buf, 6);
}
static int
send_dump_reply(struct neighbour *neigh)
{
unsigned char buf[6] = {44, 0, 0, 2, 4, 0};
debugf("-> DUMP-ACK\n");
return send_neighbour(neigh, buf, 6);
}
int
flush_updates(struct neighbour *neigh, int all)
{
unsigned char buf[1024] = {44, 0, 0, 0};
struct timespec now;
time_t time;
int i, n, rc = 0;
if(neigh->numbuffered == 0)
return 0;
clock_gettime(CLOCK_MONOTONIC, &now);
i = 0;
for(n = 0; n < neigh->numbuffered; n++) {
struct datum *datum;
if(i == 0) {
buf[i] = 44; i++;
buf[i] = 0; i++;
buf[i] = 0; i++;
buf[i] = 0; i++;
}
datum = find_datum(neigh->buffered[n].key, neigh->buffered[n].keylen);
free(neigh->buffered[n].key);
neigh->buffered[n].key = NULL;
if(datum == NULL)
continue;
time = datum->time - now.tv_sec;
if(time <= 0)
continue;
if(time > 0xFFFFFFFF)
time = 0xFFFFFFFF;
buf[i++] = 2;
buf[i++] = 1 + 2 + 4 + 1 + datum->keylen + datum->vallen;
buf[i++] = neigh->buffered[n].acked ? 0x80 : 0;
DO_HTONS(buf + i, datum->seqno); i += 2;
DO_HTONL(buf + i, time); i += 4;
buf[i++] = datum->keylen;
memcpy(buf + i, datum->datum, datum->keylen + datum->vallen);
i += datum->keylen + datum->vallen;
debugf("-> DATUM %d (%d) %ld%s\n",
datum->keylen <= 0 ? -1 : datum->datum[0], datum->keylen,
(long)time, neigh->buffered[n].acked ? " (ack)" : "");
if(i >= 1024 - 32) {
if(!all)
break;
DO_HTONS(buf + 2, i - 4);
rc = send_neighbour(neigh, buf, i);
}
}
if(i > 0) {
DO_HTONS(buf + 2, i - 4);
rc = send_neighbour(neigh, buf, i);
}
if(n < neigh->numbuffered) {
memmove(neigh->buffered, neigh->buffered + n,
(neigh->numbuffered - n) * sizeof(struct buffered));
neigh->numbuffered -= n;
} else {
neigh->numbuffered = 0;
}
return rc;
}
static int
buffer_update(struct neighbour *neigh, const unsigned char *key, int keylen,
int acked)
{
unsigned char *newkey;
if(neigh->buffered == NULL) {
neigh->buffered = malloc(MAXBUFFERED * sizeof(struct buffered));
if(neigh->buffered == NULL)
return -1;
}
if(neigh->numbuffered >= MAXBUFFERED)
flush_updates(neigh, 0);
assert(neigh->numbuffered < MAXBUFFERED);
newkey = malloc(keylen);
if(newkey == NULL)
return -1;
memcpy(newkey, key, keylen);
neigh->buffered[neigh->numbuffered].key = newkey;
neigh->buffered[neigh->numbuffered].keylen = keylen;
neigh->buffered[neigh->numbuffered].acked = acked;
neigh->numbuffered++;
return 1;
}
static int
send_keepalive(struct neighbour *neigh)
{
unsigned char buf[4] = {44, 0, 0, 0};
debugf("-> Keepalive\n");
return send_neighbour(neigh, buf, 4);
}
static int
neighbour_alive(struct neighbour *neigh, time_t now)
{
if(neigh->permanent)
return 1;
return neigh->time > now - 240;
}
void
flood(struct datum *datum, struct neighbour *neigh, int ack, int doit)
{
struct timespec now;
if(ack && neigh != NULL)
buffer_update(neigh, datum_key(datum), datum->keylen, 0);
clock_gettime(CLOCK_MONOTONIC, &now);
if(doit) {
for(int i = 0; i < numneighbours; i++) {
if(neighbour_alive(&neighbours[i], now.tv_sec) &&
&neighbours[i] != neigh)
record_unacked(&neighbours[i], datum_key(datum), datum->keylen);
}
}
schedule_flood();
}
static struct unacked *
find_unacked(struct neighbour *neigh, const unsigned char *key, int keylen)
{
for(int i = 0; i < neigh->numunacked; i++) {
if(neigh->unacked[i].keylen == keylen &&
memcmp(neigh->unacked[i].key, key, keylen) == 0)
return &neigh->unacked[i];
}
return NULL;
}
static int
record_unacked(struct neighbour *neigh, const unsigned char *key, int keylen)
{
struct unacked *unacked;
struct timespec now;
unsigned char *newkey;
clock_gettime(CLOCK_MONOTONIC, &now);
unacked = find_unacked(neigh, key, keylen);
if(unacked != NULL) {
unacked->count = 0;
unacked->time = now.tv_sec;
return 0;
}
if(neigh->numunacked >= neigh->maxunacked) {
struct unacked *n;
int count = neigh->maxunacked * 3 / 2;
if(count < 8)
count = 8;
n = realloc(neigh->unacked, count * sizeof(struct unacked));
if(n == NULL)
return -1;
neigh->unacked = n;
neigh->maxunacked = count;
}
newkey = malloc(keylen);
if(newkey == NULL)
return -1;
memcpy(newkey, key, keylen);
neigh->unacked[neigh->numunacked].count = 0;
neigh->unacked[neigh->numunacked].key = newkey;
neigh->unacked[neigh->numunacked].keylen = keylen;
neigh->unacked[neigh->numunacked].time = now.tv_sec;
neigh->numunacked++;
schedule_flood();
return 1;
}
static int
flush_unacked(struct neighbour *neigh, const unsigned char *key, int keylen)
{
int i;
struct unacked *unacked;
unacked = find_unacked(neigh, key, keylen);
if(unacked == NULL)
return 0;
i = unacked - neigh->unacked;
assert(i >= 0 && i < neigh->numunacked);
free(neigh->unacked[i].key);
neigh->unacked[i].key = NULL;
if(i < neigh->numunacked - 1)
memmove(neigh->unacked + i, neigh->unacked + i + 1,
(neigh->numunacked - i - 1) * sizeof(struct unacked));
neigh->numunacked--;
return 1;
}
static struct timespec expire_neighbours_time = {0, 0};
static void
expire_neighbours()
{
struct timespec now;
int i;
clock_gettime(CLOCK_MONOTONIC, &now);
i = 0;
while(i < numneighbours) {
if(neighbour_alive(&neighbours[i], now.tv_sec)) {
if(neighbours[i].send_time < now.tv_sec - 60)
send_keepalive(&neighbours[i]);
i++;
} else {
flush_neighbour(&neighbours[i]);
}
}
}
void
periodic_flood()
{
struct timespec now;
int work = 0;
clock_gettime(CLOCK_MONOTONIC, &now);
if(ts_compare(&expire_neighbours_time, &now) <= 0) {
expire_neighbours();
expire_neighbours_time = now;
expire_neighbours_time.tv_sec += 10;
}
for(int i = 0; i < numneighbours; i++) {
if(!neighbours[i].dump_done && neighbours[i].dump_request_count < 4) {
work = 1;
send_dump_request(&neighbours[i]);
neighbours[i].dump_request_count++;
}
if(neighbours[i].numunacked > 0)
work = 1;
for(int j = 0; j < neighbours[i].numunacked; j++) {
struct unacked *unacked = &neighbours[i].unacked[j];
struct timespec soon = {unacked->time, 0};
if(unacked->count > 0)
soon.tv_sec += 1 << (unacked->count - 1);
if(ts_compare(&soon, &now) <= 0) {
buffer_update(&neighbours[i], unacked->key, unacked->keylen, 1);
unacked->count++;
}
}
flush_updates(&neighbours[i], 1);
}
if(work) {
flood_time = now;
flood_time.tv_sec += 1;
} else {
flood_time.tv_sec = 0;
flood_time.tv_nsec = 0;
}
}
struct datum {
unsigned short seqno;
unsigned char keylen;
unsigned char vallen;
time_t time;
unsigned char datum[];
};
static inline const unsigned char *
datum_key(const struct datum *datum)
{
return datum->datum;
}
static inline const unsigned char *
datum_val(const struct datum *datum)
{
return datum->datum + datum->keylen;
}
extern int flood_port;
extern int flood_socket;
extern struct datum **data;
extern int numdata, maxdata;
extern struct timespec flood_time;
struct unacked {
int count;
unsigned char *key;
int keylen;
time_t time;
};
struct buffered {
unsigned char *key;
int keylen;
int acked;
};
#define MAXBUFFERED 100
struct neighbour {
struct sockaddr_in6 addr;
struct in6_pktinfo *pktinfo;
int permanent;
time_t time;
time_t send_time;
struct unacked *unacked;
int numunacked, maxunacked;
struct buffered *buffered;
int numbuffered;
int dump_request_count;
int dump_done;
};
extern struct neighbour *neighbours;
extern int numneighbours, maxneighbours;
struct datum *find_datum(const unsigned char *key, int keylen);
struct datum *update_datum(const unsigned char *key, int keylen,
unsigned short seqno,
const unsigned char *val, int vallen,
int time, int *updated, int *conflict);
void flush_datum(struct datum *datum);
time_t datum_remaining(const struct datum *datum);
int extend_datum(struct datum *datum, time_t extend);
int flood_setup(void (*callback)(struct datum *, int));
void flood_cleanup(void);
int flood_listen(void);
struct neighbour *
find_neighbour(struct sockaddr_in6 *sin6, int create, int update, int permanent);
void flood(struct datum *datum, struct neighbour *neigh, int ack, int doit);
void periodic_flood(void);
int flush_updates(struct neighbour *neigh, int all);
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <ifaddrs.h>
#include "interface.h"
struct interface *interfaces = NULL;
int numinterfaces = 0;
struct interface *
find_interface(int ifindex)
{
for(int i = 0; i < numinterfaces; i++) {
if(interfaces[i].ifindex == ifindex)
return &interfaces[i];
}
return NULL;
}
int
interface_v4(struct interface *interface, unsigned char *v4_return)
{
struct ifreq req;
int s, rc;
s = socket(PF_INET, SOCK_DGRAM, 0);
if(s < 0)
return -1;
memset(&req, 0, sizeof(req));
strncpy(req.ifr_name, interface->ifname, sizeof(req.ifr_name));
req.ifr_addr.sa_family = AF_INET;
rc = ioctl(s, SIOCGIFADDR, &req);
if(rc < 0) {
close(s);
return -1;
}
close(s);
memcpy(v4_return, &((struct sockaddr_in*)&req.ifr_addr)->sin_addr, 4);
return 1;
}
int
interface_v6(struct interface *interface, unsigned char *v6_return)
{
struct ifaddrs *ifaddr;
int rc, found = 0;
rc = getifaddrs(&ifaddr);
if(rc < 0)
return rc;
for(struct ifaddrs *ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
struct sockaddr_in6 *sin6;
if(ifa->ifa_addr == NULL ||
strcmp(ifa->ifa_name, interface->ifname) != 0 ||
ifa->ifa_addr->sa_family != AF_INET6)
continue;
sin6 = (struct sockaddr_in6*)ifa->ifa_addr;
if(!IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
continue;
memcpy(v6_return, &sin6->sin6_addr, 16);
found = 1;
break;
}
freeifaddrs(ifaddr);
if(!found) {
errno = ESRCH;
return -1;
}
return 1;
}
int
sock_bindtodevice(int s, struct interface *interface)
{
struct ifreq ifr;
if(interface != NULL) {
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, interface->ifname, IFNAMSIZ);
return setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr));
} else {
return setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, NULL, 0);
}
}
struct interface {
char *ifname;
int ifindex;
unsigned char mac[6];
};
extern struct interface *interfaces;
extern int numinterfaces, maxinterfaces;
struct interface *find_interface(int ifindex);
int interface_v4(struct interface *interface, unsigned char *v4_return);
int interface_v6(struct interface *interface, unsigned char *v6_return);
int sock_bindtodevice(int s, struct interface *interface);
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
#include "prefix.h"
#include "client.h"
#include "flood.h"
#include "lease.h"
unsigned char myid[8];
#define LEASE_FUDGE 10
unsigned char v4prefix[4], v6prefix[16];
int v4plen = -1, v6plen = -1;
const unsigned char zeroes[8] = {0};
struct datum *
find_lease(const unsigned char *mac, int ipv6)
{
struct datum *datum = NULL;
/* In general, multiple leases can point to the same MAC. Pick the
one that's lexicographically smallest. Perhaps we should be
routing all of the addresses instead? */
for(int i = 0; i < numdata; i++) {
if(data[i]->keylen < 1)
continue;
if(datum_key(data[i])[0] !=
(ipv6 ? DATUM_IPv6_LEASE : DATUM_IPv4_LEASE) ||
data[i]->keylen != (ipv6 ? 9 : 5))
continue;
if(data[i]->vallen != 6 || memcmp(datum_val(data[i]), mac, 6) != 0)
continue;
if(datum == NULL || memcmp(datum_val(data[i]), datum_val(datum), 6) < 0)
datum = data[i];
}
return datum;
}
struct datum *
find_lease_by_ip(const unsigned char *a, int ipv6)
{
if(ipv6) {
for(int i = 0; i < numdata; i++) {
if(data[i]->keylen == 9 &&
datum_key(data[i])[0] == DATUM_IPv6_LEASE &&
memcmp(datum_key(data[i]) + 1, a, 8) == 0)
return data[i];
}
} else {
for(int i = 0; i < numdata; i++) {
if(data[i]->keylen == 5 &&
datum_key(data[i])[0] == DATUM_IPv4_LEASE &&
memcmp(datum_key(data[i]) + 1, a, 4) == 0)
return data[i];
}
}
return NULL;
}
static struct datum *
make_lease(const unsigned char *suggested, const unsigned char *mac, int ipv6)
{
struct datum *datum = NULL;
unsigned char key[9];
struct timespec now;
int ok;
unsigned char addr[8];
clock_gettime(CLOCK_MONOTONIC, &now);
if(suggested)
datum = find_lease_by_ip(suggested, ipv6);
if(datum != NULL && datum->time + LEASE_FUDGE < now.tv_sec &&
datum->vallen == 6) {
memcpy((char*)datum_val(datum), mac, 6);
return datum;
} else {
datum = NULL;
}
ok = 0;
for(int i = 0; i < 32; i++) {
int rc;
if(!ipv6)
rc = random_prefix(v4prefix, v4plen, addr, 32);
else
rc = random_prefix(v6prefix, v6plen, addr, 64);
if(rc >= 0 && find_lease(addr, ipv6) == NULL) {
ok = 1;
break;
}
}
if(!ok)
return NULL;
key[0] = ipv6 ? DATUM_IPv6_LEASE : DATUM_IPv4_LEASE;
memcpy(key + 1, addr, ipv6 ? 8 : 4);
return update_datum(key, ipv6 ? 9 : 5, 0, mac, 6, 0, NULL, NULL);
}
struct datum *
update_lease(const unsigned char *mac, int ipv6,
const unsigned char *suggested,
int time, int *doit_return)
{
struct datum *datum;
int doit;
datum = find_lease(mac, ipv6);
if(datum != NULL) {
doit = extend_datum(datum, time);
if(doit_return)
*doit_return = doit;
flood(datum, NULL, 0, 1);
return datum;
}
if ((!ipv6 && (v4plen < 0 || v4plen > 32)) ||
(ipv6 && (v6plen < 0 || v6plen > 64))) {
return NULL;
}
datum = make_lease(suggested, mac, ipv6);
if(datum == NULL)
return NULL;
doit = extend_datum(datum, time);
if(doit_return)
*doit_return = doit;
update_client_routes(mac, lease_address(datum, ipv6), ipv6);
flood(datum, NULL, 0, 1);
return datum;
}
const unsigned char *
lease_address(const struct datum *datum, int ipv6)
{
if(datum_key(datum)[0] != (ipv6 ? DATUM_IPv6_LEASE : DATUM_IPv4_LEASE))
return NULL;
if(datum->keylen != (ipv6 ? 9 : 5))
return NULL;
return datum_key(datum) + 1;
}
void
update_lease_routes(const struct datum *datum)
{
int ipv6;
if(datum->keylen < 1)
return;
if(datum_key(datum)[0] == DATUM_IPv6_LEASE)
ipv6 = 1;
else if(datum_key(datum)[0] == DATUM_IPv4_LEASE)
ipv6 = 0;
else
return;
if(datum->keylen != (ipv6 ? 9 : 5) || datum->vallen != 6)
return;
for(int i = 0; i < numclients; i++) {
int match;
if(ipv6)
match = memcmp(clients[i].ipv6, datum_key(datum) + 1, 8) == 0;
else
match = memcmp(clients[i].ipv4, datum_key(datum) + 1, 4) == 0;
if(match && memcmp(clients[i].mac, datum_val(datum), 6) != 0) {
update_client_route(&clients[i], NULL, ipv6);
}
}
}
struct client *
update_association(struct interface *interface, const unsigned char *mac,
int time)
{
unsigned char key[1 + 6];
struct datum *datum;
int seqno = 0;
struct client *client;
client = add_client(interface, mac);
if(client == NULL)
return NULL;
key[0] = DATUM_ASSOCIATED;
memcpy(key + 1, mac, 6);
datum = find_datum(key, 7);
if(datum != NULL) {
if(datum->vallen == 8 &&
memcmp(datum_val(datum), myid, 8) == 0) {
extend_datum(datum, time);
flood(datum, NULL, 0, 1);
return client;
} else {
seqno = datum->seqno + 1;
}
}
datum = update_datum(key, 7, seqno, myid, 8, time, NULL, NULL);
flood(datum, NULL, 0, 1);
return client;
}
void
flush_association(const unsigned char *mac, int time)
{
unsigned char key[1 + 6];
struct datum *datum;
int seqno;
flush_client(mac);
key[0] = DATUM_ASSOCIATED;
memcpy(key + 1, mac, 6);
datum = find_datum(key, 7);
if(datum == NULL || datum->vallen != 8 ||
memcmp(datum_val(datum), myid, 8) != 0)
return;
seqno = datum->seqno + 1;
datum = update_datum(key, 7, seqno, NULL, 0, time, NULL, NULL);
flood(datum, NULL, 0, 1);
}
#define DATUM_IPv4_LEASE 0
#define DATUM_IPv6_LEASE 1
#define DATUM_ASSOCIATED 2
#define ASSOCIATION_TIME 5400
extern unsigned char myid[8];
extern unsigned char v4prefix[4], v6prefix[16];
extern int v4plen, v6plen;
struct datum *find_lease(const unsigned char *mac, int ipv6);
struct datum *find_lease_by_ip(const unsigned char *a, int ipv6);
struct datum *update_lease(const unsigned char *mac, int ipv6,
const unsigned char *suggested,
int time, int *doit_return);
const unsigned char *lease_address(const struct datum *datum, int ipv6);
void update_lease_routes(const struct datum *datum);
struct client *update_association(struct interface *interface,
const unsigned char *mac, int time);
void flush_association(const unsigned char *mac, int time);
void datum_notify(struct datum *datum);
#include <stdlib.h>
#include <errno.h>
#include <net/if.h>
#include <linux/nl80211.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/family.h>
#include <netlink/genl/ctrl.h>
#include <netlink/msg.h>
#include <netlink/attr.h>
#include <netlink/route/addr.h>
#include <netlink/route/route.h>
#include <netlink/errno.h>
#include "netlink.h"
struct nl_sock *nl_sock;
int nl80211_id;
struct nl_cb *nl_cb;
static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
void *arg)
{
int *ret = arg;
*ret = err->error;
return NL_STOP;
}
static int ack_handler(struct nl_msg *msg, void *arg)
{
int *ret = arg;
*ret = 0;
return NL_STOP;
}
struct handler_args {
const char *group;
int id;
};
static int family_handler(struct nl_msg *msg, void *arg)
{
struct handler_args *grp = arg;
struct nlattr *tb[CTRL_ATTR_MAX + 1];
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *mcgrp;
int rem_mcgrp;
nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[CTRL_ATTR_MCAST_GROUPS])
return NL_SKIP;
nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) {
struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX,
nla_data(mcgrp), nla_len(mcgrp), NULL);
if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] ||
!tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
continue;
if (strncmp(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]),
grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])))
continue;
grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
break;
}
return NL_SKIP;
}
int nl_get_multicast_id(struct nl_sock *sock,
const char *family, const char *group)
{
struct nl_msg *msg;
struct nl_cb *cb;
int ret, ctrlid;
struct handler_args grp = {
.group = group,
.id = -ENOENT,
};
msg = nlmsg_alloc();
if (!msg)
return -ENOMEM;
cb = nl_cb_alloc(NL_CB_DEFAULT);
if (!cb) {
ret = -ENOMEM;
goto out_fail_cb;
}
ctrlid = genl_ctrl_resolve(sock, "nlctrl");
genlmsg_put(msg, 0, 0, ctrlid, 0,
0, CTRL_CMD_GETFAMILY, 0);
ret = -ENOBUFS;
NLA_PUT_STRING(msg, CTRL_ATTR_FAMILY_NAME, family);
ret = nl_send_auto(sock, msg);
if (ret < 0)
goto out;
ret = 1;
nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret);
nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret);
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, family_handler, &grp);
while (ret > 0)
nl_recvmsgs(sock, cb);
if (ret == 0)
ret = grp.id;
nla_put_failure:
out:
nl_cb_put(cb);
out_fail_cb:
nlmsg_free(msg);
return ret;
}
static int
ok_handler(struct nl_msg *msg, void *arg)
{
return NL_OK;
}
static int
event_handler(struct nl_msg *msg, void *arg)
{
netlink_callback cb = arg;
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *tb[NL80211_ATTR_MAX + 1];
char ifname[100];
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if(tb[NL80211_ATTR_IFINDEX] == NULL)
return NL_OK;
if_indextoname(nla_get_u32(tb[NL80211_ATTR_IFINDEX]), ifname);
if(gnlh->cmd != NL80211_CMD_NEW_STATION &&
gnlh->cmd != NL80211_CMD_DEL_STATION) {
return NL_OK;
}
if(cb != NULL)
cb(gnlh->cmd == NL80211_CMD_NEW_STATION,
nla_get_u32(tb[NL80211_ATTR_IFINDEX]),
nla_data(tb[NL80211_ATTR_MAC]));
return NL_OK;
}
int
netlink_init(netlink_callback cb)
{
int rc, mcid;
nl_sock = nl_socket_alloc();
if(nl_sock == NULL)
return -1;
rc = genl_connect(nl_sock);
if(rc < 0)
return -1;
nl_socket_set_buffer_size(nl_sock, 8192, 8192);
nl80211_id = genl_ctrl_resolve(nl_sock, "nl80211");
if(nl80211_id < 0)
return -1;
mcid = nl_get_multicast_id(nl_sock, "nl80211", "mlme");
if(mcid < 0)
return -1;
rc = nl_socket_add_membership(nl_sock, mcid);
if(rc < 0)
return -1;
nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
if(nl_cb == NULL)
return -1;
nl_cb_set(nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, ok_handler, NULL);
nl_cb_set(nl_cb, NL_CB_VALID, NL_CB_CUSTOM, event_handler, cb);
rc = nl_socket_set_nonblocking(nl_sock);
if(rc < 0)
return -1;
return 1;
}
int
netlink_dump(int ifindex)
{
struct nl_msg *msg;
int rc;
msg = nlmsg_alloc();
if(msg == NULL)
return -1;
genlmsg_put(msg, 0, 0, nl80211_id, 0, NLM_F_DUMP,
NL80211_CMD_GET_STATION, 0);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex);
rc = nl_send_auto(nl_sock, msg);
nlmsg_free(msg);
if(rc < 0)
return rc;
return 1;
nla_put_failure:
return -1;
}
int
netlink_listen()
{
return nl_recvmsgs(nl_sock, nl_cb);
}
int
netlink_socket()
{
return nl_socket_get_fd(nl_sock);
}
int
netlink_disassociate(int ifindex, const unsigned char *mac,
const unsigned char *mymac)
{
struct nl_msg *msg;
int rc;
#if 0
unsigned char buf[26];
msg = nlmsg_alloc();
if(msg == NULL)
return -1;
genlmsg_put(msg, 0, 0, nl80211_id, 0, 0, NL80211_CMD_FRAME, 0);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex);
memset(buf, 0, 26);
buf[0] = 10 << 4; /* FC */
memcpy(buf + 4, mac, 6); /* da */
memcpy(buf + 10, mymac, 6); /* sa */
memcpy(buf + 16, mymac, 6); /* bssid */
buf[24] = 1; /* reason */
buf[25] = 0;
NLA_PUT(msg, NL80211_ATTR_FRAME, 26, buf);
NLA_PUT_FLAG(msg, NL80211_ATTR_OFFCHANNEL_TX_OK);
rc = nl_send_auto(nl_sock, msg);
nlmsg_free(msg);
if(rc < 0)
return rc;
#endif
msg = nlmsg_alloc();
if(msg == NULL)
return -1;
genlmsg_put(msg, 0, 0, nl80211_id, 0, 0, NL80211_CMD_DEL_STATION, 0);
NLA_PUT(msg, NL80211_ATTR_MAC, 6, mac);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex);
NLA_PUT_U8(msg, NL80211_ATTR_MGMT_SUBTYPE, 0x0a);
NLA_PUT_U16(msg, NL80211_ATTR_REASON_CODE, 1);
rc = nl_send_auto(nl_sock, msg);
nlmsg_free(msg);
if(rc < 0)
return rc;
return 1;
nla_put_failure:
return -1;
}
struct nl_sock *rtnl_sock = NULL;
int
netlink_route(int ifindex, int add, int ipv6, const unsigned char *dst, int dlen)
{
struct rtnl_route *route = NULL;
struct nl_addr *addr = NULL;
struct rtnl_nexthop *nh = NULL;
unsigned char dest[16];
int rc;
if(rtnl_sock == NULL) {
rtnl_sock = nl_socket_alloc();
if(rtnl_sock == NULL)
return -NLE_NOMEM;
rc = nl_connect(rtnl_sock, NETLINK_ROUTE);
if(rc < 0) {
nl_socket_free(rtnl_sock);
rtnl_sock = NULL;
return rc;
}
}
memcpy(dest, dst, ipv6 ? 16 : 4);
addr = nl_addr_alloc(ipv6 ? 16 : 4);
if(addr == NULL) {
rc = NLE_NOMEM;
goto fail;
}
nl_addr_set_family(addr, ipv6 ? AF_INET6 : AF_INET);
nl_addr_set_binary_addr(addr, dest, ipv6 ? 16 : 4);
nl_addr_set_prefixlen(addr, dlen);
route = rtnl_route_alloc();
if(route == NULL) {
rc = NLE_NOMEM;
goto fail;
}
rc = rtnl_route_set_family(route, ipv6 ? AF_INET6 : AF_INET);
if(rc < 0)
goto fail;
rc = rtnl_route_set_dst(route, addr);
if(rc < 0)
goto fail;
nh = rtnl_route_nh_alloc();
if(nh == NULL) {
rc = NLE_NOMEM;
goto fail;
}
rtnl_route_nh_set_ifindex(nh, ifindex);
#if 0
rtnl_route_nh_set_gateway(nh, addr);
rtnl_route_nh_set_flags(nh, RTNH_F_ONLINK);
#endif
rtnl_route_add_nexthop(route, nh);
nh = NULL;
rtnl_route_set_protocol(route, 44);
if(add)
rc = rtnl_route_add(rtnl_sock, route, 0);
else
rc = rtnl_route_delete(rtnl_sock, route, 0);
if(rc < 0) {
goto fail;
}
rtnl_route_put(route);
nl_addr_put(addr);
return 1;
fail:
if(route != NULL)
rtnl_route_put(route);
if(nh != NULL)
rtnl_route_nh_free(nh);
if(addr != NULL)
nl_addr_put(addr);
return rc;
}
typedef void (*netlink_callback)(int add, int ifindex, const unsigned char *mac);
int netlink_init(netlink_callback cb);
int netlink_dump(int ifindex);
int netlink_listen(void);
int netlink_socket(void);
int netlink_disassociate(int ifindex, const unsigned char *mac,
const unsigned char *mymac);
int netlink_route(int ifindex, int add, int ipv6,
const unsigned char *dst, int dlen);
#include <stdlib.h>
#include <string.h>
#include "prefix.h"
int
in_prefix(const unsigned char *a, const unsigned char *p, int plen)
{
if(memcmp(a, p, plen / 8) != 0)
return 0;
if(plen % 8 == 0) {
return 1;
} else {
int i = plen / 8 + 1;
unsigned char mask = (0xFF << (plen % 8)) & 0xFF;
return (a[i] & mask) == (p[i] & mask);
}
}
static void
random_bits(unsigned char *buf, int first, int len)
{
int i;
if(first % 8 != 0) {
unsigned char mask = (0xFF >> (first % 8)) ^ 0xFF;
buf[first / 8] &= mask;
buf[first / 8] |= random() & (0xFF ^ mask);
}
for(i = (first + 7) / 8; i < (first + len) / 8; i++)
buf[i] = random() % 0xFF;
if((first + len) % 8 != 0) {
unsigned char mask = 0xFF >> ((first + len) % 8);
buf[(first + len) / 8] &= mask;
buf[(first + len) / 8] |= random() & (0xFF ^ mask);
}
}
int
random_prefix(const unsigned char *p, int plen, unsigned char *q, int qlen)
{
if(plen < 0 || plen > qlen)
return -1;
memset(q, 0, (qlen + 7) / 8);
memcpy(q, p, (plen + 7) / 8);
random_bits(q, plen, qlen - plen);
return 1;
}
int in_prefix(const unsigned char *a, const unsigned char *p, int plen);
int
random_prefix(const unsigned char *p, int plen, unsigned char *q, int qlen);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/icmp6.h>
#include "interface.h"
#include "client.h"
#include "lease.h"
#include "flood.h"
#include "util.h"
#include "ra.h"
unsigned char dnsv6[16][16];
int numdnsv6 = 0;
int ra_socket = -1;
int
setup_ra_socket()
{
int s, i, rc, one = 1, ff = 255;
struct icmp6_filter filter;
if(ra_socket >= 0) {
close(ra_socket);
ra_socket = -1;
}
s = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if(s < 0)
return -1;
rc = setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ff, sizeof(ff));
if(rc < 0)
goto fail;
rc = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ff, sizeof(ff));
if(rc < 0)
goto fail;
rc = setsockopt(s, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one));
if(rc < 0)
goto fail;
for(i = 0; i < numinterfaces; i++) {
struct ipv6_mreq mreq;
const unsigned char all_routers[16] =
{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02};
if(interfaces[i].ifindex <= 0)
continue;
memset(&mreq, 0, sizeof(mreq));
memcpy(&mreq.ipv6mr_multiaddr, &all_routers, 16);
mreq.ipv6mr_interface = interfaces[i].ifindex;
rc = setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP,
(char*)&mreq, sizeof(mreq));
if(rc < 0)
goto fail;
}
ICMP6_FILTER_SETBLOCKALL(&filter);
ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
rc = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter));
if(rc < 0)
goto fail;
rc = fcntl(s, F_GETFD, 0);
if(rc < 0)
goto fail;
rc = fcntl(s, F_SETFD, rc | FD_CLOEXEC);
if(rc < 0)
goto fail;
rc = fcntl(s, F_GETFL, 0);
if(rc < 0)
goto fail;
rc = fcntl(s, F_SETFL, (rc | O_NONBLOCK));
if(rc < 0)
goto fail;
ra_socket = s;
return s;
fail:
return -1;
}
#define CHECK(_n) if(buflen < i + (_n)) goto sendit
#define BYTE(_v) buf[i] = (_v); i++
#define BYTES(_v, _len) memcpy(buf + i, (_v), (_len)); i += (_len)
#define SHORT(_v) DO_HTONS(buf + i, (_v)); i += 2
#define LONG(_v) DO_HTONL(buf + i, (_v)); i += 4
static const unsigned char zeroes[8] = {0};
int
send_ra(const unsigned char *prefix, int tm,
const struct sockaddr_in6 *to, struct interface *interface)
{
int buflen = 1024;
unsigned char buf[buflen];
int i = 0;
if(tm > 0xffff)
tm = 0xffff;
CHECK(16);
BYTE(134);
BYTE(0);
SHORT(0);
BYTE(0);
BYTE(0);
SHORT(prefix != NULL ? tm : 0);
LONG(0);
LONG(0);
if(prefix != NULL) {
CHECK(32);
BYTE(3);
BYTE(4);
BYTE(64);
BYTE(0x80 | 0x40);
LONG(tm);
LONG(2 * tm / 3);
LONG(0);
BYTES(prefix, 8);
BYTES(zeroes, 8);
if(numdnsv6 > 0) {
CHECK(8 + numdnsv6 * 16);
BYTE(25);
BYTE(1 + numdnsv6 * 2);
SHORT(0);
LONG(MAX_RTR_ADV_INTERVAL * 3 / 2);
for(int j = 0; j < numdnsv6; j++) {
BYTES(&dnsv6[j], 16);
}
}
}
if(memcmp(interface->mac, zeroes, 6) != 0) {
CHECK(8);
BYTE(1);
BYTE(1);
BYTES(interface->mac, 6);
}
sendit:
debugf("-> RA\n");
return sendto(ra_socket, buf, i, 0, (struct sockaddr*)to, sizeof(*to));
}
int
receive_rs()
{
int buflen = 1500, rc;
unsigned char buf[buflen];
unsigned char *mac;
struct sockaddr_in6 from;
struct interface *interface;
struct iovec iov[1];
struct msghdr msg;
int cmsglen = 100;
char cmsgbuf[100];
struct cmsghdr *cmsg = (struct cmsghdr*)cmsgbuf;
int hoplimit = -1;
int i, doit;
struct datum *lease;
const unsigned char *addr;
struct client *client;
iov[0].iov_base = buf;
iov[0].iov_len = buflen;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &from;
msg.msg_namelen = sizeof(from);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg;
msg.msg_controllen = cmsglen;
rc = recvmsg(ra_socket, &msg, 0);
if(rc < 0)
return rc;
if(msg.msg_namelen < sizeof(struct sockaddr_in6) ||
from.sin6_family != AF_INET6)
return 0;
cmsg = CMSG_FIRSTHDR(&msg);
while(cmsg != NULL) {
if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
(cmsg->cmsg_type == IPV6_HOPLIMIT)) {
hoplimit = *(unsigned char*)CMSG_DATA(cmsg);
break;
}
}
if(hoplimit != 255)
return 0;
if(rc < 8)
return 0;
if(buf[0] != 133 || buf[1] != 0)
return 0;
if(from.sin6_scope_id == 0)
return 0;
interface = find_interface(from.sin6_scope_id);
if(interface == NULL)
return 0;
mac = NULL;
i = 8;
while(i <= rc - 8) {
if(buf[i] == 1 && buf[i + 1] == 1) {
mac = buf + i + 2;
break;
}
i += buf[i + 1] * 8;
}
if(mac == NULL) {
debugf("No source address option in router solicitation.\n");
return -1;
}
debugf("<- RS %s\n", interface->ifname);
client = update_association(interface, mac, ASSOCIATION_TIME);
if(client == NULL) {
fprintf(stderr, "Failed to create client.\n");
return -1;
}
lease = update_lease(mac, 1, NULL, 3600, &doit);
if(lease == NULL)
return -1;
addr = lease_address(lease, 1);
if(addr == NULL)
return -1;
update_client_route(client, addr, 1);
send_ra(addr, datum_remaining(lease), &from, interface);
return 1;
}
static const unsigned char all_nodes[16] =
{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
int
send_gratuitious_na(struct interface *interface)
{
int buflen = 1024;
unsigned char buf[buflen], myipv6[16];
struct sockaddr_in6 to;
int rc, i = 0;
memset(&to, 0, sizeof(to));
to.sin6_family = AF_INET6;
memcpy(&to.sin6_addr, all_nodes, 16);
to.sin6_scope_id = interface->ifindex;
rc = interface_v6(interface, myipv6);
if(rc < 0)
return rc;
CHECK(24);
BYTE(136);
BYTE(0);
SHORT(0);
BYTE(0x80 | 0x20);
BYTE(0);
SHORT(0);
BYTES(myipv6, 16);
if(memcmp(interface->mac, zeroes, 6) != 0) {
CHECK(8);
BYTE(2);
BYTE(1);
BYTES(interface->mac, 6);
}
sendit:
debugf("-> Neigbour Advertisement\n");
return sendto(ra_socket, buf, i, 0, (struct sockaddr*)&to, sizeof(to));
}
int
ra_setup()
{
return setup_ra_socket();
}
void
ra_cleanup()
{
if(ra_socket < 0)
return;
for(int i = 0; i < numinterfaces; i++) {
struct sockaddr_in6 to;
memset(&to, 0, sizeof(to));
to.sin6_family = AF_INET6;
memcpy(&to.sin6_addr, all_nodes, 16);
to.sin6_scope_id = interfaces[i].ifindex;
send_ra(NULL, 0, &to, &interfaces[i]);
}
close(ra_socket);
ra_socket = -1;
}
/*
Copyright (c) 2015 by Juliusz Chroboczek
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#define MAX_RA_DELAY_TIME 500
#define MIN_DELAY_BETWEEN_RAS 3000
#define MAX_RTR_ADV_INTERVAL 600000
#define MIN_RTR_ADV_INTERVAL (33 * MAX_RTR_ADV_INTERVAL / 100)
extern int ra_socket;
extern unsigned char dnsv6[16][16];
extern int numdnsv6;
int ra_setup(void);
void ra_cleanup(void);
int send_ra(const unsigned char *prefix, int tm,
const struct sockaddr_in6 *to, struct interface *interface);
int receive_rs(void);
int send_gratuitious_na(struct interface *interface);
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netlink/errno.h>
#include "interface.h"
#include "client.h"
#include "lease.h"
#include "ra.h"
#include "dhcpv4.h"
#include "flood.h"
#include "netlink.h"
#include "util.h"
static const unsigned char v4mapped[16] =
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0 };
static void
callback(int add, int ifindex, const unsigned char *mac)
{
struct interface *interface;
struct client *client;
struct datum *lease;
interface = find_interface(ifindex);
if(interface == NULL)
return;
debugf("%s: %s station %s\n",
interface->ifname, add ? "add" : "del", format_48(mac));
if(add) {
unsigned char myv4[4];
int rc;
struct timespec tv;
client = update_association(interface, mac, ASSOCIATION_TIME);
if(client == NULL) {
fprintf(stderr, "Failed to add client.\n");
flush_association(mac, ASSOCIATION_TIME);
return;
}
lease = find_lease(mac, 0);
if(lease) {
const unsigned char *addr;
addr = lease_address(lease, 0);
if(addr == NULL) {
fprintf(stderr, "Bad lease.\n");
return;
}
update_client_route(client, addr, 0);
}
lease = find_lease(mac, 1);
if(lease) {
const unsigned char *addr;
addr = lease_address(lease, 1);
if(addr == NULL) {
fprintf(stderr, "Bad lease.\n");
return;
}
update_client_route(client, addr, 1);
}
tv.tv_sec = 0;
tv.tv_nsec = 100 * 1000 * 1000;
nanosleep(&tv, NULL);
rc = interface_v4(interface, myv4);
if(rc >= 0) {
rc = send_gratuitious_arp(myv4, interface, mac);
if(rc < 0)
perror("send_gratuitious_arp");
}
rc = send_gratuitious_na(interface);
if(rc < 0)
perror("send_gratuitious_na");
} else {
flush_association(mac, ASSOCIATION_TIME);
}
}
void
datum_callback(struct datum *datum, int conflict)
{
if(datum->keylen < 1)
return;
switch(datum_key(datum)[0]) {
case DATUM_ASSOCIATED: {
struct client *client;
if(datum->keylen != 7 || (datum->vallen != 0 && datum->vallen != 8)) {
fprintf(stderr, "Corrupt association.\n");
return;
}
if(datum->vallen == 0)
return;
client = find_client(datum_key(datum) + 1);
if(client != NULL && memcmp(datum_val(datum), myid, 8) != 0) {
debugf("Disassociating %s.\n", format_48(client->mac));
netlink_disassociate(client->interface->ifindex, client->mac,
client->interface->mac);
flush_client(client->mac);
if(conflict)
flush_association(client->mac, ASSOCIATION_TIME);
}
break;
}
case DATUM_IPv4_LEASE:
case DATUM_IPv6_LEASE:
update_lease_routes(datum);
/* XXX discard any matching routes, send RA or FORERENEW. */
break;
}
}
static volatile sig_atomic_t exiting = 0, dumping = 0;
static void
sigexit(int signo)
{
exiting = 1;
}
static void
sigdump(int signo)
{
dumping = 1;
}
static void
init_signals(void)
{
struct sigaction sa;
sigset_t ss;
sigemptyset(&ss);
sa.sa_handler = sigexit;
sa.sa_mask = ss;
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, NULL);
sigemptyset(&ss);
sa.sa_handler = sigexit;
sa.sa_mask = ss;
sa.sa_flags = 0;
sigaction(SIGHUP, &sa, NULL);
sigemptyset(&ss);
sa.sa_handler = sigexit;
sa.sa_mask = ss;
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigemptyset(&ss);
sa.sa_handler = sigdump;
sa.sa_mask = ss;
sa.sa_flags = 0;
sigaction(SIGUSR1, &sa, NULL);
#ifdef SIGINFO
sigemptyset(&ss);
sa.sa_handler = sigdump;
sa.sa_mask = ss;
sa.sa_flags = 0;
sigaction(SIGINFO, &sa, NULL);
#endif
}
static void
check_interface(struct interface *iif)
{
int ifindex, rc;
ifindex = if_nametoindex(iif->ifname);
if(ifindex != iif->ifindex)
iif->ifindex = ifindex;
if(iif->ifindex > 0) {
rc = if_macaddr(iif->ifname, iif->ifindex, iif->mac);
if(rc < 0)
memset(iif->mac, 0, 6);
}
}
int
main(int argc, char **argv)
{
int rc, opt;
unsigned char addr4[4] = {0, 0, 0, 0};
const unsigned char zeroes[4] = {0, 0, 0, 0};
while(1) {
opt = getopt(argc, argv, "f:a:P:d:N:F:");
if(opt < 0)
break;
switch(opt) {
case 'f': {
int p;
char *end;
p = strtol(optarg, &end, 0);
if(*end != '\0' || p <= 0 || p > 0xFFFF)
goto usage;
flood_port = p;
}
break;
case 'a':
rc = inet_pton(AF_INET, optarg, &addr4);
if(rc < 1)
goto usage;
break;
case 'P': {
unsigned char buf[16];
int plen, af;
af = parse_prefix(optarg, buf, &plen);
if(af == 4) {
memcpy(v4prefix, buf, 4);
v4plen = plen;
} else if(af == 6) {
memcpy(v6prefix, buf, 16);
v6plen = plen;
} else {
goto usage;
}
}
break;
case 'd': {
char *end;
debug_level = strtol(optarg, &end, 0);
if(*end != '\0')
goto usage;
break;
}
case 'N': {
unsigned char buf[16];
int af;
af = parse_address(optarg, buf);
if(af == 4) {
if(numdnsv4 > 16)
goto usage;
memcpy(dnsv4[numdnsv4++], buf, 4);
} else if(af == 6) {
if(numdnsv6 > 16)
goto usage;
memcpy(dnsv6[numdnsv6++], buf, 16);
} else {
goto usage;
}
}
break;
case 'F': {
unsigned char buf[16];
unsigned short port;
int af;
af = parse_addrport(optarg, buf, &port);
if(af >= 0) {
struct sockaddr_in6 sin6;
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
if(af == 4) {
memcpy(&sin6.sin6_addr, v4mapped, 12);
memcpy((unsigned char*)&sin6.sin6_addr + 12, buf, 4);
} else {
memcpy(&sin6.sin6_addr, buf, 16);
}
sin6.sin6_port = htons(port);
find_neighbour(&sin6, 1, 0, 1);
} else {
goto usage;
}
}
break;
default:
goto usage;
}
}
rc = read_random_bytes(myid, sizeof(myid));
if(rc < 0) {
perror("read_random_bytes");
exit(1);
}
for(int i = optind; i < argc; i++) {
struct interface *n =
realloc(interfaces,
(numinterfaces + 1) * sizeof(struct interface));
if(n == NULL) {
perror("alloc(interfaces)");
exit(1);
}
interfaces = n;
memset(&interfaces[numinterfaces], 0, sizeof(struct interface));
interfaces[numinterfaces].ifname = argv[i];
numinterfaces++;
}
init_signals();
rc = netlink_init(callback);
if(rc < 0) {
perror("netlink_init");
exit(1);
}
for(int i = 0; i < numinterfaces; i++) {
check_interface(&interfaces[i]);
if(interfaces[i].ifindex > 0) {
rc = netlink_dump(interfaces[i].ifindex);
if(rc < 0)
perror("netlink_dump");
}
}
rc = flood_setup(datum_callback);
if(rc < 0) {
perror("flood_setup");
exit(1);
}
if(numinterfaces > 0) {
rc = ra_setup();
if(rc < 0) {
perror("ra_setup");
exit(1);
}
rc = dhcpv4_setup(memcmp(addr4, zeroes, 4) == 0 ? NULL : addr4);
if(rc < 0) {
perror("dhcpv4_setup");
exit(1);
}
}
while(1) {
fd_set readfds;
int nls = netlink_socket();
int maxfd;
struct timespec deadline;
FD_ZERO(&readfds);
FD_SET(nls, &readfds);
if(numinterfaces > 0) {
FD_SET(ra_socket, &readfds);
FD_SET(dhcpv4_socket, &readfds);
}
FD_SET(flood_socket, &readfds);
maxfd = max(nls, flood_socket);
if(numinterfaces > 0)
maxfd = max(maxfd, max(ra_socket, dhcpv4_socket));
if(flood_time.tv_sec > 0) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
ts_minus(&deadline, &flood_time, &now);
if(deadline.tv_sec < 0) {
deadline.tv_sec = 0;
deadline.tv_nsec = 0;
}
}
rc = pselect(maxfd + 1, &readfds, NULL, NULL,
flood_time.tv_sec > 0 ? &deadline : NULL, NULL);
if(rc < 0 && errno != EINTR) {
perror("pselect");
sleep(1);
}
if(exiting)
break;
if(dumping) {
static const char zeroes[8] = {0};
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
printf("Interfaces");
for(int i = 0; i < numinterfaces; i++)
printf(" %s", interfaces[i].ifname);
printf(".\n");
for(int i = 0; i < numdata; i++) {
if(data[i]->keylen < 1) {
printf("Datum %d %d", data[i]->keylen, data[i]->vallen);
continue;
}
switch(datum_key(data[i])[0]) {
case DATUM_IPv4_LEASE:
case DATUM_IPv6_LEASE: {
char addr[INET6_ADDRSTRLEN];
if(datum_key(data[i])[0] == DATUM_IPv4_LEASE &&
data[i]->keylen == 5) {
inet_ntop(AF_INET, datum_key(data[i]) + 1,
addr, sizeof(addr));
} else if(datum_key(data[i])[0] == DATUM_IPv6_LEASE &&
data[i]->keylen == 9) {
unsigned char ipv6[16];
memcpy(ipv6, datum_key(data[i]) + 1, 8);
memset(ipv6 + 8, 0, 8);
inet_ntop(AF_INET6, ipv6, addr, sizeof(addr));
strncat(addr, "/64", sizeof(addr) - 1);
} else {
strncpy(addr, "(corrupt)", sizeof(addr));
}
printf("Lease %s %s %d %ds.\n",
addr,
data[i]->vallen == 6 ?
format_48(datum_val(data[i])) :
"(corrupt)",
data[i]->seqno,
(int)(data[i]->time - now.tv_sec));
break;
}
case DATUM_ASSOCIATED: {
char mac[24], id[28];
if(data[i]->keylen == 7)
strncpy(mac, format_48(datum_key(data[i]) + 1), 20);
else
strncpy(mac, "(truncated)", 20);
if(data[i]->vallen == 8)
strncpy(id, format_64(datum_val(data[i])), 20);
else
strncpy(id, "(truncated)", 20);
printf("Assoc %s %s %d %ds.\n",
data[i]->keylen == 7 ?
format_48(datum_key(data[i]) + 1) : "(corrupt)",
data[i]->vallen == 0 ? "(gone)" :
data[i]->vallen == 8 ?
format_64(datum_val(data[i])) : "(corrupt)",
data[i]->seqno,
(int)(data[i]->time - now.tv_sec));
break;
}
default:
printf("Datum %d %d %d %d %ds.\n",
data[i]->keylen, data[i]->vallen,
datum_key(data[i])[0],
data[i]->seqno,
(int)(data[i]->time - now.tv_sec));
}
}
for(int i = 0; i < numclients; i++) {
char buf[INET6_ADDRSTRLEN];
printf("Client %s if %s",
format_48(clients[i].mac), clients[i].interface->ifname);
if(memcmp(clients[i].ipv4, zeroes, 8) != 0) {
inet_ntop(AF_INET, clients[i].ipv4, buf, sizeof(buf));
printf(" ipv4 %s", buf);
}
if(memcmp(clients[i].ipv6, zeroes, 8) != 0) {
unsigned char ipv6[16];
memcpy(ipv6, clients[i].ipv6, 8);
memset(ipv6 + 8, 0, 8);
inet_ntop(AF_INET6, ipv6, buf, sizeof(buf));
printf(" ipv6 %s/64", buf);
}
printf(".\n");
}
printf("\n");
for(int i = 0; i < numneighbours; i++) {
char buf[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &neighbours[i].addr.sin6_addr,
buf, sizeof(buf));
printf("Neighbour %s:%d %ds %ds%s.\n",
buf, ntohs(neighbours[i].addr.sin6_port),
(int)(now.tv_sec - neighbours[i].time),
(int)(now.tv_sec - neighbours[i].send_time),
neighbours[i].permanent ? " (permanent)" : "");
}
fflush(stdout);
dumping = 0;
}
if(flood_time.tv_sec > 0) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if(ts_compare(&flood_time, &now) <= 0) {
periodic_flood();
}
}
if(rc <= 0)
continue;
if(FD_ISSET(nls, &readfds)) {
rc = netlink_listen();
if(rc < 0)
nl_perror(rc, "netlink_listen");
}
if(FD_ISSET(flood_socket, &readfds))
flood_listen();
if(numinterfaces > 0) {
if(FD_ISSET(ra_socket, &readfds))
receive_rs();
if(FD_ISSET(dhcpv4_socket, &readfds))
dhcpv4_receive();
}
}
client_cleanup();
if(numinterfaces > 0) {
ra_cleanup();
dhcpv4_cleanup();
}
flood_cleanup();
return 0;
usage:
fprintf(stderr,
"Usage: sroamd [-d level] [-P prefix]... [-N nameserver]... [-a address]\n"
" [-f port] [-F addr:port]... [interface]...\n");
exit(1);
}
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#ifdef HAVE_GETRANDOM
#include <sys/random.h>
#endif
#include <stdarg.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#include <net/if_arp.h>
#include "util.h"
#ifndef NO_DEBUG
int debug_level = 0;
#endif
int
read_random_bytes(void *buf, int len)
{
int rc;
#ifdef HAVE_GETRANDOM
rc = getrandom(buf, len, 0);
#else
rc = -1;
errno = ENOSYS;
#endif
if(rc < 0 && errno == ENOSYS) {
int fd;
fd = open("/dev/urandom", O_RDONLY);
if(fd < 0)
return -1;
rc = read(fd, buf, len);
close(fd);
}
if(rc < len)
rc = -1;
return rc;
}
int
ts_compare(const struct timespec *s1, const struct timespec *s2)
{
if(s1->tv_sec < s2->tv_sec)
return -1;
else if(s1->tv_sec > s2->tv_sec)
return 1;
else if(s1->tv_nsec < s2->tv_nsec)
return -1;
else if(s1->tv_nsec > s2->tv_nsec)
return 1;
else
return 0;
}
/* {0, 0} represents infinity */
void
ts_min(struct timespec *d, const struct timespec *s)
{
if(s->tv_sec == 0)
return;
if(d->tv_sec == 0 || ts_compare(d, s) > 0) {
*d = *s;
}
}
void
ts_minus(struct timespec *d,
const struct timespec *s1, const struct timespec *s2)
{
if(s1->tv_nsec >= s2->tv_nsec) {
d->tv_nsec = s1->tv_nsec - s2->tv_nsec;
d->tv_sec = s1->tv_sec - s2->tv_sec;
} else {
d->tv_nsec = s1->tv_nsec + 1000000000 - s2->tv_nsec;
d->tv_sec = s1->tv_sec - s2->tv_sec - 1;
}
}
int
ts_minus_msec(const struct timespec *s1, const struct timespec *s2)
{
return (s1->tv_sec - s2->tv_sec) * 1000 +
(s1->tv_nsec - s2->tv_nsec) / 1000000;
}
static void
ts_add_nsec(struct timespec *d, const struct timespec *s, long long nsecs)
{
*d = *s;
while(nsecs + d->tv_nsec > 1000000000) {
d->tv_sec += 1;
nsecs -= 1000000000LL;
}
while(nsecs + d->tv_nsec < 0) {
d->tv_sec -= 1;
nsecs += 1000000000LL;
}
d->tv_nsec += nsecs;
}
static const long long million = 1000000LL;
void
ts_add_msec(struct timespec *d, const struct timespec *s, int msecs)
{
ts_add_nsec(d, s, msecs * million);
}
void
ts_add_random(struct timespec *d, const struct timespec *s, int msecs)
{
ts_add_nsec(d, s, (random() % msecs) * million + random() % million);
}
void
ts_zero(struct timespec *d)
{
d->tv_sec = 0;
d->tv_nsec = 0;
}
const char *
format_32(const unsigned char *data)
{
static char buf[4][16];
static int i = 0;
i = (i + 1) % 4;
snprintf(buf[i], 16, "%02x:%02x:%02x:%02x",
data[0], data[1], data[2], data[3]);
return buf[i];
}
const char *
format_48(const unsigned char *data)
{
static char buf[4][22];
static int i = 0;
i = (i + 1) % 4;
snprintf(buf[i], 22, "%02x:%02x:%02x:%02x:%02x:%02x",
data[0], data[1], data[2], data[3], data[4], data[5]);
return buf[i];
}
const char *
format_64(const unsigned char *data)
{
static char buf[4][28];
static int i = 0;
i = (i + 1) % 4;
snprintf(buf[i], 28, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
data[0], data[1], data[2], data[3],
data[4], data[5], data[6], data[7]);
return buf[i];
}
void
do_debugf(int level, const char *format, ...)
{
va_list args;
va_start(args, format);
if(debug_level >= level) {
vfprintf(stderr, format, args);
fflush(stderr);
}
va_end(args);
}
int
parse_address(const char *string, unsigned char *p_return)
{
char buf[INET6_ADDRSTRLEN];
int rc;
struct in_addr ina;
struct in6_addr ina6;
strncpy(buf, string, INET6_ADDRSTRLEN);
buf[INET6_ADDRSTRLEN - 1] = '\0';
rc = inet_pton(AF_INET, buf, &ina);
if(rc > 0) {
memcpy(p_return, &ina, 4);
return 4;
}
rc = inet_pton(AF_INET6, buf, &ina6);
if(rc > 0) {
memcpy(p_return, &ina6, 16);
return 6;
}
return -1;
}
int
parse_prefix(const char *string, unsigned char *p_return, int *plen_return)
{
char buf[INET6_ADDRSTRLEN];
int rc, plen;
char *slash;
struct in_addr ina;
struct in6_addr ina6;
strncpy(buf, string, INET6_ADDRSTRLEN);
buf[INET6_ADDRSTRLEN - 1] = '\0';
slash = strchr(buf, '/');
if(slash == NULL) {
return -1;
} else {
char *end;
plen = strtol(slash + 1, &end, 0);
if(*end != '\0' || plen < 0 || plen > 128)
return -1;
*slash = '\0';
}
rc = inet_pton(AF_INET, buf, &ina);
if(rc > 0) {
memcpy(p_return, &ina, 4);
*plen_return = plen;
return 4;
}
rc = inet_pton(AF_INET6, buf, &ina6);
if(rc > 0) {
memcpy(p_return, &ina6, 16);
*plen_return = plen;
return 6;
}
return -1;
}
int parse_addrport(const char *string, unsigned char *a_return,
unsigned short *port_return)
{
char buf[INET6_ADDRSTRLEN];
int rc, port;
char *colon;
struct in_addr ina;
struct in6_addr ina6;
strncpy(buf, string, INET6_ADDRSTRLEN);
buf[INET6_ADDRSTRLEN - 1] = '\0';
colon = strchr(buf, ':');
if(colon == NULL) {
return -1;
} else {
char *end;
port = strtol(colon + 1, &end, 0);
if(*end != '\0' || port <= 0 || port > 0xFFFF)
return -1;
*colon = '\0';
}
rc = inet_pton(AF_INET, buf, &ina);
if(rc > 0) {
memcpy(a_return, &ina, 4);
*port_return = port;
return 4;
}
rc = inet_pton(AF_INET6, buf, &ina6);
if(rc > 0) {
memcpy(a_return, &ina6, 16);
*port_return = port;
return 6;
}
return -1;
}
static const unsigned char zeroes[6];
int
if_macaddr(char *ifname, int ifindex, unsigned char *mac_return)
{
int s, rc;
struct ifreq ifr;
unsigned char *mac;
s = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
if(s < 0) return -1;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
rc = ioctl(s, SIOCGIFHWADDR, &ifr);
if(rc < 0) {
int saved_errno = errno;
close(s);
errno = saved_errno;
return -1;
}
close(s);
if(ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
debugf("Unknown hardware type %d.\n", ifr.ifr_hwaddr.sa_family);
errno = ENOENT;
return -1;
}
mac = (unsigned char *)ifr.ifr_hwaddr.sa_data;
if(memcmp(mac, zeroes, 6) == 0) {
errno = ENOENT;
return -1;
}
memcpy(mac_return, mac, 6);
return 1;
}
/*
Copyright (c) 2015 by Juliusz Chroboczek
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifdef NO_DEBUG
#define debugf(...) do {} while(0)
#else
extern int debug_level;
#define debugf(...) \
do { \
if(debug_level >= 2) do_debugf(2, __VA_ARGS__); \
} while(0)
#endif
#define DO_NTOHS(_d, _s) \
do { unsigned short _dd; \
memcpy(&(_dd), (_s), 2); \
_d = ntohs(_dd); } while(0)
#define DO_NTOHL(_d, _s) \
do { unsigned int _dd; \
memcpy(&(_dd), (_s), 4); \
_d = ntohl(_dd); } while(0)
#define DO_HTONS(_d, _s) \
do { unsigned short _dd; \
_dd = htons(_s); \
memcpy((_d), &(_dd), 2); } while(0)
#define DO_HTONL(_d, _s) \
do { unsigned _dd; \
_dd = htonl(_s); \
memcpy((_d), &(_dd), 4); } while(0)
#ifdef HAVE_VALGRIND
#include <valgrind/memcheck.h>
#define MEM_UNDEFINED VALGRIND_MAKE_MEM_UNDEFINED
#else
#define MEM_UNDEFINED(_a, _l) do {} while(0)
#endif
static inline int
min(int x, int y)
{
return x > y ? y : x;
}
static inline int
max(int x, int y)
{
return x > y ? x : y;
}
int read_random_bytes(void *buf, int len);
int ts_compare(const struct timespec *s1, const struct timespec *s2);
void ts_min(struct timespec *d, const struct timespec *s);
void ts_minus(struct timespec *d,
const struct timespec *s1, const struct timespec *s2);
int ts_minus_msec(const struct timespec *s1, const struct timespec *s2);
void ts_min_sec(struct timespec *d, int secs);
void ts_add_msec(struct timespec *d, const struct timespec *s, int msecs);
void ts_add_random(struct timespec *d, const struct timespec *s, int msecs);
void ts_zero(struct timespec *d);
const char *format_32(const unsigned char *data);
const char *format_48(const unsigned char *data);
const char *format_64(const unsigned char *data);
void do_debugf(int level, const char *format, ...)
#ifdef __GNUC__
__attribute__((format (printf, 2, 3))) __attribute__((cold))
#endif
;
int parse_address(const char *string, unsigned char *p_return);
int parse_prefix(const char *string, unsigned char *p_return, int *plen_return);
int parse_addrport(const char *string, unsigned char *a_return,
unsigned short *port_return);
int if_macaddr(char *ifname, int ifindex, unsigned char *mac_return);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment