ipv4acd: rework how we pick ipv4ll addresses

Let's make the seed actually work as stable seed, and use siphash24 to generate
the series of addresses, instead of the opaque libc random_r().

This not only makes the seed truly work as stable, portable seed, but also
makes the code quite a bit shorter, and removes a couple of memory allocations.
This commit is contained in:
Lennart Poettering 2016-05-23 19:24:21 +02:00
parent 38958cd66e
commit 96a7979f3d

View file

@ -28,6 +28,7 @@
#include "sd-ipv4ll.h"
#include "alloc-util.h"
#include "ether-addr-util.h"
#include "in-addr-util.h"
#include "list.h"
#include "random-util.h"
@ -35,8 +36,8 @@
#include "sparse-endian.h"
#include "util.h"
#define IPV4LL_NETWORK 0xA9FE0000UL
#define IPV4LL_NETMASK 0xFFFF0000UL
#define IPV4LL_NETWORK UINT32_C(0xA9FE0000)
#define IPV4LL_NETMASK UINT32_C(0xFFFF0000)
#define IPV4LL_DONT_DESTROY(ll) \
_cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll)
@ -45,16 +46,25 @@ struct sd_ipv4ll {
unsigned n_ref;
sd_ipv4acd *acd;
be32_t address; /* the address pushed to ACD */
struct random_data *random_data;
char *random_data_state;
struct ether_addr mac;
struct {
le64_t value;
le64_t generation;
} seed;
bool seed_set;
/* External */
be32_t claimed_address;
sd_ipv4ll_callback_t callback;
void* userdata;
};
static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll) {
if (!ll)
return NULL;
@ -76,16 +86,11 @@ sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) {
return NULL;
sd_ipv4acd_unref(ll->acd);
free(ll->random_data);
free(ll->random_data_state);
free(ll);
return NULL;
}
static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
int sd_ipv4ll_new(sd_ipv4ll **ret) {
_cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL;
int r;
@ -113,43 +118,32 @@ int sd_ipv4ll_new(sd_ipv4ll **ret) {
}
int sd_ipv4ll_stop(sd_ipv4ll *ll) {
int r;
assert_return(ll, -EINVAL);
r = sd_ipv4acd_stop(ll->acd);
if (r < 0)
return r;
return 0;
return sd_ipv4acd_stop(ll->acd);
}
int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) {
assert_return(ll, -EINVAL);
assert_return(ifindex > 0, -EINVAL);
assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
return sd_ipv4acd_set_ifindex(ll->acd, ifindex);
}
#define HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) {
int r;
assert_return(ll, -EINVAL);
assert_return(addr, -EINVAL);
assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
if (!ll->random_data) {
uint64_t seed;
r = sd_ipv4acd_set_mac(ll->acd, addr);
if (r < 0)
return r;
/* If no random data is set, generate some from the MAC */
seed = siphash24(&addr->ether_addr_octet, ETH_ALEN, HASH_KEY.bytes);
r = sd_ipv4ll_set_address_seed(ll, htole64(seed));
if (r < 0)
return r;
}
return sd_ipv4acd_set_mac(ll->acd, addr);
ll->mac = *addr;
return 0;
}
int sd_ipv4ll_detach_event(sd_ipv4ll *ll) {
@ -186,31 +180,11 @@ int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) {
}
int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) {
_cleanup_free_ struct random_data *random_data = NULL;
_cleanup_free_ char *random_data_state = NULL;
int r;
assert_return(ll, -EINVAL);
assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
random_data = new0(struct random_data, 1);
if (!random_data)
return -ENOMEM;
random_data_state = new0(char, 128);
if (!random_data_state)
return -ENOMEM;
r = initstate_r((unsigned) seed, random_data_state, 128, random_data);
if (r < 0)
return r;
free(ll->random_data);
ll->random_data = random_data;
random_data = NULL;
free(ll->random_data_state);
ll->random_data_state = random_data_state;
random_data_state = NULL;
ll->seed.value = htole64(seed);
ll->seed_set = true;
return 0;
}
@ -254,48 +228,64 @@ int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) {
return 0;
}
#define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b)
static int ipv4ll_pick_address(sd_ipv4ll *ll) {
struct in_addr in_addr;
be32_t addr;
int r;
int32_t random;
assert(ll);
assert(ll->random_data);
do {
r = random_r(ll->random_data, &random);
if (r < 0)
return r;
addr = htobe32((random & 0x0000FFFF) | IPV4LL_NETWORK);
uint64_t h;
h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes);
/* Increase the generation counter by one */
ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1);
addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK);
} while (addr == ll->address ||
(ntohl(addr) & 0x0000FF00) == 0x0000 ||
(ntohl(addr) & 0x0000FF00) == 0xFF00);
(be32toh(addr) & 0x0000FF00) == 0x0000 ||
(be32toh(addr) & 0x0000FF00) == 0xFF00);
in_addr.s_addr = addr;
r = sd_ipv4ll_set_address(ll, &in_addr);
if (r < 0)
return r;
return 0;
return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr });
}
#define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
int sd_ipv4ll_start(sd_ipv4ll *ll) {
int r;
bool picked_address = false;
assert_return(ll, -EINVAL);
assert_return(ll->random_data, -EINVAL);
assert_return(!ether_addr_is_null(&ll->mac), -EINVAL);
assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY);
/* If no random seed is set, generate some from the MAC address */
if (!ll->seed_set)
ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes));
/* Restart the generation counter. */
ll->seed.generation = 0;
if (ll->address == 0) {
r = ipv4ll_pick_address(ll);
if (r < 0)
return r;
picked_address = true;
}
r = sd_ipv4acd_start(ll->acd);
if (r < 0)
if (r < 0) {
/* We couldn't start? If so, let's forget the picked address again, the user might make a change and
* retry, and we want the new data to take effect when picking an address. */
if (picked_address)
ll->address = 0;
return r;
}
return 0;
}
@ -345,6 +335,7 @@ void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) {
}
break;
default:
assert_not_reached("Invalid IPv4ACD event.");
}