This repository has been archived on 2020-04-11. You can view files and clone it, but cannot push or open issues or pull requests.
eilduscd/src/nwstatus.c

326 lines
7.4 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <linux/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include "list.h"
#include "common.h"
#include "nwstatus.h"
typedef struct {
List *interfaces;
List *ipv4;
List *ipv6;
} netconfig_t;
static netconfig_t *nstatus = NULL;
static void sort_addresses(List *list);
void
netstatus_init(void) {
if (nstatus == NULL) {
nstatus = malloc(sizeof(netconfig_t));
nstatus->interfaces = new_list();
nstatus->ipv4 = new_list();
nstatus->ipv6 = new_list();
}
}
static int
find_interface(void *data, void *user_data) {
return ((interface_t *)data)->index == *((int *)user_data);
}
static interface_t *
new_interface(int index) {
interface_t *result;
result = malloc(sizeof(interface_t));
result->name = NULL;
result->index = index;
result->has_link = -1;
list_append(nstatus->interfaces, result);
return result;
}
static interface_t *
get_interface(index) {
interface_t *result;
result = list_search(nstatus->interfaces, find_interface, &index);
if (result == NULL) {
result = new_interface(index);
}
return result;
}
void
interface_set_name(int index, char *name) {
interface_t *interface;
interface = get_interface(index);
free(interface->name);
interface->name = strdup(name);
}
void
interface_set_link(int index, int has_link) {
interface_t *interface;
interface = get_interface(index);
interface->has_link = has_link;
}
void
interface_set_flags(int index, unsigned int flags) {
interface_t *interface;
interface = get_interface(index);
interface->flags = flags;
}
int
interface_get_link(int index) {
return get_interface(index)->has_link;
}
char *interface_get_name(int index) {
return get_interface(index)->name;
}
static int
do_del_address(void *data, void *user_data) {
address_t *a;
a = (address_t *)data;
if (a->interface == user_data) {
switch (a->family) {
case AF_INET:
list_del(nstatus->ipv4, a);
break;
case AF_INET6:
list_del(nstatus->ipv6, a);
break;
}
free(a);
}
return TRUE;
}
void del_interface(int index) {
interface_t *interface;
interface = get_interface(index);
list_foreach(nstatus->ipv4, do_del_address, interface);
list_foreach(nstatus->ipv6, do_del_address, interface);
list_del(nstatus->interfaces, interface);
sort_addresses(nstatus->ipv4);
sort_addresses(nstatus->ipv6);
}
static int
find_v4(void *data, void *user_data) {
address_t *a;
a = (address_t *)data;
return a->a.v4.s_addr == ((struct in_addr *) user_data)->s_addr;
}
static int
find_v6(void *data, void *user_data) {
address_t *a;
struct in6_addr *addr;
int i;
a = (address_t *)data;
addr = (struct in6_addr *)user_data;
for (i = 0 ; i < 4 ; i++) {
if (a->a.v6.s6_addr32[i] != addr->s6_addr32[i]) {
return FALSE;
}
}
return TRUE;
}
void
add_address(int index, uint8_t scope, uint8_t family, void *addr) {
address_t *a = NULL;
switch (family) {
case AF_INET:
a = list_search(nstatus->ipv4, find_v4, addr);
break;
case AF_INET6:
a = list_search(nstatus->ipv6, find_v6, addr);
break;
default:
return;
}
if (a == NULL) {
a = malloc(sizeof(address_t));
a->family = family;
a->interface = get_interface(index);
a->scope = scope;
a->current = 0;
switch (family) {
case AF_INET:
a->a.v4 = *(struct in_addr*)addr;
list_append(nstatus->ipv4, a);
sort_addresses(nstatus->ipv4);
break;
case AF_INET6:
a->a.v6 = *(struct in6_addr *)addr;
list_append(nstatus->ipv6, a);
sort_addresses(nstatus->ipv6);
break;
}
}
}
void
del_address(int index, uint8_t family, void *addr) {
address_t *a = NULL;
switch (family) {
case AF_INET:
a = list_search(nstatus->ipv4, find_v4, addr);
list_del(nstatus->ipv4, a);
sort_addresses(nstatus->ipv4);
break;
case AF_INET6:
a = list_search(nstatus->ipv6, find_v6, addr);
list_del(nstatus->ipv6, a);
sort_addresses(nstatus->ipv6);
break;
}
free(a);
}
static
int v4address_type(address_t *addr) {
#define IN_SUBNET(i0,i1,i2,i3, mask) \
!(((ntohl(addr->a.v4.s_addr)) - \
((i0 << 24) + (i1 << 16) + (i2 << 8) + (i3))) \
& (0xffffffff << (32 - mask)))
#define NORMAL 0
#define PRIVATE 1
#define ZEROCONF 2
if (IN_SUBNET(168,254,0,0, 16)) {
return ZEROCONF;
}
if (IN_SUBNET(10,0,0,0, 8)
|| IN_SUBNET(172,16,0,0, 12)
|| IN_SUBNET(192,168,0,0, 16)) {
return PRIVATE;
}
return NORMAL;
}
static int print_address(void *data, void *user_data);
int
address_cmp(void *data0, void *data1, void *user_data) {
address_t *a0 = (address_t *)data0;
address_t *a1 = (address_t *)data1;
/* We want scope global/universe in front */
if (a0->scope != RT_SCOPE_UNIVERSE || a1->scope != RT_SCOPE_UNIVERSE) {
return (a1->scope == RT_SCOPE_UNIVERSE ? 1 : 0) -
(a0->scope == RT_SCOPE_UNIVERSE ? 1 : 0);
}
/* Address with link up before addresses without a link */
if (!a0->interface->has_link || !a1->interface->has_link) {
return (a1->interface->has_link ? 1 : 0) -
(a0->interface->has_link ? 1 : 0);
}
/* Prefer to first have normal, then private then zeroconf addresses */
if (a0->family == AF_INET) {
int a0_type = v4address_type(a0);
int a1_type = v4address_type(a1);
if (a0_type != a1_type) {
return a0_type - a1_type;
}
}
return a1->current - a0->current;
}
static void
sort_addresses(List *list) {
list_sort(list, address_cmp, NULL);
}
static
address_t *get_best_address(List *list) {
List_ptr *p;
address_t *result = NULL;
p = new_list_ptr(list);
if (list_ptr_first(p)) {
result = (address_t *)list_ptr_get_data(p);
if (result->scope != RT_SCOPE_UNIVERSE
|| !result->interface->has_link) {
result = NULL;
}
}
del_list_ptr(p);
return result;
}
address_t *get_best_v4_address(void) {
return get_best_address(nstatus->ipv4);
}
address_t *get_best_v6_address(void) {
return get_best_address(nstatus->ipv6);
}
static int
unset_current_address(void *data, void *user_data) {
((address_t *)data)->current = FALSE;
return TRUE;
}
void address_toggle_current(address_t *address) {
switch (address->family) {
case AF_INET:
list_foreach(nstatus->ipv4, unset_current_address, NULL);
break;
case AF_INET6:
list_foreach(nstatus->ipv6, unset_current_address, NULL);
break;
}
address->current = TRUE;
}
static int
print_interfaces(void *data, void *user_data) {
interface_t *i;
i = (interface_t *)data;
printf("%s (%s, %x)\n", i->name, i->has_link ? "UP" : "DOWN", i->flags);
return TRUE;
}
static int
print_address(void *data, void *user_data) {
char buffer[INET6_ADDRSTRLEN];
address_t *a;
a = (address_t *)data;
if (a == NULL) {
return TRUE;
}
inet_ntop(a->family, &(a->a.v4), buffer, INET6_ADDRSTRLEN);
printf("\t%s (%d): %s\n", a->interface->name, a->interface->index, buffer);
return TRUE;
}
void
print_status(void) {
list_foreach(nstatus->interfaces, print_interfaces, NULL);
list_foreach(nstatus->ipv4, print_address, NULL);
list_foreach(nstatus->ipv6, print_address, NULL);
printf("Choosen addresses:\n");
print_address(get_best_v4_address(), NULL);
print_address(get_best_v6_address(), NULL);
}