network_state_information_priv.c [plain text]
#include <arpa/inet.h>
#include <assert.h>
#include <notify.h>
#include <string.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdbool.h>
#include "network_state_information_priv.h"
#include <limits.h>
#include <stdio.h>
#include <syslog.h>
#define NWI_IFSTATE_FLAGS(flags) \
((flags) & NWI_IFSTATE_FLAGS_MASK)
#define NWI_IFSTATE_FLAGS_GET_DIFF(flags) \
(((flags) & NWI_IFSTATE_FLAGS_DIFF_MASK) >> 8)
#define NWI_IFSTATE_FLAGS_FROM_DIFF(diff) \
(((diff) << 8) & NWI_IFSTATE_FLAGS_DIFF_MASK)
#define NWI_IFSTATE_DIFF_UNCHANGED 0
#define NWI_IFSTATE_DIFF_ADDED 1
#define NWI_IFSTATE_DIFF_REMOVED 2
#define NWI_IFSTATE_DIFF_CHANGED 3
#define NWI_IFSTATE_DIFF_RANK_UP 4
#define NWI_IFSTATE_DIFF_RANK_DOWN 5
static void
nwi_state_fix_af_aliases(nwi_state_t state, uint32_t old_max_if_count)
{
int64_t offset;
int i;
nwi_ifstate_t ifstate;
offset = state->max_if_count - old_max_if_count;
if (offset < 0) {
syslog(LOG_ERR, "new count %d < old count %d",
state->max_if_count, old_max_if_count);
return;
}
for (i = 0, ifstate = nwi_state_ifstate_list(state, AF_INET);
i < state->ipv4_count;
i++, ifstate++) {
if (ifstate->af_alias_offset != 0) {
ifstate->af_alias_offset += offset;
}
}
for (i = 0, ifstate = nwi_state_ifstate_list(state, AF_INET6);
i < state->ipv6_count;
i++, ifstate++) {
if (ifstate->af_alias_offset != 0) {
ifstate->af_alias_offset -= offset;
}
}
return;
}
static void
nwi_state_add_to_if_list(nwi_state_t state, nwi_ifstate_t ifstate)
{
int i;
nwi_ifindex_t * scan;
if ((ifstate->flags & NWI_IFSTATE_FLAGS_NOT_IN_IFLIST) != 0) {
return;
}
if (state->if_list_count >= state->max_if_count) {
return;
}
for (i = 0, scan = nwi_state_if_list(state);
i < state->if_list_count;
i++, scan++) {
nwi_ifstate_t this;
this = state->ifstate_list + *scan;
if (strcmp(this->ifname, ifstate->ifname) == 0) {
return;
}
}
*scan = (nwi_ifindex_t)(ifstate - state->ifstate_list);
state->if_list_count++;
return;
}
static void
nwi_state_set_if_list(nwi_state_t state)
{
nwi_ifstate_t scan_v4;
nwi_ifstate_t scan_v6;
int v4;
int v6;
v4 = 0;
v6 = 0;
state->if_list_count = 0;
scan_v4 = nwi_state_get_ifstate_with_index(state, AF_INET, v4);
scan_v6 = nwi_state_get_ifstate_with_index(state, AF_INET6, v6);
while (scan_v4 != NULL || scan_v6 != NULL) {
bool add_v4 = FALSE;
if (scan_v4 != NULL && scan_v6 != NULL) {
if (scan_v4->rank <= scan_v6->rank) {
add_v4 = TRUE;
}
}
else if (scan_v4 != NULL) {
add_v4 = TRUE;
}
if (add_v4) {
nwi_state_add_to_if_list(state, scan_v4);
v4++;
scan_v4 = nwi_state_get_ifstate_with_index(state,
AF_INET,
v4);
}
else {
nwi_state_add_to_if_list(state, scan_v6);
v6++;
scan_v6 = nwi_state_get_ifstate_with_index(state,
AF_INET6,
v6);
}
}
return;
}
__private_extern__
nwi_state_t
nwi_state_make_copy(nwi_state_t src)
{
nwi_state_t dest = NULL;
size_t size;
if (src == NULL) {
return dest;
}
size = nwi_state_size(src);
dest = malloc(size);
if (dest != NULL) {
bcopy(src, dest, size);
}
return dest;
}
__private_extern__
nwi_state_t
nwi_state_new(nwi_state_t old_state, int max_if_count)
{
size_t size;
nwi_state_t state = NULL;
if (old_state == NULL && max_if_count == 0) {
return NULL;
}
if (old_state != NULL) {
if (old_state->max_if_count >= max_if_count) {
return (old_state);
}
}
size = nwi_state_compute_size(max_if_count);
state = malloc(size);
bzero(state, size);
state->max_if_count = max_if_count;
state->version = NWI_STATE_VERSION;
if (old_state != NULL) {
state->ipv6_count = old_state->ipv6_count;
if (state->ipv6_count > 0) {
bcopy((void *)&old_state->ifstate_list[old_state->max_if_count],
(void *)&state->ifstate_list[state->max_if_count],
old_state->ipv6_count * sizeof(nwi_ifstate));
}
state->ipv4_count = old_state->ipv4_count;
if (state->ipv4_count > 0) {
bcopy((void *)old_state->ifstate_list,
(void *)state->ifstate_list,
old_state->ipv4_count * sizeof(nwi_ifstate));
}
nwi_state_fix_af_aliases(state, old_state->max_if_count);
nwi_state_set_if_list(state);
nwi_state_free(old_state);
} else {
state->ipv4_count = 0;
state->ipv6_count = 0;
}
return state;
}
__private_extern__ void
nwi_state_finalize(nwi_state_t state)
{
if (state == NULL) {
return;
}
nwi_state_set_if_list(state);
return;
}
static __inline__
nwi_ifstate_t
nwi_state_get_last_ifstate(nwi_state_t state, int af, nwi_ifindex_t** last)
{
nwi_ifindex_t * count;
int idx;
assert(state != NULL);
count = (af == AF_INET) ? &state->ipv4_count
: &state->ipv6_count;
idx = (af == AF_INET) ? state->ipv4_count
: (state->max_if_count + state->ipv6_count);
*last = count;
return &state->ifstate_list[idx];
}
__private_extern__
void
nwi_ifstate_set_signature(nwi_ifstate_t ifstate, uint8_t * signature)
{
bcopy(signature, ifstate->signature, sizeof(ifstate->signature));
ifstate->flags |= NWI_IFSTATE_FLAGS_HAS_SIGNATURE;
return;
}
static void
nwi_state_add_ifstate_alias(nwi_state_t state, nwi_ifstate_t ifstate)
{
nwi_ifstate_t alias;
alias = nwi_state_get_ifstate_with_name(state,
nwi_other_af(ifstate->af),
ifstate->ifname);
if (alias == NULL) {
return;
}
ifstate->af_alias_offset = (nwi_ifindex_t)(alias - ifstate);
alias->af_alias_offset = (nwi_ifindex_t)(ifstate - alias);
return;
}
__private_extern__ nwi_ifstate_t
nwi_state_add_ifstate(nwi_state_t state,
const char * ifname, int af,
uint64_t flags, Rank rank,
void * ifa,
struct sockaddr * vpn_server_addr,
uint32_t reach_flags)
{
nwi_ifstate_t ifstate;
ifstate = nwi_state_get_ifstate_with_name(state, af, ifname);
if (ifstate != NULL) {
if (ifstate->rank < rank) {
return NULL;
}
}
else {
nwi_ifindex_t count;
nwi_ifindex_t * count_p;
ifstate = nwi_state_get_last_ifstate(state, af, &count_p);
count = *count_p;
if (count == state->max_if_count) {
syslog(LOG_ERR,
"nwi_state_add_ifstate: full at count %d\n",
count);
return (NULL);
}
if (count > 0) {
nwi_ifstate_t prev = ifstate - 1;
prev->flags &= ~NWI_IFSTATE_FLAGS_LAST_ITEM;
}
bzero(ifstate, sizeof(*ifstate));
strlcpy(ifstate->ifname, ifname, sizeof(ifstate->ifname));
ifstate->af = af;
ifstate->flags |= NWI_IFSTATE_FLAGS_LAST_ITEM;
(*count_p)++;
nwi_state_add_ifstate_alias(state, ifstate);
}
if (ifa != NULL) {
switch (af) {
case AF_INET:
ifstate->iaddr = *((struct in_addr *) ifa);
break;
case AF_INET6:
ifstate->iaddr6 = *((struct in6_addr *) ifa);
break;
default:
break;
}
}
if (vpn_server_addr != NULL && vpn_server_addr->sa_family != 0) {
_nwi_ifstate_set_vpn_server(ifstate, vpn_server_addr);
} else {
_nwi_ifstate_set_vpn_server(ifstate, NULL);
}
ifstate->reach_flags = reach_flags;
ifstate->rank = rank;
ifstate->flags &= ~NWI_IFSTATE_FLAGS_MASK;
ifstate->flags |= NWI_IFSTATE_FLAGS(flags);
return ifstate;
}
__private_extern__
void
nwi_state_clear(nwi_state_t state, int af)
{
if (af == AF_INET) {
state->ipv4_count = 0;
}
else {
state->ipv6_count = 0;
}
return;
}
__private_extern__
void *
nwi_ifstate_get_address(nwi_ifstate_t ifstate)
{
return (void *)&ifstate->iaddr;
}
static __inline__ uint8_t
nwi_ifstate_get_diff(nwi_ifstate_t ifstate)
{
return (NWI_IFSTATE_FLAGS_GET_DIFF(ifstate->flags));
}
__private_extern__ const char *
nwi_ifstate_get_diff_str(nwi_ifstate_t ifstate)
{
const char * strings[] = {
"",
"+",
"-",
"!",
"/",
"\\"
};
uint8_t diff;
diff = nwi_ifstate_get_diff(ifstate);
if (diff < sizeof(strings) / sizeof(strings[0])) {
return (strings[diff]);
}
return ("?");
}
__private_extern__ nwi_ifstate_difference_t
nwi_ifstate_get_difference(nwi_ifstate_t diff_ifstate)
{
nwi_ifstate_difference_t diff;
switch (nwi_ifstate_get_diff(diff_ifstate)) {
case NWI_IFSTATE_DIFF_ADDED:
case NWI_IFSTATE_DIFF_CHANGED:
diff = knwi_ifstate_difference_changed;
break;
case NWI_IFSTATE_DIFF_REMOVED:
diff = knwi_ifstate_difference_removed;
break;
default:
diff = knwi_ifstate_difference_none;
break;
}
return (diff);
}
static inline boolean_t
nwi_ifstate_has_changed(nwi_ifstate_t ifstate1, nwi_ifstate_t ifstate2)
{
if (NWI_IFSTATE_FLAGS(ifstate1->flags)
!= NWI_IFSTATE_FLAGS(ifstate2->flags)) {
return TRUE;
}
if (ifstate1->af == AF_INET) {
if (memcmp(&ifstate1->iaddr, &ifstate2->iaddr, sizeof(struct in_addr)) != 0) {
return TRUE;
}
} else {
if (memcmp(&ifstate1->iaddr6, &ifstate2->iaddr6, sizeof(struct in6_addr)) != 0) {
return TRUE;
}
}
return FALSE;
}
static inline nwi_ifstate_t
nwi_state_diff_append(nwi_state_t state, nwi_ifstate_t scan)
{
nwi_ifstate_t new_ifstate = NULL;
nwi_ifindex_t * last;
new_ifstate = nwi_state_get_last_ifstate(state, scan->af, &last);
memcpy(new_ifstate, scan, sizeof(*scan));
(*last)++;
return new_ifstate;
}
static inline void
nwi_ifstate_set_diff(nwi_ifstate_t ifstate, uint8_t diff)
{
ifstate->flags &= ~NWI_IFSTATE_FLAGS_DIFF_MASK;
if (diff != NWI_IFSTATE_DIFF_UNCHANGED) {
ifstate->flags |= NWI_IFSTATE_FLAGS_FROM_DIFF(diff);
}
}
static void
nwi_state_diff_add_change(nwi_state_t diff, nwi_state_t old,
nwi_ifstate_t ifstate)
{
nwi_ifstate_t existing;
nwi_ifstate_t new;
existing = nwi_state_get_ifstate_with_name(old,
ifstate->af,
nwi_ifstate_get_ifname(ifstate));
new = nwi_state_diff_append(diff, ifstate);
if (existing != NULL) {
if (nwi_ifstate_has_changed(existing, new)) {
nwi_ifstate_set_diff(new,
NWI_IFSTATE_DIFF_CHANGED);
} else if (existing->rank < new->rank) {
nwi_ifstate_set_diff(new,
NWI_IFSTATE_DIFF_RANK_DOWN);
} else if (existing->rank > new->rank) {
nwi_ifstate_set_diff(new,
NWI_IFSTATE_DIFF_RANK_UP);
} else {
nwi_ifstate_set_diff(new,
NWI_IFSTATE_DIFF_UNCHANGED);
}
} else {
nwi_ifstate_set_diff(new, NWI_IFSTATE_DIFF_ADDED);
}
return;
}
static void
nwi_state_diff_remove(nwi_state_t state, nwi_ifstate_t ifstate)
{
nwi_ifstate_t removed_ifstate;
if (nwi_state_get_ifstate_with_name(state,
ifstate->af,
nwi_ifstate_get_ifname(ifstate))
!= NULL) {
return;
}
removed_ifstate = nwi_state_diff_append(state, ifstate);
nwi_ifstate_set_diff(removed_ifstate, NWI_IFSTATE_DIFF_REMOVED);
return;
}
static void
nwi_state_diff_populate(nwi_state_t diff, nwi_state_t old, nwi_state_t new)
{
int i;
nwi_ifstate_t scan;
if (new != NULL) {
if (new->ipv4_count) {
for (i = 0, scan = new->ifstate_list;
i < new->ipv4_count; i++, scan++) {
nwi_state_diff_add_change(diff, old, scan);
}
}
if (new->ipv6_count) {
scan = new->ifstate_list + new->max_if_count;
for (i = 0;
i < new->ipv6_count; i++, scan++) {
nwi_state_diff_add_change(diff, old, scan);
}
}
}
if (old != NULL) {
if (old->ipv4_count) {
for (i = 0, scan = old->ifstate_list;
i < old->ipv4_count; i++, scan++) {
nwi_state_diff_remove(diff, scan);
}
}
if (old->ipv6_count) {
scan = old->ifstate_list + old->max_if_count;
for (i = 0;
i < old->ipv6_count; i++, scan++) {
nwi_state_diff_remove(diff, scan);
}
}
}
return;
}
static int
nwi_state_max_af_count(nwi_state_t state)
{
if (state->ipv4_count >= state->ipv6_count) {
return (state->ipv4_count);
}
return (state->ipv6_count);
}
__private_extern__ nwi_state_t
nwi_state_diff(nwi_state_t old, nwi_state_t new)
{
nwi_state_t diff;
int total_count = 0;
if (old != NULL) {
total_count += nwi_state_max_af_count(old);
}
if (new != NULL) {
total_count += nwi_state_max_af_count(new);
}
if (total_count == 0) {
return NULL;
}
diff = nwi_state_new(NULL, total_count);
nwi_state_diff_populate(diff, old, new);
return diff;
}
static __inline__
void
_nwi_ifstate_set_generation(nwi_ifstate_t ifstate, uint64_t generation_count)
{
ifstate->if_generation_count = generation_count;
return;
}
static
boolean_t
_nwi_ifstate_has_changed(nwi_state_t state, const char * ifname)
{
nwi_ifstate_t ifstate;
ifstate = nwi_state_get_ifstate_with_name(state,
AF_INET,
ifname);
if (ifstate != NULL
&& nwi_ifstate_get_diff(ifstate) != NWI_IFSTATE_DIFF_UNCHANGED) {
return (TRUE);
}
ifstate = nwi_state_get_ifstate_with_name(state,
AF_INET6,
ifname);
if (ifstate != NULL
&& nwi_ifstate_get_diff(ifstate) != NWI_IFSTATE_DIFF_UNCHANGED) {
return (TRUE);
}
return (FALSE);
}
__private_extern__
void
_nwi_state_update_interface_generations(nwi_state_t old_state, nwi_state_t state, nwi_state_t changes)
{
int i;
uint64_t generation_count;
nwi_ifstate_t scan;
if (state == NULL || changes == NULL) {
return;
}
generation_count = state->generation_count;
for (i = 0, scan = nwi_state_ifstate_list(state, AF_INET);
i < state->ipv4_count; i++, scan++) {
if (_nwi_ifstate_has_changed(changes, scan->ifname)) {
_nwi_ifstate_set_generation(scan, generation_count);
} else {
nwi_ifstate_t old_ifstate;
old_ifstate = nwi_state_get_ifstate_with_name(old_state,
AF_INET,
scan->ifname);
assert(old_ifstate != NULL);
_nwi_ifstate_set_generation(scan,
old_ifstate->if_generation_count);
}
}
for (i = 0, scan = nwi_state_ifstate_list(state, AF_INET6);
i < state->ipv6_count; i++, scan++) {
if (nwi_ifstate_get_generation(scan) ==
generation_count) {
continue;
}
if (_nwi_ifstate_has_changed(changes, scan->ifname)) {
_nwi_ifstate_set_generation(scan, generation_count);
} else {
nwi_ifstate_t old_ifstate;
old_ifstate = nwi_state_get_ifstate_with_name(old_state,
AF_INET6,
scan->ifname);
assert(old_ifstate != NULL);
_nwi_ifstate_set_generation(scan,
old_ifstate->if_generation_count);
}
}
return;
}
__private_extern__
void
_nwi_state_compute_sha1_hash(nwi_state_t state,
unsigned char hash[CC_SHA1_DIGEST_LENGTH])
{
if (state == NULL) {
bzero(hash, CC_SHA1_DIGEST_LENGTH);
}
else {
CC_SHA1_CTX ctx;
uint64_t generation_save;
generation_save = state->generation_count;
state->generation_count = 0;
CC_SHA1_Init(&ctx);
CC_SHA1_Update(&ctx, state, (CC_LONG)nwi_state_size(state));
CC_SHA1_Final(hash, &ctx);
state->generation_count = generation_save;
}
return;
}