diff options
Diffstat (limited to 'shared')
32 files changed, 3184 insertions, 724 deletions
diff --git a/shared/nm-utils/c-list.h b/shared/c-list/src/c-list.h index a3c4053b..ff434d8d 100644 --- a/shared/nm-utils/c-list.h +++ b/shared/c-list/src/c-list.h @@ -1,7 +1,7 @@ #pragma once /* - * Circular Double Linked List Implementation in Standard ISO-C11 + * Circular Intrusive Double Linked List Collection in ISO-C11 * * This implements a generic circular double linked list. List entries must * embed the CList object, which provides pointers to the next and previous @@ -225,71 +225,6 @@ static inline void c_list_splice(CList *target, CList *source) { } /** - * c_list_for_each() - loop over all list entries - * @_iter: iterator to use - * @_list: list to loop over - * - * This is a macro to use as for-loop to iterate an entire list. It is meant as - * convenience macro. Feel free to code your own loop iterator. - */ -#define c_list_for_each(_iter, _list) \ - for (_iter = (_list)->next; \ - (_iter) != (_list); \ - _iter = (_iter)->next) - - -/** - * c_list_for_each_safe() - loop over all list entries, safe for removal - * @_iter: iterator to use - * @_safe: used to store pointer to next element - * @_list: list to loop over - * - * This is a macro to use as for-loop to iterate an entire list, safe against - * removal of the current element. It is meant as convenience macro. Feel free - * to code your own loop iterator. - * - * Note that this fetches the next element prior to executing the loop body. - * This makes it safe against removal of the current entry, but it will go - * havoc if you remove other list entries. You better not modify anything but - * the current list entry. - */ -#define c_list_for_each_safe(_iter, _safe, _list) \ - for (_iter = (_list)->next, _safe = (_iter)->next; \ - (_iter) != (_list); \ - _iter = (_safe), _safe = (_safe)->next) - -/** - * c_list_for_each_entry() - loop over all list entries - * @_iter: iterator to use - * @_list: list to loop over - * @_m: member name of CList object in list type - * - * This combines c_list_for_each() with c_list_entry(), making it easy to - * iterate over a list of a specific type. - */ -#define c_list_for_each_entry(_iter, _list, _m) \ - for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m); \ - &(_iter)->_m != (_list); \ - _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m)) - -/** - * c_list_for_each_entry_safe() - loop over all list entries, safe for removal - * @_iter: iterator to use - * @_safe: used to store pointer to next element - * @_list: list to loop over - * @_m: member name of CList object in list type - * - * This combines c_list_for_each_safe() with c_list_entry(), making it easy to - * iterate over a list of a specific type. - */ -#define c_list_for_each_entry_safe(_iter, _safe, _list, _m) \ - for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m), \ - _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \ - &(_iter)->_m != (_list); \ - _iter = (_safe), \ - _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) \ - -/** * c_list_first() - return pointer to first element, or NULL if empty * @list: list to operate on, or NULL * @@ -342,6 +277,96 @@ static inline CList *c_list_last(CList *list) { c_list_entry(c_list_last(_list), _t, _m) /** + * c_list_for_each*() - iterators + * + * The c_list_for_each*() macros provide simple for-loop wrappers to iterate + * a linked list. They come in a set of flavours: + * + * - "entry": This combines c_list_entry() with the loop iterator, so the + * iterator always has the type of the surrounding object, rather + * than CList. + * + * - "safe": The loop iterator always keeps track of the next element to + * visit. This means, you can safely modify the current element, + * while retaining loop-integrity. + * You still must not touch any other entry of the list. Otherwise, + * the loop-iterator will be corrupted. + * + * - "continue": Rather than starting the iteration at the front of the list, + * use the current value of the iterator as starting position. + * Note that the first loop iteration will be the following + * element, not the given element. + * + * - "unlink": This unlinks the current element from the list before the loop + * code is run. Note that this only does a partial unlink, since + * it assumes the entire list will be unlinked. You must not + * break out of the loop, or the list will be in an inconsistent + * state. + */ + +#define c_list_for_each(_iter, _list) \ + for (_iter = (_list)->next; \ + (_iter) != (_list); \ + _iter = (_iter)->next) + +#define c_list_for_each_entry(_iter, _list, _m) \ + for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m); \ + &(_iter)->_m != (_list); \ + _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m)) + +#define c_list_for_each_safe(_iter, _safe, _list) \ + for (_iter = (_list)->next, _safe = (_iter)->next; \ + (_iter) != (_list); \ + _iter = (_safe), _safe = (_safe)->next) + +#define c_list_for_each_entry_safe(_iter, _safe, _list, _m) \ + for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m), \ + _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \ + &(_iter)->_m != (_list); \ + _iter = (_safe), \ + _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) \ + +#define c_list_for_each_continue(_iter, _list) \ + for (_iter = (_iter) ? (_iter)->next : (_list)->next; \ + (_iter) != (_list); \ + _iter = (_iter)->next) + +#define c_list_for_each_entry_continue(_iter, _list, _m) \ + for (_iter = c_list_entry((_iter) ? (_iter)->_m.next : (_list)->next, \ + __typeof__(*_iter), \ + _m); \ + &(_iter)->_m != (_list); \ + _iter = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m)) + +#define c_list_for_each_safe_continue(_iter, _safe, _list) \ + for (_iter = (_iter) ? (_iter)->next : (_list)->next, \ + _safe = (_iter)->next; \ + (_iter) != (_list); \ + _iter = (_safe), _safe = (_safe)->next) + +#define c_list_for_each_entry_safe_continue(_iter, _safe, _list, _m) \ + for (_iter = c_list_entry((_iter) ? (_iter)->_m.next : (_list)->next, \ + __typeof__(*_iter), \ + _m), \ + _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \ + &(_iter)->_m != (_list); \ + _iter = (_safe), \ + _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) \ + +#define c_list_for_each_safe_unlink(_iter, _safe, _list) \ + for (_iter = (_list)->next, _safe = (_iter)->next; \ + ((*_iter = (CList)C_LIST_INIT(*_iter)), (_iter) != (_list)); \ + _iter = (_safe), _safe = (_safe)->next) + +#define c_list_for_each_entry_safe_unlink(_iter, _safe, _list, _m) \ + for (_iter = c_list_entry((_list)->next, __typeof__(*_iter), _m), \ + _safe = c_list_entry((_iter)->_m.next, __typeof__(*_iter), _m); \ + (((_iter)->_m = (CList)C_LIST_INIT((_iter)->_m)), \ + &(_iter)->_m != (_list)); \ + _iter = (_safe), \ + _safe = c_list_entry((_safe)->_m.next, __typeof__(*_iter), _m)) \ + +/** * c_list_length() - return number of linked entries, excluding the head * @list: list to operate on * diff --git a/shared/c-siphash/src/c-siphash.c b/shared/c-siphash/src/c-siphash.c new file mode 100644 index 00000000..76b25b86 --- /dev/null +++ b/shared/c-siphash/src/c-siphash.c @@ -0,0 +1,246 @@ +/* + * SipHash Implementation + * + * For highlevel documentation of the API see the header file and the docbook + * comments. This implementation is based on the reference implementation of + * SipHash, written by Jean-Philippe Aumasson and Daniel J. Bernstein, and + * released to the Public Domain. + * + * So far, only SipHash24 is implemented, since there was no need for other + * parameters. However, adjusted c_siphash_append_X() and + * C_siphash_finalize_Y() can be easily provided, if required. + */ + +#include <stddef.h> +#include <stdint.h> +#include "c-siphash.h" + +#define _public_ __attribute__((__visibility__("default"))) + +static inline uint64_t c_siphash_read_le64(const uint8_t bytes[8]) { + return ((uint64_t) bytes[0]) | + (((uint64_t) bytes[1]) << 8) | + (((uint64_t) bytes[2]) << 16) | + (((uint64_t) bytes[3]) << 24) | + (((uint64_t) bytes[4]) << 32) | + (((uint64_t) bytes[5]) << 40) | + (((uint64_t) bytes[6]) << 48) | + (((uint64_t) bytes[7]) << 56); +} + +static inline uint64_t c_siphash_rotate_left(uint64_t x, uint8_t b) { + return (x << b) | (x >> (64 - b)); +} + +static inline void c_siphash_sipround(CSipHash *state) { + state->v0 += state->v1; + state->v1 = c_siphash_rotate_left(state->v1, 13); + state->v1 ^= state->v0; + state->v0 = c_siphash_rotate_left(state->v0, 32); + state->v2 += state->v3; + state->v3 = c_siphash_rotate_left(state->v3, 16); + state->v3 ^= state->v2; + state->v0 += state->v3; + state->v3 = c_siphash_rotate_left(state->v3, 21); + state->v3 ^= state->v0; + state->v2 += state->v1; + state->v1 = c_siphash_rotate_left(state->v1, 17); + state->v1 ^= state->v2; + state->v2 = c_siphash_rotate_left(state->v2, 32); +} + +/** + * c_siphash_init() - initialize siphash context + * @state: context object + * @seed: 128bit seed + * + * This initializes the siphash state context. Once initialized, it can be used + * to hash arbitary input. To feed data into it, use c_siphash_append(). To get + * the final hash, use c_siphash_finalize(). + * + * Note that the siphash context does not allocate state. There is no need to + * deserialize it before releasing its backing memory. + * + * The hashes generated by this context change depending on the seed. Every + * user is highly inclined to provide their unique seed. If no stable hashes + * are needed, a random seed will do fine. + * + * Right now, only SipHash24 is supported. Other SipHash parameters can be + * easily added if required. + */ +_public_ void c_siphash_init(CSipHash *state, const uint8_t seed[16]) { + uint64_t k0, k1; + + k0 = c_siphash_read_le64(seed); + k1 = c_siphash_read_le64(seed + 8); + + *state = (CSipHash) { + /* + * Default seed is taken from the reference implementation + * of SipHash24 ("somepseudorandomlygeneratedbytes"). Callers + * are still recommended to provide proper seeds themselves. + */ + .v0 = 0x736f6d6570736575ULL ^ k0, + .v1 = 0x646f72616e646f6dULL ^ k1, + .v2 = 0x6c7967656e657261ULL ^ k0, + .v3 = 0x7465646279746573ULL ^ k1, + .padding = 0, + .n_bytes = 0, + }; +} + +/** + * c_siphash_append() - hash stream of data + * @state: context object + * @bytes: array of input bytes + * @n_bytes: number of input bytes + * + * This feeds an array of bytes into the SipHash state machine. This is a + * streaming-capable API. That is, the resulting hash is the same, regardless + * of the way you chunk the input. + * This function simply feeds the given bytes into the SipHash state machine. + * It does not produce a final hash. You can call this function many times to + * append more data. To retrieve the final hash, call c_siphash_finalize(). + * + * Note that this implementation works best when used with chunk-sizes of + * multiples of 64bit (8-bytes). This is not a requirement, though. + */ +_public_ void c_siphash_append(CSipHash *state, const uint8_t *bytes, size_t n_bytes) { + const uint8_t *end = bytes + n_bytes; + size_t left = state->n_bytes & 7; + uint64_t m; + + state->n_bytes += n_bytes; + + /* + * SipHash operates on 64bit chunks. If the previous blob was not a + * multiple of 64bit in length, we must operate on single bytes. + */ + if (left > 0) { + for ( ; bytes < end && left < 8; ++bytes, ++left) + state->padding |= ((uint64_t) *bytes) << (left * 8); + + if (bytes == end && left < 8) + return; + + state->v3 ^= state->padding; + c_siphash_sipround(state); + c_siphash_sipround(state); + state->v0 ^= state->padding; + + state->padding = 0; + } + + end -= (state->n_bytes % sizeof(uint64_t)); + + /* + * We are now guaranteed to be at a 64bit state boudary. Hence, we can + * operate in 64bit chunks on all input. This is much faster than the + * one-byte-at-a-time loop. + */ + for ( ; bytes < end; bytes += 8) { + m = c_siphash_read_le64(bytes); + + state->v3 ^= m; + c_siphash_sipround(state); + c_siphash_sipround(state); + state->v0 ^= m; + } + + /* + * Now that we hashed as much 64bit chunks as possible, we need to + * remember the remaining trailing bytes. Keep them in @padding so the + * next round (or the finalizer) get access to them. + */ + left = state->n_bytes & 7; + switch (left) { + case 7: + state->padding |= ((uint64_t) bytes[6]) << 48; + /* fallthrough */ + case 6: + state->padding |= ((uint64_t) bytes[5]) << 40; + /* fallthrough */ + case 5: + state->padding |= ((uint64_t) bytes[4]) << 32; + /* fallthrough */ + case 4: + state->padding |= ((uint64_t) bytes[3]) << 24; + /* fallthrough */ + case 3: + state->padding |= ((uint64_t) bytes[2]) << 16; + /* fallthrough */ + case 2: + state->padding |= ((uint64_t) bytes[1]) << 8; + /* fallthrough */ + case 1: + state->padding |= ((uint64_t) bytes[0]); + /* fallthrough */ + case 0: + break; + } +} + +/** + * c_siphash_finalize() - finalize hash + * @state: context object + * + * This produces the final SipHash24 hash value for the given SipHash state. + * That is, it produces a hash value corresponding to the SipHash24 hash value + * of the concatenated byte-array passed into @state via c_siphash_append(). + * + * Note that @state has an invalid state after this function returns. To reuse + * it for another hash, you must call c_siphash_init() again. If you don't need + * the object, anymore, you can release it any time. There is no need to + * destroy the object explicitly. + * + * Return: 64bit hash value + */ +_public_ uint64_t c_siphash_finalize(CSipHash *state) { + uint64_t b; + + b = state->padding | (((uint64_t) state->n_bytes) << 56); + + state->v3 ^= b; + c_siphash_sipround(state); + c_siphash_sipround(state); + state->v0 ^= b; + + state->v2 ^= 0xff; + + c_siphash_sipround(state); + c_siphash_sipround(state); + c_siphash_sipround(state); + c_siphash_sipround(state); + + return state->v0 ^ state->v1 ^ state->v2 ^ state->v3; +} + +/** + * c_siphash_hash() - hash data blob + * @seed: 128bit seed + * @bytes: byte array to hash + * @n_bytes: number of bytes to hash + * + * This produces the SipHash24 hash value for the input @bytes / @n_bytes, + * using the seed provided as @seed. + * + * This is functionally equivalent to: + * + * CSipHash state; + * c_siphash_init(&state, seed); + * c_siphash_apend(&state, bytes, n_bytes); + * return c_siphash_finalize(&state); + * + * Unlike the streaming API, this is a one-shot call suitable for any data that + * is available in-memory at the same time. + * + * Return: 64bit hash value + */ +_public_ uint64_t c_siphash_hash(const uint8_t seed[16], const uint8_t *bytes, size_t n_bytes) { + CSipHash state; + + c_siphash_init(&state, seed); + c_siphash_append(&state, bytes, n_bytes); + + return c_siphash_finalize(&state); +} diff --git a/shared/c-siphash/src/c-siphash.h b/shared/c-siphash/src/c-siphash.h new file mode 100644 index 00000000..c0cfc1e0 --- /dev/null +++ b/shared/c-siphash/src/c-siphash.h @@ -0,0 +1,60 @@ +#pragma once + +/** + * Streaming-capable SipHash Implementation + * + * This library provides a SipHash API, that is fully implemented in ISO-C11 + * and has no external dependencies. The library performs no memory allocation, + * and provides a streaming API where data to be hashed can be appended + * piecemeal. + * + * A streaming-capable hash state is represented by the "CSipHash" structure, + * which should be initialized with a unique seed before use. If streaming + * capabilities are not required, c_siphash_hash() provides a simple one-shot + * API. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stddef.h> +#include <stdint.h> + +typedef struct CSipHash CSipHash; + +/** + * struct CSipHash - SipHash state object + * @v0-@v3: internal state + * @padding: pending bytes that were not a multiple of 8 + * @n_bytes: number of hashed bytes + * + * The state of an inflight hash is represenetd by a CSipHash object. Before + * hashing, it must be initialized with c_siphash_init(), providing a unique + * random hash seed. Data is hashed by appending it to the state object, using + * c_siphash_append(). Finally, the hash is read out by calling + * c_siphash_finalize(). + * + * This state object has no allocated resources. It is safe to release its + * backing memory without any further action. + */ +struct CSipHash { + uint64_t v0; + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t padding; + size_t n_bytes; +}; + +#define C_SIPHASH_NULL {} + +void c_siphash_init(CSipHash *state, const uint8_t seed[16]); +void c_siphash_append(CSipHash *state, const uint8_t *bytes, size_t n_bytes); +uint64_t c_siphash_finalize(CSipHash *state); + +uint64_t c_siphash_hash(const uint8_t seed[16], const uint8_t *bytes, size_t n_bytes); + +#ifdef __cplusplus +} +#endif diff --git a/shared/meson.build b/shared/meson.build new file mode 100644 index 00000000..a812b588 --- /dev/null +++ b/shared/meson.build @@ -0,0 +1,70 @@ +shared_c_list_dep = declare_dependency( + include_directories: include_directories('c-list/src') +) + +shared_c_siphash = static_library( + 'c-siphash', + sources: ['c-siphash/src/c-siphash.c'] +) + +shared_c_siphash_dep = declare_dependency( + include_directories: include_directories('c-siphash/src'), + link_with: shared_c_siphash +) + +shared_n_acd = static_library( + 'n-acd', + sources: ['n-acd/src/n-acd.c'], + dependencies: [ shared_c_siphash_dep, shared_c_list_dep ] +) + +shared_n_acd_dep = declare_dependency( + include_directories: include_directories('.'), + link_with: shared_n_acd, +) + +shared_inc = include_directories('.') + +version_conf = configuration_data() +version_conf.set('NM_MAJOR_VERSION', nm_major_version) +version_conf.set('NM_MINOR_VERSION', nm_minor_version) +version_conf.set('NM_MICRO_VERSION', nm_micro_version) + +version = 'nm-version-macros.h' + +version_header = configure_file( + input: version + '.in', + output: version, + configuration: version_conf +) + +shared_meta_setting = files('nm-meta-setting.c') + +shared_test_utils = files('nm-test-utils-impl.c') + +shared_siphash = files('nm-utils/siphash24.c') + +shared_udev_utils = files('nm-utils/nm-udev-utils.c') + +shared_utils = files( + 'nm-utils/nm-enum-utils.c', + 'nm-utils/nm-hash-utils.c', + 'nm-utils/nm-random-utils.c', + 'nm-utils/nm-shared-utils.c' +) + +shared_vpn_plugin_utils = files('nm-utils/nm-vpn-plugin-utils.c') + +shared_sources = shared_utils + shared_meta_setting + shared_udev_utils + files( + 'nm-utils/c-list-util.c', + 'nm-utils/nm-dedup-multi.c' +) + +shared_dep = declare_dependency( + include_directories: [ + top_inc, + shared_inc, + include_directories('nm-utils') + ], + dependencies: glib_dep +) diff --git a/shared/n-acd/src/n-acd.c b/shared/n-acd/src/n-acd.c new file mode 100644 index 00000000..ae149abb --- /dev/null +++ b/shared/n-acd/src/n-acd.c @@ -0,0 +1,1245 @@ +/* + * IPv4 Address Conflict Detection + * + * This implements the main n-acd API. It is built around an epoll-fd to + * encapsulate a timerfd+socket. The n-acd context has quite straightforward + * lifetime rules. The parameters must be set when the engine is started, and + * they can only be changed by stopping and restartding the engine. The engine + * is started on demand and stopped when no longer needed. + * During the entire lifetime the context can be dispatched. That is, the + * dispatcher does not have to be aware of the context state. After each call + * to dispatch(), the caller must pop all pending events until -EAGAIN is + * returned. + * + * If a conflict is detected, the ACD engine reports to the caller and stops + * the engine. The caller can now modify parameters and restart the engine, if + * required. + */ + +#include <assert.h> +#include <c-list.h> +#include <c-siphash.h> +#include <endian.h> +#include <errno.h> +#include <limits.h> +#include <linux/filter.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <net/ethernet.h> +#include <netinet/if_ether.h> +#include <netinet/in.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/auxv.h> +#include <sys/epoll.h> +#include <sys/socket.h> +#include <sys/timerfd.h> +#include <sys/types.h> +#include <unistd.h> +#include "n-acd.h" + +#define _public_ __attribute__((__visibility__("default"))) + +/* + * These parameters and timing intervals specified in RFC-5227. The original + * values are: + * + * PROBE_NUM 3 + * PROBE_WAIT 1s + * PROBE_MIN 1s + * PROBE_MAX 3s + * ANNOUNCE_NUM 3 + * ANNOUNCE_WAIT 2s + * ANNOUNCE_INTERVAL 2s + * MAX_CONFLICTS 10 + * RATE_LIMIT_INTERVAL 60s + * DEFEND_INTERVAL 10s + * + * If we assume a best-case and worst-case scenario for non-conflicted runs, we + * end up with a runtime between 4s and 9s to finish the probe. Then it still + * takes a fixed 4s to finish the announcements. + * + * RFC 5227 section 1.1: + * [...] (Note that the values listed here are fixed constants; they are + * not intended to be modifiable by implementers, operators, or end users. + * These constants are given symbolic names here to facilitate the writing + * of future standards that may want to reference this document with + * different values for these named constants; however, at the present time + * no such future standards exist.) [...] + * + * Unfortunately, no-one ever stepped up to write a "future standard" to revise + * the timings. A 9s timeout for successful link setups is not acceptable today. + * Hence, we will just go forward and ignore the proposed values. On both + * wired and wireless local links round-trip latencies of below 3ms are common, + * while latencies above 10ms are rarely seen. We require the caller to set a + * timeout multiplier, where 1 corresponds to a total probe time of 0.5 ms and + * 1.0 ms. On modern networks a multiplier of about 100 should be a reasonable + * default. To comply with the RFC select a multiplier of 9000. + */ +#define N_ACD_RFC_PROBE_NUM (3) +#define N_ACD_RFC_PROBE_WAIT_USEC (UINT64_C(111)) /* 111us */ +#define N_ACD_RFC_PROBE_MIN_USEC (UINT64_C(111)) /* 111us */ +#define N_ACD_RFC_PROBE_MAX_USEC (UINT64_C(333)) /* 333us */ +#define N_ACD_RFC_ANNOUNCE_NUM (3) +#define N_ACD_RFC_ANNOUNCE_WAIT_USEC (UINT64_C(222)) /* 222us */ +#define N_ACD_RFC_ANNOUNCE_INTERVAL_USEC (UINT64_C(222)) /* 222us */ +#define N_ACD_RFC_MAX_CONFLICTS (10) +#define N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC (UINT64_C(60000000)) /* 60s */ +#define N_ACD_RFC_DEFEND_INTERVAL_USEC (UINT64_C(10000000)) /* 10s */ + +/* + * Fake ENETDOWN error-code. We use this as replacement for known EFOOBAR error + * codes. It is explicitly chosen to be outside the known error-code range. + * Whenever we are deep down in a call-stack and notice a ENETDOWN error, we + * return this instead. It is caught by the top-level dispatcher and then + * properly handled. + * This avoids gracefully handling ENETDOWN in call-stacks, but then continuing + * with some work in the callers without noticing the soft failure. + */ +#define N_ACD_E_DOWN (INT_MAX) + +#define TIME_INFINITY ((uint64_t) -1) + +enum { + N_ACD_EPOLL_TIMER, + N_ACD_EPOLL_SOCKET, +}; + +enum { + N_ACD_STATE_INIT, + N_ACD_STATE_PROBING, + N_ACD_STATE_CONFIGURING, + N_ACD_STATE_ANNOUNCING, +}; + +typedef struct NAcdEventNode { + NAcdEvent event; + uint8_t sender[ETH_ALEN]; + CList link; +} NAcdEventNode; + +struct NAcd { + /* context */ + unsigned int seed; + int fd_epoll; + int fd_timer; + + /* configuration */ + NAcdConfig config; + uint8_t mac[ETH_ALEN]; + uint64_t timeout_multiplier; + + /* runtime */ + int fd_socket; + unsigned int state; + unsigned int n_iteration; + unsigned int n_conflicts; + unsigned int defend; + uint64_t last_defend; + uint64_t last_conflict; + + /* pending events */ + CList events; + NAcdEventNode *current; +}; + +static int n_acd_errno(void) { + /* + * Compilers continuously warn about uninitialized variables since they + * cannot deduce that `return -errno;` will always be negative. This + * small wrapper makes sure compilers figure that out. Use it as + * replacement for `errno` read access. Yes, it generates worse code, + * but only marginally and only affects slow-paths. + */ + return abs(errno) ? : EIO; +} + +static int n_acd_event_node_new(NAcdEventNode **nodep, unsigned int event) { + NAcdEventNode *node; + + node = calloc(1, sizeof(*node)); + if (!node) + return -ENOMEM; + + node->event.event = event; + node->link = (CList)C_LIST_INIT(node->link); + + *nodep = node; + + return 0; +} + +static NAcdEventNode *n_acd_event_node_free(NAcdEventNode *node) { + if (!node) + return NULL; + + c_list_unlink(&node->link); + free(node); + + return NULL; +} + +static int n_acd_get_random(unsigned int *random) { + uint8_t hash_seed[] = { 0x3a, 0x0c, 0xa6, 0xdd, 0x44, 0xef, 0x5f, 0x7a, 0x5e, 0xd7, 0x25, 0x37, 0xbf, 0x4e, 0x80, 0xa1 }; + CSipHash hash = C_SIPHASH_NULL; + struct timespec ts; + const uint8_t *p; + int r; + + /* + * We need random jitter for all timeouts when handling ARP probes. Use + * AT_RANDOM to get a seed for rand_r(3p), if available (should always + * be available on linux). See the time-out scheduler for details. + * Additionally, we include the current time in the seed. This avoids + * using the same jitter in case you run multiple ACD engines in the + * same process. Lastly, the seed is hashed with SipHash24 to avoid + * exposing the value of AT_RANDOM on the network. + */ + c_siphash_init(&hash, hash_seed); + + p = (const uint8_t *)getauxval(AT_RANDOM); + if (p) + c_siphash_append(&hash, p, 16); + + r = clock_gettime(CLOCK_BOOTTIME, &ts); + if (r < 0) + return -n_acd_errno(); + + c_siphash_append(&hash, (const uint8_t *)&ts.tv_sec, sizeof(ts.tv_sec)); + c_siphash_append(&hash, (const uint8_t *)&ts.tv_nsec, sizeof(ts.tv_nsec)); + + *random = c_siphash_finalize(&hash); + return 0; +} + +static void n_acd_reset(NAcd *acd) { + acd->state = N_ACD_STATE_INIT; + acd->defend = N_ACD_DEFEND_NEVER; + acd->n_iteration = 0; + acd->last_defend = 0; + timerfd_settime(acd->fd_timer, 0, &(struct itimerspec){}, NULL); + + if (acd->fd_socket >= 0) { + assert(acd->fd_epoll >= 0); + epoll_ctl(acd->fd_epoll, EPOLL_CTL_DEL, acd->fd_socket, NULL); + close(acd->fd_socket); + acd->fd_socket = -1; + } +} + +/** + * n_acd_new() - create a new ACD context + * @acdp: output argument for context + * + * Create a new ACD context and return it in @acdp. + * + * Return: 0 on success, or a negative error code on failure. + */ +_public_ int n_acd_new(NAcd **acdp) { + NAcd *acd; + int r; + + acd = calloc(1, sizeof(*acd)); + if (!acd) + return -ENOMEM; + + acd->fd_epoll = -1; + acd->fd_timer = -1; + acd->fd_socket = -1; + acd->state = N_ACD_STATE_INIT; + acd->defend = N_ACD_DEFEND_NEVER; + acd->events = (CList)C_LIST_INIT(acd->events); + acd->last_conflict = TIME_INFINITY; + + r = n_acd_get_random(&acd->seed); + if (r < 0) + return r; + + acd->fd_epoll = epoll_create1(EPOLL_CLOEXEC); + if (acd->fd_epoll < 0) { + r = -n_acd_errno(); + goto error; + } + + acd->fd_timer = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK); + if (acd->fd_timer < 0 && errno == EINVAL) { + /* + * Fall back to CLOCK_MONOTONIC when CLOCK_BOOTTIME is + * not available (kernel < 3.15). + */ + acd->fd_timer = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); + } + if (acd->fd_timer < 0) { + r = -n_acd_errno(); + goto error; + } + + r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->fd_timer, + &(struct epoll_event){ + .events = EPOLLIN, + .data.u32 = N_ACD_EPOLL_TIMER, + }); + if (r < 0) { + r = -n_acd_errno(); + goto error; + } + + *acdp = acd; + return 0; + +error: + n_acd_free(acd); + return r; +} + +/** + * n_acd_free() - free an ACD context + * + * Frees all resources held by the context. This may be called at any time, + * but doing so invalidates all data owned by the context. + * + * Return: NULL. + */ +_public_ void n_acd_free(NAcd *acd) { + NAcdEventNode *node; + + if (!acd) + return; + + n_acd_reset(acd); + + acd->current = n_acd_event_node_free(acd->current); + + while ((node = c_list_first_entry(&acd->events, NAcdEventNode, link))) + n_acd_event_node_free(node); + + assert(acd->fd_socket < 0); + + if (acd->fd_timer >= 0) { + assert(acd->fd_epoll >= 0); + epoll_ctl(acd->fd_epoll, EPOLL_CTL_DEL, acd->fd_timer, NULL); + close(acd->fd_timer); + acd->fd_timer = -1; + } + + if (acd->fd_epoll >= 0) { + close(acd->fd_epoll); + acd->fd_epoll = -1; + } + + free(acd); +} + +/** + * n_acd_get_fd() - get pollable file descriptor + * @acd: ACD context + * @fdp: output argument for file descriptor + * + * Returns a file descriptor in @fdp. This filedescriptor can be polled by + * the caller to indicate when the ACD context can be dispatched. + */ +_public_ void n_acd_get_fd(NAcd *acd, int *fdp) { + *fdp = acd->fd_epoll; +} + +static int n_acd_push_event(NAcd *acd, unsigned int event, uint16_t *operation, uint8_t (*sender)[6], uint8_t (*target)[4]) { + NAcdEventNode *node; + int r; + + r = n_acd_event_node_new(&node, event); + if (r < 0) + return r; + + switch (event) { + case N_ACD_EVENT_USED: + node->event.used.operation = be16toh(*operation); + memcpy(node->sender, sender, sizeof(node->sender)); + node->event.used.sender = node->sender; + node->event.used.n_sender = sizeof(node->sender); + memcpy(&node->event.used.target, target, sizeof(node->event.used.target)); + break; + case N_ACD_EVENT_CONFLICT: + node->event.conflict.operation = be16toh(*operation); + memcpy(node->sender, sender, sizeof(node->sender)); + node->event.used.sender = node->sender; + node->event.used.n_sender = sizeof(node->sender); + memcpy(&node->event.conflict.target, target, sizeof(node->event.conflict.target)); + break; + case N_ACD_EVENT_DEFENDED: + node->event.defended.operation = be16toh(*operation); + memcpy(node->sender, sender, sizeof(node->sender)); + node->event.used.sender = node->sender; + node->event.used.n_sender = sizeof(node->sender); + memcpy(&node->event.defended.target, target, sizeof(node->event.defended.target)); + break; + case N_ACD_EVENT_READY: + case N_ACD_EVENT_DOWN: + break; + default: + assert(0); + } + + c_list_link_tail(&acd->events, &node->link); + + return 0; +} + +static int n_acd_now(uint64_t *nowp) { + struct timespec ts; + int r; + + r = clock_gettime(CLOCK_BOOTTIME, &ts); + if (r < 0) + return -n_acd_errno(); + + *nowp = ts.tv_sec * UINT64_C(1000000) + ts.tv_nsec / UINT64_C(1000); + return 0; +} + +static int n_acd_schedule(NAcd *acd, uint64_t u_timeout, unsigned int u_jitter) { + uint64_t u_next = u_timeout; + int r; + + /* + * ACD specifies jitter values to reduce packet storms on the local + * link. This call accepts the maximum relative jitter value in + * microseconds as @u_jitter. We then use rand_r(3p) to get a + * pseudo-random jitter on top of the real timeout given as @u_timeout. + * Note that rand_r() is fine for this. Before you try to improve the + * RNG, you better spend some time securing ARP. + */ + if (u_jitter) + u_next += rand_r(&acd->seed) % u_jitter; + + /* + * Setting .it_value to 0 in timerfd_settime() disarms the timer. Avoid + * this and always schedule at least 1us. Otherwise, we'd have to + * recursively call into the time-out handler, which we really want to + * avoid. No reason to optimize performance here. + */ + if (!u_next) + u_next = 1; + + r = timerfd_settime(acd->fd_timer, 0, + &(struct itimerspec){ .it_value = { + .tv_sec = u_next / UINT64_C(1000000), + .tv_nsec = u_next % UINT64_C(1000000) * UINT64_C(1000), + } }, NULL); + if (r < 0) + return -n_acd_errno(); + + return 0; +} + +static int n_acd_send(NAcd *acd, const struct in_addr *spa) { + struct sockaddr_ll address = { + .sll_family = AF_PACKET, + .sll_protocol = htobe16(ETH_P_ARP), + .sll_ifindex = acd->config.ifindex, + .sll_halen = ETH_ALEN, + .sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + struct ether_arp arp = { + .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), + .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), + .ea_hdr.ar_hln = sizeof(acd->mac), + .ea_hdr.ar_pln = sizeof(uint32_t), + .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), + }; + ssize_t l; + + memcpy(arp.arp_sha, acd->mac, sizeof(acd->mac)); + memcpy(arp.arp_tpa, &acd->config.ip.s_addr, sizeof(uint32_t)); + + if (spa) + memcpy(arp.arp_spa, &spa->s_addr, sizeof(spa->s_addr)); + + l = sendto(acd->fd_socket, &arp, sizeof(arp), MSG_NOSIGNAL, (struct sockaddr *)&address, sizeof(address)); + if (l == (ssize_t)sizeof(arp)) { + /* Packet was properly sent. */ + return 0; + } else if (l >= 0) { + /* + * Ugh. The packet was truncated. This should not happen, but + * lets just pretend the packet was dropped. + */ + return 0; + } else if (errno == EAGAIN || errno == ENOBUFS) { + /* + * In case the output buffer is full, the packet is silently + * dropped. This is just as if the physical layer happened to + * drop the packet. We are not on a reliable medium, so no + * reason to pretend we are. + */ + return 0; + } else if (errno == ENETDOWN || errno == ENXIO) { + /* + * We get ENETDOWN if the network-device goes down or is + * removed. ENXIO might happen on async send-operations if the + * network-device was unplugged and thus the kernel is no + * longer aware of it. + * In any case, we do not allow proceeding with this socket. We + * stop the engine and notify the user gracefully. + */ + return -N_ACD_E_DOWN; + } + + return -n_acd_errno(); +} + +static void n_acd_remember_conflict(NAcd *acd, uint64_t now) { + if (++acd->n_conflicts >= N_ACD_RFC_MAX_CONFLICTS) { + acd->n_conflicts = N_ACD_RFC_MAX_CONFLICTS; + acd->last_conflict = now; + } +} + +static int n_acd_handle_timeout(NAcd *acd) { + int r; + + switch (acd->state) { + case N_ACD_STATE_PROBING: + /* + * We are still PROBING. We send 3 probes with a random timeout + * scheduled between each. If, after a fixed timeout, we did + * not receive any conflict we consider the probing successful. + */ + if (acd->n_iteration >= N_ACD_RFC_PROBE_NUM) { + /* + * All 3 probes succeeded and we waited enough to + * consider this address usable by now. Do not announce + * the address, yet. We must first give the caller a + * chance to configure the address (so they can answer + * ARP requests), before announcing it. But our + * callbacks are not necessarily synchronous (we want + * to allow IPC there), so just notify the caller and + * wait for further instructions, thus effectively + * increasing the probe-wait. + */ + r = n_acd_push_event(acd, N_ACD_EVENT_READY, NULL, NULL, NULL); + if (r) + return r; + + acd->state = N_ACD_STATE_CONFIGURING; + } else { + /* + * We have not sent all 3 probes, yet. A timer fired, + * so we are ready to send the next probe. If this is + * the third probe, schedule a timer for ANNOUNCE_WAIT + * to give other peers a chance to answer. If this is + * not the third probe, wait between PROBE_MIN and + * PROBE_MAX for the next probe. + */ + + r = n_acd_send(acd, NULL); + if (r < 0) + return r; + + if (++acd->n_iteration >= N_ACD_RFC_PROBE_NUM) + r = n_acd_schedule(acd, acd->timeout_multiplier * N_ACD_RFC_ANNOUNCE_WAIT_USEC, 0); + else + r = n_acd_schedule(acd, acd->timeout_multiplier * N_ACD_RFC_PROBE_MIN_USEC, + acd->timeout_multiplier * (N_ACD_RFC_PROBE_MAX_USEC - N_ACD_RFC_PROBE_MIN_USEC)); + if (r < 0) + return r; + } + + break; + + case N_ACD_STATE_ANNOUNCING: + /* + * We are ANNOUNCING, meaning the caller configured the address + * on the interface and is actively using it. We send 3 + * announcements out, in a short interval, and then just + * perform passive conflict detection. + * Note that once all 3 announcements are sent, we no longer + * schedule a timer, so this part should not trigger, anymore. + */ + + r = n_acd_send(acd, &acd->config.ip); + if (r < 0) + return r; + + if (++acd->n_iteration < N_ACD_RFC_ANNOUNCE_NUM) { + r = n_acd_schedule(acd, acd->timeout_multiplier * N_ACD_RFC_ANNOUNCE_INTERVAL_USEC, 0); + if (r < 0) + return r; + } + + break; + + case N_ACD_STATE_INIT: + case N_ACD_STATE_CONFIGURING: + default: + /* + * There are no timeouts in these states. If we trigger one, + * something is fishy. Let the caller deal with this. + */ + return -EIO; + } + + return 0; +} + +static int n_acd_handle_packet(NAcd *acd, struct ether_arp *packet) { + bool hard_conflict; + uint64_t now; + int r; + + /* + * Via BPF we discard any non-conflict packets. There are only 2 types + * that can pass: A conflict on the Sender Protocol Address, or a + * conflict on the Target Protocol Address. + * + * The former we call a hard-conflict. It implies that the sender uses + * the address already. We must always catch this and in some way react + * to it. Any kind, REQUEST or REPLY must be caught (though it is + * unlikely that we ever catch REPLIES since they tend to be unicasts). + * + * However, in case the Target Protocol Address matches, we just know + * that somebody is looking for the address. Hence, we must also check + * that the packet is an ARP-Probe (Sender Protocol Address is 0). If + * it is, it means someone else does ACD on our address. We call this a + * soft conflict. + */ + if (!memcmp(packet->arp_spa, (uint8_t[4]){ }, sizeof(packet->arp_spa)) && + !memcmp(packet->arp_tpa, &acd->config.ip.s_addr, sizeof(packet->arp_tpa)) && + packet->ea_hdr.ar_op == htobe16(ARPOP_REQUEST)) { + hard_conflict = false; + } else if (!memcmp(packet->arp_spa, &acd->config.ip.s_addr, sizeof(packet->arp_spa))) { + hard_conflict = true; + } else { + /* + * Ignore anything that is specific enough to match the BPF + * filter, but is none of the conflicts described above. + */ + return 0; + } + + r = n_acd_now(&now); + if (r < 0) + return r; + + switch (acd->state) { + case N_ACD_STATE_PROBING: + /* + * Regardless whether this is a hard or soft conflict, we must + * treat this as a probe failure. That is, notify the caller of + * the conflict and wait for further instructions. We do not + * react to this, until the caller tells us what to do. But we + * immediately stop the engine, since there is no point in + * continuing the probing. + */ + n_acd_remember_conflict(acd, now); + n_acd_reset(acd); + r = n_acd_push_event(acd, N_ACD_EVENT_USED, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa); + if (r) + return r; + + break; + + case N_ACD_STATE_CONFIGURING: + /* + * We are waiting for the caller to configure the interface and + * start ANNOUNCING. In this state, we cannot defend the address + * as that would indicate that it is ready to be used, and we + * cannot signal CONFLICT or USED as the caller may already have + * started to use the address (and may have configured the engine + * to always defend it, which means they should be able to rely on + * never losing it after READY). Simply drop the event, and rely + * on the anticipated ANNOUNCE to trigger it again. + */ + + break; + + case N_ACD_STATE_ANNOUNCING: + /* + * We were already instructed to announce the address, which + * means the address is configured and in use. Hence, the + * caller is responsible to serve regular ARP queries. Meaning, + * we can ignore any soft conflicts (other peers doing ACD). + * + * But if we see a hard-conflict, we either defend the address + * according to the caller's instructions, or we report the + * conflict and bail out. + */ + + if (!hard_conflict) + break; + + if (acd->defend == N_ACD_DEFEND_NEVER) { + n_acd_remember_conflict(acd, now); + n_acd_reset(acd); + r = n_acd_push_event(acd, N_ACD_EVENT_CONFLICT, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa); + if (r) + return r; + } else { + if (now > acd->last_defend + N_ACD_RFC_DEFEND_INTERVAL_USEC) { + r = n_acd_send(acd, &acd->config.ip); + if (r < 0) + return r; + + acd->last_defend = now; + r = n_acd_push_event(acd, N_ACD_EVENT_DEFENDED, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa); + if (r) + return r; + } else if (acd->defend == N_ACD_DEFEND_ONCE) { + n_acd_remember_conflict(acd, now); + n_acd_reset(acd); + r = n_acd_push_event(acd, N_ACD_EVENT_CONFLICT, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa); + if (r) + return r; + } else { + r = n_acd_push_event(acd, N_ACD_EVENT_DEFENDED, &packet->ea_hdr.ar_op, &packet->arp_sha, &packet->arp_tpa); + if (r) + return r; + } + } + + break; + + case N_ACD_STATE_INIT: + default: + /* + * The socket should not be dispatched in those states, since + * it is neither allocated nor added to epoll. Fail hard if we + * trigger this somehow. + */ + return -EIO; + } + + return 0; +} + +static int n_acd_dispatch_timer(NAcd *acd, struct epoll_event *event) { + uint64_t v; + int r; + + if (event->events & (EPOLLHUP | EPOLLERR)) { + /* + * There is no way to handle either gracefully. If we ignored + * them, we would busy-loop, so lets rather forward the error + * to the caller. + */ + return -EIO; + } + + if (event->events & EPOLLIN) { + for (unsigned int i = 0; i < 128; ++i) { + r = read(acd->fd_timer, &v, sizeof(v)); + if (r == sizeof(v)) { + /* + * We successfully read a timer-value. Handle it and + * return. We do NOT fall-through to EPOLLHUP handling, + * as we always must drain buffers first. + */ + return n_acd_handle_timeout(acd); + } else if (r >= 0) { + /* + * Kernel guarantees 8-byte reads; fail hard if it + * suddenly starts doing weird shit. No clue what to do + * with those values, anyway. + */ + return -EIO; + } else if (errno == EAGAIN) { + /* + * No more pending events. + */ + return 0; + } else { + /* + * Something failed. We use CLOCK_BOOTTIME, so + * ECANCELED cannot happen. Hence, there is no error + * that we could gracefully handle. Fail hard and let + * the caller deal with it. + */ + return -n_acd_errno(); + } + } + + return N_ACD_E_PREEMPTED; + } + + return 0; +} + +static int n_acd_dispatch_socket(NAcd *acd, struct epoll_event *event) { + struct ether_arp packet; + ssize_t l; + + for (unsigned int i = 0; i < 128; ++i) { + /* + * Regardless whether EPOLLIN is set in @event->events, we always + * invoke recv(2). This is a safety-net for sockets, which always fetch + * queued errors on all syscalls. That means, if anything failed on the + * socket, we will be notified via recv(2). This simplifies the code + * and avoid magic EPOLLIN/ERR/HUP juggling. + * + * Note that we must use recv(2) over read(2), since the latter cannot + * deal with empty packets properly. + * + * We explicitly skip passing MSG_TRUNC here. We *WANT* + * overlong packets to be retrieved and truncated. Ethernet + * frames might not have byte-granular lengths. Real hardware + * does add trailing padding/garbage, so we must discard this + * here. + */ + l = recv(acd->fd_socket, &packet, sizeof(packet), 0); + if (l == (ssize_t)sizeof(packet)) { + /* + * We read a full ARP packet. We never fall-through to EPOLLHUP + * handling, as we always must drain buffers first. + */ + return n_acd_handle_packet(acd, &packet); + } else if (l >= 0) { + /* + * The BPF filter discards short packets, so error out + * if something slips through for any reason. Don't silently + * ignore it, since we explicitly want to know if something + * went fishy. + */ + return -EIO; + } else if (errno == ENETDOWN || errno == ENXIO) { + /* + * We get ENETDOWN if the network-device goes down or is + * removed. ENXIO might happen on async send-operations if the + * network-device was unplugged and thus the kernel is no + * longer aware of it. + * In any case, we do not allow proceeding with this socket. We + * stop the engine and notify the user gracefully. + */ + return -N_ACD_E_DOWN; + } else if (errno == EAGAIN) { + /* + * We cannot read data from the socket (we got EAGAIN). As a safety net + * check for EPOLLHUP/ERR. Those cannot be disabled with epoll, so we + * must make sure to not busy-loop by ignoring them. Note that we know + * recv(2) on sockets to return an error if either of these epoll-flags + * is set. Hence, if we did not handle it above, we have no other way + * but treating those flags as fatal errors and returning them to the + * caller. + */ + if (event->events & (EPOLLHUP | EPOLLERR)) + return -EIO; + + return 0; + } else { + /* + * Cannot dispatch the packet. This might be due to OOM, HUP, + * or something else. We cannot handle it gracefully so forward + * to the caller. + */ + return -n_acd_errno(); + } + } + + return N_ACD_E_PREEMPTED; +} + +/** + * n_acd_dispatch() - dispatch ACD context + * @acd: ACD context + * + * Return: 0 on successful dispatch of all pending events, N_ACD_E_PREEMPT in + * case there are more still more events to be dispatched, or a + * negative error code on failure. + */ +_public_ int n_acd_dispatch(NAcd *acd) { + struct epoll_event events[2]; + int n, i, r = 0; + bool preempted = false; + + n = epoll_wait(acd->fd_epoll, events, sizeof(events) / sizeof(*events), 0); + if (n < 0) { + return -n_acd_errno(); + } + + for (i = 0; i < n; ++i) { + switch (events[i].data.u32) { + case N_ACD_EPOLL_TIMER: + r = n_acd_dispatch_timer(acd, events + i); + break; + case N_ACD_EPOLL_SOCKET: + r = n_acd_dispatch_socket(acd, events + i); + break; + default: + r = 0; + break; + } + + if (r == N_ACD_E_PREEMPTED) + preempted = true; + else if (r != 0) + break; + } + + if (r == -N_ACD_E_DOWN) { + /* + * N_ACD_E_DOWN is synthesized whenever we notice + * ENETDOWN-related errors on the network interface. This + * allows bailing out of deep call-paths and then handling the + * error gracefully here. + */ + n_acd_reset(acd); + r = n_acd_push_event(acd, N_ACD_EVENT_DOWN, NULL, NULL, NULL); + if (r) + return r; + + return 0; + } + + if (preempted) + return N_ACD_E_PREEMPTED; + else + return r; +} + +/** + * n_acd_pop_event() - get the next pending event + * @acd: ACD context + * @eventp: output argument for the event + * + * Returns a pointer to the next pending event. The event is still owend by + * the context, and is only valid until the next call to n_acd_pop_event() + * or until the context is freed. + * + * The possible events are: + * * N_ACD_EVENT_READY: The configured IP address was probed successfully + * and is ready to be used. Once configured on the + * interface, the caller must call n_acd_announce() + * to announce and start defending the address. + * No further events may be received before + * n_acd_announce() has been called. + * * N_ACD_EVENT_USED: Someone is already using the IP address being + * probed. The engine was stopped, and the caller + * may restart it to try again. + * * N_ACD_EVENT_DEFENDED: A conflict was detected for the announced IP + * address, and the engine attempted to defend it. + * This is purely informational, and no action is + * required by the caller. + * * N_ACD_EVENT_CONFLICT: A conflict was detected for the announced IP + * address, and the engine was not able to defend + * it (according to the configured policy). The + * engine has stoppde, the caller must stop using + * the address immediately, and may restart the + * engine to retry. + * * N_ACD_EVENT_DOWN: A network error was detected. The engine was + * stopped and it is the responsibility of the + * caller to restart it once the network may be + * functional again. + * + * Returns: 0 on success, N_ACD_E_STOPPED if there are no more events and + * the engine has been stopped, N_ACD_E_DONE if there are no more + * events, but the engine is still running, or a negative error + * code on failure. + */ +_public_ int n_acd_pop_event(NAcd *acd, NAcdEvent **eventp) { + acd->current = n_acd_event_node_free(acd->current); + + if (c_list_is_empty(&acd->events)) { + if (acd->state == N_ACD_STATE_INIT) + return N_ACD_E_STOPPED; + else + return N_ACD_E_DONE; + } + + acd->current = c_list_first_entry(&acd->events, NAcdEventNode, link); + c_list_unlink(&acd->current->link); + + if (eventp) + *eventp = &acd->current->event; + + return 0; +} + +static int n_acd_bind_socket(NAcd *acd, int s) { + /* + * Due to strict aliasing, we cannot get uint32_t/uint16_t pointers to + * acd->config.mac, so provide a union accessor. + */ + const union { + uint8_t u8[6]; + uint16_t u16[3]; + uint32_t u32[1]; + } mac = { + .u8 = { + acd->mac[0], + acd->mac[1], + acd->mac[2], + acd->mac[3], + acd->mac[4], + acd->mac[5], + }, + }; + struct sock_filter filter[] = { + /* + * Basic ARP header validation. Make sure the packet-length, + * wire type, protocol type, and address lengths are correct. + */ + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* #packet >= #arp-packet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)), /* A <- hardware address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0), /* length == sizeof(ether_addr)? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)), /* A <- protocol address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0), /* length == sizeof(in_addr) ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), /* protocol == request ? */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0), /* protocol == reply ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* + * Sender hardware address must be different from ours. Note + * that BPF runs in big-endian mode, but assumes immediates are + * given in native-endian. This might look weird on 6-byte mac + * addresses, but is needed to revert the BPF magic. + */ + BPF_STMT(BPF_LD + BPF_IMM, be32toh(mac.u32[0])), /* A <- 4 bytes of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)), /* A <- 4 bytes of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6), /* A == 0 ? */ + BPF_STMT(BPF_LD + BPF_IMM, be16toh(mac.u16[2])), /* A <- remainder of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4), /* A <- remainder of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* + * Sender protocol address or target protocol address must be + * equal to the one we care about. Again, immediates must be + * given in native-endian. + */ + BPF_STMT(BPF_LD + BPF_IMM, be32toh(acd->config.ip.s_addr)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_LD + BPF_IMM, be32toh(acd->config.ip.s_addr)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + }; + const struct sock_fprog fprog = { + .len = sizeof(filter) / sizeof(*filter), + .filter = filter, + }; + const struct sockaddr_ll address = { + .sll_family = AF_PACKET, + .sll_protocol = htobe16(ETH_P_ARP), + .sll_ifindex = acd->config.ifindex, + .sll_halen = ETH_ALEN, + .sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + int r; + + /* + * Install a packet filter that matches on the ARP header and + * addresses, to reduce the number of wake-ups to a minimum. + */ + r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -n_acd_errno(); + + /* + * Bind the packet-socket to ETH_P_ARP and the specified network + * interface. + */ + r = bind(s, (struct sockaddr *)&address, sizeof(address)); + if (r < 0) + return -n_acd_errno(); + + return 0; +} + +static int n_acd_setup_socket(NAcd *acd) { + int r, s; + + s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -n_acd_errno(); + + r = n_acd_bind_socket(acd, s); + if (r < 0) + goto error; + + r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, s, + &(struct epoll_event){ + .events = EPOLLIN, + .data.u32 = N_ACD_EPOLL_SOCKET, + }); + if (r < 0) { + r = -n_acd_errno(); + goto error; + } + + acd->fd_socket = s; + return 0; + +error: + close(s); + return r; +} + +/** + * n_acd_start() - start the ACD engine + * @acd: ACD context + * @config: description of interface and desired IP address + * + * Start probing the given address on the given interface. + * + * The engine must not already be running, and there must not be + * any pending events. + * + * Returns: 0 on success, N_ACD_E_INVALID_ARGUMENT in case the configuration + * was invalid, N_ACD_E_BUSY if the engine is running or there are + * pending events, or a negative error code on failure. + */ +_public_ int n_acd_start(NAcd *acd, NAcdConfig *config) { + uint64_t now, delay; + int r; + + if (config->ifindex <= 0 || + config->transport != N_ACD_TRANSPORT_ETHERNET || + config->n_mac != ETH_ALEN || + !memcmp(config->mac, (uint8_t[ETH_ALEN]){ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, ETH_ALEN) || + !config->ip.s_addr) + return N_ACD_E_INVALID_ARGUMENT; + + if (acd->state != N_ACD_STATE_INIT || !c_list_is_empty(&acd->events)) + return N_ACD_E_BUSY; + + acd->config = *config; + memcpy(acd->mac, config->mac, config->n_mac); + acd->config.mac = acd->mac; + acd->timeout_multiplier = config->timeout_msec; + + r = n_acd_setup_socket(acd); + if (r < 0) + goto error; + + if (acd->timeout_multiplier) { + delay = 0; + acd->n_iteration = 0; + + if (acd->last_conflict != TIME_INFINITY) { + r = n_acd_now(&now); + if (r < 0) + goto error; + + if (now < acd->last_conflict + N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC) + delay = acd->last_conflict + N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC - now; + } + + r = n_acd_schedule(acd, delay, acd->timeout_multiplier * N_ACD_RFC_PROBE_WAIT_USEC); + if (r < 0) + goto error; + } else { + /* + * A zero timeout means we drop the probing alltogether, and behave as if + * the last probe succeeded immediately. + */ + acd->n_iteration = N_ACD_RFC_PROBE_NUM; + + r = n_acd_schedule(acd, 0, 0); + if (r < 0) + goto error; + } + + acd->state = N_ACD_STATE_PROBING; + acd->defend = N_ACD_DEFEND_NEVER; + acd->last_defend = 0; + return 0; + +error: + n_acd_reset(acd); + return r; +} + +/** + * n_acd_stop() - stop the ACD engine + * @acd: ACD context + * + * Stop the engine. No new events may be triggered, but pending events are not + * flushed. Before calling n_acd_start() again all pending events must be popped. + * + * Return: 0 on success, negative error code on failure. + */ +_public_ int n_acd_stop(NAcd *acd) { + n_acd_reset(acd); + return 0; +} + +/** + * n_acd_announce() - announce the configured IP address + * @acd: ACD context + * @defend: defence policy + * + * Announce the IP address on the local link, and start defending it according + * to the given policy, which mut be one of N_ACD_DEFEND_ONCE, + * N_ACD_DEFEND_NEVER, or N_ACD_DEFEND_ALWAYS. + * + * This must be called after the engine in response to an N_ACD_EVENT_READY + * event, and only after the given address has been configured on the given + * interface. + * + * Return: 0 on success, N_ACD_E_INVALID_ARGUMENT in case the defence policy + * is invalid, N_ACD_E_BUSY if this is not in response to a + * N_ACD_EVENT_READY event, or a negative error code on failure. + */ +_public_ int n_acd_announce(NAcd *acd, unsigned int defend) { + uint64_t now; + int r; + + if (defend >= _N_ACD_DEFEND_N) + return N_ACD_E_INVALID_ARGUMENT; + if (acd->state != N_ACD_STATE_CONFIGURING) + return N_ACD_E_BUSY; + + /* + * Sending announcements means we finished probing and use the address + * now. We therefore reset the conflict counter in case we adhered to + * the rate-limit. Since probing is properly delayed, a well-behaving + * client will always reset the conflict counter here. However, if you + * force-use an address regardless of conflicts, then this will not + * trigger and the conflict counter stays untouched. + */ + if (acd->last_conflict != TIME_INFINITY) { + r = n_acd_now(&now); + if (r < 0) + return r; + + if (now >= acd->last_conflict + N_ACD_RFC_RATE_LIMIT_INTERVAL_USEC) + acd->n_conflicts = 0; + } + + /* + * Instead of sending the first announcement here, we schedule an idle + * timer. This avoids possibly recursing into the user callback. We + * should never trigger callbacks from arbitrary stacks, but always + * restrict them to the dispatcher. + */ + r = n_acd_schedule(acd, 0, 0); + if (r < 0) + return r; + + acd->state = N_ACD_STATE_ANNOUNCING; + acd->defend = defend; + acd->n_iteration = 0; + return 0; +} diff --git a/shared/n-acd/src/n-acd.h b/shared/n-acd/src/n-acd.h new file mode 100644 index 00000000..46394dca --- /dev/null +++ b/shared/n-acd/src/n-acd.h @@ -0,0 +1,94 @@ +#pragma once + +/* + * IPv4 Address Conflict Detection + * + * This is the public header of the n-acd library, implementing IPv4 Address + * Conflict Detection as described in RFC-5227. This header defines the public + * API and all entry points of n-acd. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <netinet/in.h> +#include <stdbool.h> + +enum { + _N_ACD_E_SUCCESS, + + N_ACD_E_DONE, + N_ACD_E_STOPPED, + N_ACD_E_PREEMPTED, + + N_ACD_E_INVALID_ARGUMENT, + N_ACD_E_BUSY, +}; + +typedef struct NAcd NAcd; + +typedef struct NAcdConfig { + int ifindex; + unsigned int transport; + const uint8_t *mac; + size_t n_mac; + struct in_addr ip; + uint64_t timeout_msec; +} NAcdConfig; + +typedef struct NAcdEvent { + unsigned int event; + union { + struct { + } ready, down; + struct { + uint16_t operation; + uint8_t *sender; + size_t n_sender; + struct in_addr target; + } used, defended, conflict; + }; +} NAcdEvent; + +enum { + N_ACD_TRANSPORT_ETHERNET, + _N_ACD_TRANSPORT_N, +}; + +enum { + N_ACD_EVENT_READY, + N_ACD_EVENT_USED, + N_ACD_EVENT_DEFENDED, + N_ACD_EVENT_CONFLICT, + N_ACD_EVENT_DOWN, + _N_ACD_EVENT_N, +}; + +enum { + N_ACD_DEFEND_NEVER, + N_ACD_DEFEND_ONCE, + N_ACD_DEFEND_ALWAYS, + _N_ACD_DEFEND_N, +}; + +int n_acd_new(NAcd **acdp); +void n_acd_free(NAcd *acd); + +void n_acd_get_fd(NAcd *acd, int *fdp); + +int n_acd_dispatch(NAcd *acd); +int n_acd_pop_event(NAcd *acd, NAcdEvent **eventp); +int n_acd_announce(NAcd *acd, unsigned int defend); + +int n_acd_start(NAcd *acd, NAcdConfig *config); +int n_acd_stop(NAcd *acd); + +static inline void n_acd_freep(NAcd **acd) { + if (*acd) + n_acd_free(*acd); +} + +#ifdef __cplusplus +} +#endif diff --git a/shared/nm-default.h b/shared/nm-default.h index 9e2377cf..b9be4768 100644 --- a/shared/nm-default.h +++ b/shared/nm-default.h @@ -22,19 +22,85 @@ #ifndef __NM_DEFAULT_H__ #define __NM_DEFAULT_H__ -/* makefiles define NETWORKMANAGER_COMPILATION for compiling NetworkManager. - * Depending on which parts are compiled, different values are set. */ -#define NM_NETWORKMANAGER_COMPILATION_DEFAULT 0x0001 -#define NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON 0x0002 -#define NM_NETWORKMANAGER_COMPILATION_LIB 0x0004 -#define NM_NETWORKMANAGER_COMPILATION_SYSTEMD 0x0008 -#define NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY 0x0010 +#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB (1 << 0) +#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB (1 << 1) +#define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG (1 << 2) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM (1 << 3) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE (1 << 4) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE (1 << 5) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL (1 << 6) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE (1 << 7) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL (1 << 8) +#define NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB (1 << 9) +#define NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON (1 << 10) +#define NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD (1 << 11) + +#define NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_LIBNM ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_LIBNM_UTIL ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_LIBNM_GLIB ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_LIBNM_UTIL \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_CLIENT ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_DAEMON ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE \ + | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL \ + | NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_SYSTEMD ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_DAEMON \ + | NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD \ + ) + +#define NM_NETWORKMANAGER_COMPILATION_GLIB ( 0 \ + | NM_NETWORKMANAGER_COMPILATION_WITH_GLIB \ + ) #ifndef NETWORKMANAGER_COMPILATION -/* For convenience, we don't require our Makefile.am to define - * -DNETWORKMANAGER_COMPILATION. As we now include this internal header, - * we know we do a NETWORKMANAGER_COMPILATION. */ -#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_DEFAULT +#error Define NETWORKMANAGER_COMPILATION accordingly +#endif + +#ifndef G_LOG_DOMAIN +#if defined(NETWORKMANAGER_COMPILATION_TEST) +#define G_LOG_DOMAIN "test" +#elif NETWORKMANAGER_COMPILATION & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON +#define G_LOG_DOMAIN "NetworkManager" +#else +#error Need to define G_LOG_DOMAIN +#endif +#elif defined (NETWORKMANAGER_COMPILATION_TEST) || (NETWORKMANAGER_COMPILATION & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON) +#error Do not define G_LOG_DOMAIN with NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON #endif /*****************************************************************************/ @@ -49,7 +115,6 @@ /* for internal compilation we don't want the deprecation macros * to be in effect. Define the widest range of versions to effectively * disable deprecation checks */ -#define NM_VERSION_MAX_ALLOWED NM_VERSION_NEXT_STABLE #define NM_VERSION_MIN_REQUIRED NM_VERSION_0_9_8 #ifndef NM_MORE_ASSERTS @@ -109,9 +174,52 @@ #endif #endif +#if NM_MORE_ASSERTS == 0 +#ifndef G_DISABLE_CAST_CHECKS +/* Unless compiling with G_DISABLE_CAST_CHECKS, glib performs type checking + * during G_VARIANT_TYPE() via g_variant_type_checked_(). This is not necesary + * because commonly this cast is needed during something like + * + * g_variant_builder_init (&props, G_VARIANT_TYPE ("a{sv}")); + * + * Note that in if the variant type would be invalid, the check still + * wouldn't make the buggy code magically work. Instead of passing a + * bogus type string (bad), it would pass %NULL to g_variant_builder_init() + * (also bad). + * + * Also, a function like g_variant_builder_init() already validates + * the input type via something like + * + * g_return_if_fail (g_variant_type_is_container (type)); + * + * So, by having G_VARIANT_TYPE() also validate the type, we validate + * twice, whereas the first validation is rather pointless because it + * doesn't prevent the function to be called with invalid arguments. + * + * Just patch G_VARIANT_TYPE() to perform no check. + */ +#undef G_VARIANT_TYPE +#define G_VARIANT_TYPE(type_string) ((const GVariantType *) (type_string)) +#endif +#endif + #include <stdlib.h> + +/*****************************************************************************/ + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB + #include <glib.h> +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB +#error Cannot define NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG and NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB +#endif +#include <glib/gi18n.h> +#elif (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_LIB +#include <glib/gi18n-lib.h> +#endif + /*****************************************************************************/ #if NM_MORE_ASSERTS == 0 @@ -178,31 +286,30 @@ _nm_g_return_if_fail_warning (const char *log_domain, #include "nm-utils/nm-macros-internal.h" #include "nm-utils/nm-shared-utils.h" -#include "nm-version.h" - -/*****************************************************************************/ - -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB) || ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY) - -#include <glib/gi18n-lib.h> - +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL +/* no hash-utils in legacy code. */ #else - -#include <glib/gi18n.h> - -#endif /* NM_NETWORKMANAGER_COMPILATION_LIB || NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY */ +#include "nm-utils/nm-hash-utils.h" +#endif /*****************************************************************************/ -#if (NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON || (NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_SYSTEMD +#if (NETWORKMANAGER_COMPILATION) & (NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL) +#include "nm-version.h" +#endif -/* the header is used inside src/, where additional - * headers are available. */ +/*****************************************************************************/ +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON #include "nm-types.h" #include "nm-logging.h" +#endif + +#if ((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM) && !((NETWORKMANAGER_COMPILATION) & (NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_PRIVATE | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_INTERNAL)) +#include "NetworkManager.h" +#endif -#endif /* NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON */ +#endif /* NM_NETWORKMANAGER_COMPILATION_WITH_GLIB */ /*****************************************************************************/ diff --git a/shared/nm-test-libnm-utils.h b/shared/nm-test-libnm-utils.h index 192c089e..c4731a52 100644 --- a/shared/nm-test-libnm-utils.h +++ b/shared/nm-test-libnm-utils.h @@ -22,6 +22,10 @@ #include "nm-utils/nm-test-utils.h" +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB +#include "nm-dbus-glib-types.h" +#endif + /*****************************************************************************/ typedef struct { @@ -29,7 +33,7 @@ typedef struct { GDBusProxy *proxy; GPid pid; int keepalive_fd; -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY) +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB struct { DBusGConnection *bus; } libdbus; @@ -54,7 +58,15 @@ static inline void _nmtstc_auto_service_cleanup (NMTstcServiceInfo **info) /*****************************************************************************/ -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB) +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB + +#include "nm-client.h" +#include "nm-remote-settings.h" + +NMClient *nmtstc_nm_client_new (void); +NMRemoteSettings *nmtstc_nm_remote_settings_new (void); + +#else NMDevice *nmtstc_service_add_device (NMTstcServiceInfo *info, NMClient *client, @@ -67,17 +79,9 @@ NMDevice * nmtstc_service_add_wired_device (NMTstcServiceInfo *sinfo, const char *hwaddr, const char **subchannels); -#endif /* NM_NETWORKMANAGER_COMPILATION_LIB */ - -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY) - -#include "nm-client.h" -#include "nm-remote-settings.h" - -NMClient *nmtstc_nm_client_new (void); -NMRemoteSettings *nmtstc_nm_remote_settings_new (void); +#endif -#endif /* NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY */ +/*****************************************************************************/ void nmtstc_service_add_connection (NMTstcServiceInfo *sinfo, NMConnection *connection, diff --git a/shared/nm-test-utils-impl.c b/shared/nm-test-utils-impl.c index 3eb726d9..998d792a 100644 --- a/shared/nm-test-utils-impl.c +++ b/shared/nm-test-utils-impl.c @@ -25,10 +25,6 @@ #include "NetworkManager.h" #include "nm-dbus-compat.h" -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY) -#include "nm-dbus-glib-types.h" -#endif - #include "nm-test-libnm-utils.h" /*****************************************************************************/ @@ -58,8 +54,7 @@ name_exists (GDBusConnection *c, const char *name) return exists; } -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY) - +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB static DBusGProxy * _libdbus_create_proxy_test (DBusGConnection *bus) { @@ -75,7 +70,6 @@ _libdbus_create_proxy_test (DBusGConnection *bus) return proxy; } - #endif NMTstcServiceInfo * @@ -121,7 +115,7 @@ nmtstc_service_init (void) NULL, &error); g_assert_no_error (error); -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY) +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB info->libdbus.bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error); g_assert_no_error (error); g_assert (info->libdbus.bus); @@ -148,7 +142,7 @@ nmtstc_service_cleanup (NMTstcServiceInfo *info) g_object_unref (info->bus); nm_close (info->keepalive_fd); -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY) +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB g_clear_pointer (&info->libdbus.bus, dbus_g_connection_unref); #endif @@ -156,8 +150,7 @@ nmtstc_service_cleanup (NMTstcServiceInfo *info) g_free (info); } -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB) - +#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB) typedef struct { GMainLoop *loop; const char *ifname; @@ -270,8 +263,7 @@ nmtstc_service_add_wired_device (NMTstcServiceInfo *sinfo, NMClient *client, { return add_device_common (sinfo, client, "AddWiredDevice", ifname, hwaddr, subchannels); } - -#endif /* NM_NETWORKMANAGER_COMPILATION_LIB */ +#endif void nmtstc_service_add_connection (NMTstcServiceInfo *sinfo, @@ -279,7 +271,7 @@ nmtstc_service_add_connection (NMTstcServiceInfo *sinfo, gboolean verify_connection, char **out_path) { -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY) +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB gs_unref_hashtable GHashTable *new_settings = NULL; gboolean success; gs_free_error GError *error = NULL; @@ -353,7 +345,7 @@ nmtstc_service_update_connection (NMTstcServiceInfo *sinfo, path = nm_connection_get_path (connection); g_assert (path); -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY) +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB { gs_unref_hashtable GHashTable *new_settings = NULL; gboolean success; @@ -414,8 +406,7 @@ nmtstc_service_update_connection_variant (NMTstcServiceInfo *sinfo, /*****************************************************************************/ -#if ((NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY) - +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB NMClient * nmtstc_nm_client_new (void) { @@ -461,7 +452,6 @@ nmtstc_nm_remote_settings_new (void) return settings; } - -#endif /* NM_NETWORKMANAGER_COMPILATION_LIB_LEGACY */ +#endif /*****************************************************************************/ diff --git a/shared/nm-utils/c-list-util.c b/shared/nm-utils/c-list-util.c index 070323c6..44ca26a5 100644 --- a/shared/nm-utils/c-list-util.c +++ b/shared/nm-utils/c-list-util.c @@ -58,39 +58,35 @@ c_list_relink (CList *lst) /*****************************************************************************/ static CList * -_c_list_sort (CList *ls, - CListSortCmp cmp, - const void *user_data) +_c_list_srt_split (CList *ls) { - CList *ls1, *ls2; - CList head; + CList *ls2; - if (!ls->next) - return ls; - - /* split list in two halfs @ls1 and @ls2. */ - ls1 = ls; ls2 = ls; ls = ls->next; - while (ls) { + if (!ls) + return NULL; + do { ls = ls->next; if (!ls) break; ls = ls->next; ls2 = ls2->next; - } - ls = ls2; - ls2 = ls->next; - ls->next = NULL; - - /* recurse */ - ls1 = _c_list_sort (ls1, cmp, user_data); - if (!ls2) - return ls1; + } while (ls); + ls = ls2->next; + ls2->next = NULL; + return ls; +} - ls2 = _c_list_sort (ls2, cmp, user_data); +static CList * +_c_list_srt_merge (CList *ls1, + CList *ls2, + CListSortCmp cmp, + const void *user_data) +{ + CList *ls; + CList head; - /* merge */ ls = &head; for (;;) { /* while invoking the @cmp function, the list @@ -115,6 +111,54 @@ _c_list_sort (CList *ls, return head.next; } +typedef struct { + CList *ls1; + CList *ls2; + char ls1_sorted; +} SortStack; + +static CList * +_c_list_sort (CList *ls, + CListSortCmp cmp, + const void *user_data) +{ + /* reserve a huge stack-size. We need roughly log2(n) entries, hence this + * is much more we will ever need. We don't guard for stack-overflow either. */ + SortStack stack_arr[70]; + SortStack *stack_head = stack_arr; + + stack_arr[0].ls1 = ls; + + /* A simple top-down, non-recursive, stable merge-sort. + * + * Maybe natural merge-sort would be better, to do better for + * partially sorted lists. */ +_split: + stack_head[0].ls2 = _c_list_srt_split (stack_head[0].ls1); + if (stack_head[0].ls2) { + stack_head[0].ls1_sorted = 0; + stack_head[1].ls1 = stack_head[0].ls1; + stack_head++; + goto _split; + } + +_backtrack: + if (stack_head == stack_arr) + return stack_arr[0].ls1; + + stack_head--; + if (!stack_head[0].ls1_sorted) { + stack_head[0].ls1 = stack_head[1].ls1; + stack_head[0].ls1_sorted = 1; + stack_head[1].ls1 = stack_head[0].ls2; + stack_head++; + goto _split; + } + + stack_head[0].ls1 = _c_list_srt_merge (stack_head[0].ls1, stack_head[1].ls1, cmp, user_data); + goto _backtrack; +} + /** * c_list_sort_headless: * @lst: the list. diff --git a/shared/nm-utils/c-list-util.h b/shared/nm-utils/c-list-util.h index 199583cf..e87f1c19 100644 --- a/shared/nm-utils/c-list-util.h +++ b/shared/nm-utils/c-list-util.h @@ -22,7 +22,7 @@ #ifndef __C_LIST_UTIL_H__ #define __C_LIST_UTIL_H__ -#include "c-list.h" +#include "c-list/src/c-list.h" /*****************************************************************************/ diff --git a/shared/nm-utils/nm-c-list.h b/shared/nm-utils/nm-c-list.h new file mode 100644 index 00000000..b43d1441 --- /dev/null +++ b/shared/nm-utils/nm-c-list.h @@ -0,0 +1,81 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2014 Red Hat, Inc. + */ + +#ifndef __NM_C_LIST_H__ +#define __NM_C_LIST_H__ + +#include "c-list/src/c-list.h" + +/*****************************************************************************/ + +#define nm_c_list_contains_entry(list, what, member) \ + ({ \ + typeof (what) _what = (what); \ + \ + _what && c_list_contains (list, &_what->member); \ + }) + +typedef struct { + CList lst; + void *data; +} NMCListElem; + +static inline NMCListElem * +nm_c_list_elem_new_stale (void *data) +{ + NMCListElem *elem; + + elem = g_slice_new (NMCListElem); + elem->data = data; + return elem; +} + +static inline void * +nm_c_list_elem_get (CList *lst) +{ + if (!lst) + return NULL; + return c_list_entry (lst, NMCListElem, lst)->data; +} + +static inline void +nm_c_list_elem_free (NMCListElem *elem) +{ + if (elem) { + c_list_unlink_stale (&elem->lst); + g_slice_free (NMCListElem, elem); + } +} + +static inline void +nm_c_list_elem_free_all (CList *head, GDestroyNotify free_fcn) +{ + NMCListElem *elem; + + while ((elem = c_list_first_entry (head, NMCListElem, lst))) { + if (free_fcn) + free_fcn (elem->data); + c_list_unlink_stale (&elem->lst); + g_slice_free (NMCListElem, elem); + } +} + +#endif /* __NM_C_LIST_H__ */ diff --git a/shared/nm-utils/nm-compat.c b/shared/nm-utils/nm-compat.c new file mode 100644 index 00000000..90328c06 --- /dev/null +++ b/shared/nm-utils/nm-compat.c @@ -0,0 +1,95 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2017 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-compat.h" + +/*****************************************************************************/ + +static void +_get_keys_cb (const char *key, const char *val, gpointer user_data) +{ + GPtrArray *a = user_data; + + g_ptr_array_add (a, g_strdup (key)); +} + +static const char ** +_get_keys (NMSettingVpn *setting, + gboolean is_secrets, + guint *out_length) +{ + guint len; + const char **keys = NULL; + GPtrArray *a; + + nm_assert (NM_IS_SETTING_VPN (setting)); + + if (is_secrets) + len = nm_setting_vpn_get_num_secrets (setting); + else + len = nm_setting_vpn_get_num_data_items (setting); + + a = g_ptr_array_sized_new (len + 1); + + if (is_secrets) + nm_setting_vpn_foreach_secret (setting, _get_keys_cb, a); + else + nm_setting_vpn_foreach_data_item (setting, _get_keys_cb, a); + + len = a->len; + if (len) { + g_ptr_array_sort (a, nm_strcmp_p); + g_ptr_array_add (a, NULL); + keys = g_memdup (a->pdata, a->len * sizeof (gpointer)); + + /* we need to cache the keys *somewhere*. */ + g_object_set_qdata_full (G_OBJECT (setting), + is_secrets + ? NM_CACHED_QUARK ("libnm._nm_setting_vpn_get_secret_keys") + : NM_CACHED_QUARK ("libnm._nm_setting_vpn_get_data_keys"), + g_ptr_array_free (a, FALSE), + (GDestroyNotify) g_strfreev); + } else + g_ptr_array_free (a, TRUE); + + NM_SET_OUT (out_length, len); + return keys; +} + +const char ** +_nm_setting_vpn_get_data_keys (NMSettingVpn *setting, + guint *out_length) +{ + g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL); + + return _get_keys (setting, FALSE, out_length); +} + +const char ** +_nm_setting_vpn_get_secret_keys (NMSettingVpn *setting, + guint *out_length) +{ + g_return_val_if_fail (NM_IS_SETTING_VPN (setting), NULL); + + return _get_keys (setting, TRUE, out_length); +} diff --git a/shared/nm-utils/nm-compat.h b/shared/nm-utils/nm-compat.h new file mode 100644 index 00000000..52341690 --- /dev/null +++ b/shared/nm-utils/nm-compat.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* NetworkManager -- Network link manager + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * (C) Copyright 2017 Red Hat, Inc. + */ + +#ifndef __NM_COMPAT_H__ +#define __NM_COMPAT_H__ + +#include "nm-setting-vpn.h" + +const char **_nm_setting_vpn_get_data_keys (NMSettingVpn *setting, + guint *out_length); + +const char **_nm_setting_vpn_get_secret_keys (NMSettingVpn *setting, + guint *out_length); + +#if NM_CHECK_VERSION (1, 11, 0) +#define nm_setting_vpn_get_data_keys(setting, out_length) \ + ({ \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + nm_setting_vpn_get_data_keys (setting, out_length); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + }) +#define nm_setting_vpn_get_secret_keys(setting, out_length) \ + ({ \ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ + nm_setting_vpn_get_secret_keys (setting, out_length); \ + G_GNUC_END_IGNORE_DEPRECATIONS \ + }) +#else +#define nm_setting_vpn_get_data_keys(setting, out_length) \ + _nm_setting_vpn_get_data_keys (setting, out_length) +#define nm_setting_vpn_get_secret_keys(setting, out_length) \ + _nm_setting_vpn_get_secret_keys (setting, out_length) +#endif + +#endif /* __NM_COMPAT_H__ */ diff --git a/shared/nm-utils/nm-dedup-multi.c b/shared/nm-utils/nm-dedup-multi.c index ee310a7b..fc134e25 100644 --- a/shared/nm-utils/nm-dedup-multi.c +++ b/shared/nm-utils/nm-dedup-multi.c @@ -386,10 +386,10 @@ _add (NMDedupMultiIndex *self, head_entry->len++; if ( add_head_entry - && !nm_g_hash_table_add (self->idx_entries, head_entry)) + && !g_hash_table_add (self->idx_entries, head_entry)) nm_assert_not_reached (); - if (!nm_g_hash_table_add (self->idx_entries, entry)) + if (!g_hash_table_add (self->idx_entries, entry)) nm_assert_not_reached (); NM_SET_OUT (out_entry, entry); @@ -870,14 +870,14 @@ nm_dedup_multi_index_obj_intern (NMDedupMultiIndex *self, nm_assert (obj_new); nm_assert (!obj_new->_multi_idx); - if (!nm_g_hash_table_add (self->idx_objs, (gpointer) obj_new)) + if (!g_hash_table_add (self->idx_objs, (gpointer) obj_new)) nm_assert_not_reached (); ((NMDedupMultiObj *) obj_new)->_multi_idx = self; return obj_new; } -const NMDedupMultiObj * +void nm_dedup_multi_obj_unref (const NMDedupMultiObj *obj) { if (obj) { @@ -899,8 +899,6 @@ again: obj->klass->obj_destroy ((NMDedupMultiObj *) obj); } } - - return NULL; } gboolean diff --git a/shared/nm-utils/nm-dedup-multi.h b/shared/nm-utils/nm-dedup-multi.h index bebfe43d..8d482de9 100644 --- a/shared/nm-utils/nm-dedup-multi.h +++ b/shared/nm-utils/nm-dedup-multi.h @@ -97,7 +97,7 @@ nm_dedup_multi_obj_ref (const NMDedupMultiObj *obj) return obj; } -const NMDedupMultiObj *nm_dedup_multi_obj_unref (const NMDedupMultiObj *obj); +void nm_dedup_multi_obj_unref (const NMDedupMultiObj *obj); const NMDedupMultiObj *nm_dedup_multi_obj_clone (const NMDedupMultiObj *obj); gboolean nm_dedup_multi_obj_needs_clone (const NMDedupMultiObj *obj); @@ -115,7 +115,7 @@ void nm_dedup_multi_index_obj_release (NMDedupMultiIndex *self, /* the NMDedupMultiIdxType is an access handle under which you can store and * retrieve NMDedupMultiObj instances in NMDedupMultiIndex. * - * The NMDedupMultiIdxTypeClass determines it's behavior, but you can have + * The NMDedupMultiIdxTypeClass determines its behavior, but you can have * multiple instances (of the same class). * * For example, NMIP4Config can have idx-type to put there all IPv4 Routes. diff --git a/shared/nm-utils/nm-enum-utils.c b/shared/nm-utils/nm-enum-utils.c index 70a8b415..b9bc6e88 100644 --- a/shared/nm-utils/nm-enum-utils.c +++ b/shared/nm-utils/nm-enum-utils.c @@ -64,10 +64,10 @@ _enum_is_valid_flags_nick (const char *str) char * _nm_utils_enum_to_str_full (GType type, int value, - const char *flags_separator) + const char *flags_separator, + const NMUtilsEnumValueInfo *value_infos) { - GTypeClass *class; - char *ret; + nm_auto_unref_gtypeclass GTypeClass *class = NULL; if ( flags_separator && ( !flags_separator[0] @@ -79,12 +79,17 @@ _nm_utils_enum_to_str_full (GType type, if (G_IS_ENUM_CLASS (class)) { GEnumValue *enum_value; + for ( ; value_infos && value_infos->nick; value_infos++) { + if (value_infos->value == value) + return g_strdup (value_infos->nick); + } + enum_value = g_enum_get_value (G_ENUM_CLASS (class), value); if ( !enum_value || !_enum_is_valid_enum_nick (enum_value->value_nick)) - ret = g_strdup_printf ("%d", value); + return g_strdup_printf ("%d", value); else - ret = strdup (enum_value->value_nick); + return g_strdup (enum_value->value_nick); } else if (G_IS_FLAGS_CLASS (class)) { GFlagsValue *flags_value; GString *str = g_string_new (""); @@ -92,6 +97,28 @@ _nm_utils_enum_to_str_full (GType type, flags_separator = flags_separator ?: " "; + for ( ; value_infos && value_infos->nick; value_infos++) { + + nm_assert (_enum_is_valid_flags_nick (value_infos->nick)); + + if (uvalue == 0) { + if (value_infos->value != 0) + continue; + } else { + if (!NM_FLAGS_ALL (uvalue, (unsigned) value_infos->value)) + continue; + } + + if (str->len) + g_string_append (str, flags_separator); + g_string_append (str, value_infos->nick); + uvalue &= ~((unsigned) value_infos->value); + if (uvalue == 0) { + /* we printed all flags. Done. */ + goto flags_done; + } + } + do { flags_value = g_flags_get_first_value (G_FLAGS_CLASS (class), uvalue); if (str->len) @@ -105,12 +132,12 @@ _nm_utils_enum_to_str_full (GType type, g_string_append (str, flags_value->value_nick); uvalue &= ~flags_value->value; } while (uvalue); - ret = g_string_free (str, FALSE); - } else - g_return_val_if_reached (NULL); - g_type_class_unref (class); - return ret; +flags_done: + return g_string_free (str, FALSE); + } + + g_return_val_if_reached (NULL); } static const NMUtilsEnumValueInfo * diff --git a/shared/nm-utils/nm-enum-utils.h b/shared/nm-utils/nm-enum-utils.h index b78d9191..d6dae859 100644 --- a/shared/nm-utils/nm-enum-utils.h +++ b/shared/nm-utils/nm-enum-utils.h @@ -31,7 +31,10 @@ typedef struct _NMUtilsEnumValueInfo { int value; } NMUtilsEnumValueInfo; -char *_nm_utils_enum_to_str_full (GType type, int value, const char *sep); +char *_nm_utils_enum_to_str_full (GType type, + int value, + const char *sep, + const NMUtilsEnumValueInfo *value_infos); gboolean _nm_utils_enum_from_str_full (GType type, const char *str, int *out_value, diff --git a/shared/nm-utils/nm-glib.h b/shared/nm-utils/nm-glib.h index 599890e0..f1498dc4 100644 --- a/shared/nm-utils/nm-glib.h +++ b/shared/nm-utils/nm-glib.h @@ -14,7 +14,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * Copyright 2008 - 2011 Red Hat, Inc. + * Copyright 2008 - 2018 Red Hat, Inc. */ #ifndef __NM_GLIB_H__ @@ -40,84 +40,6 @@ #endif -static inline void -__g_type_ensure (GType type) -{ -#if !GLIB_CHECK_VERSION(2,34,0) - if (G_UNLIKELY (type == (GType)-1)) - g_error ("can't happen"); -#else - G_GNUC_BEGIN_IGNORE_DEPRECATIONS; - g_type_ensure (type); - G_GNUC_END_IGNORE_DEPRECATIONS; -#endif -} -#define g_type_ensure __g_type_ensure - -#if !GLIB_CHECK_VERSION(2,34,0) - -#define g_clear_pointer(pp, destroy) \ - G_STMT_START { \ - G_STATIC_ASSERT (sizeof *(pp) == sizeof (gpointer)); \ - /* Only one access, please */ \ - gpointer *_pp = (gpointer *) (pp); \ - gpointer _p; \ - /* This assignment is needed to avoid a gcc warning */ \ - GDestroyNotify _destroy = (GDestroyNotify) (destroy); \ - \ - _p = *_pp; \ - if (_p) \ - { \ - *_pp = NULL; \ - _destroy (_p); \ - } \ - } G_STMT_END - -/* These are used to clean up the output of test programs; we can just let - * them no-op in older glib. - */ -#define g_test_expect_message(log_domain, log_level, pattern) -#define g_test_assert_expected_messages() - -#else - -/* We build with -DGLIB_MAX_ALLOWED_VERSION set to 2.32 to make sure we don't - * accidentally use new API that we shouldn't. But we don't want warnings for - * the APIs that we emulate above. - */ - -#define g_test_expect_message(domain, level, format...) \ - G_STMT_START { \ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ - g_test_expect_message (domain, level, format); \ - G_GNUC_END_IGNORE_DEPRECATIONS \ - } G_STMT_END - -#define g_test_assert_expected_messages_internal(domain, file, line, func) \ - G_STMT_START { \ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ - g_test_assert_expected_messages_internal (domain, file, line, func); \ - G_GNUC_END_IGNORE_DEPRECATIONS \ - } G_STMT_END - -#endif - - -#if GLIB_CHECK_VERSION (2, 35, 0) -/* For glib >= 2.36, g_type_init() is deprecated. - * But since 2.35.1 (7c42ab23b55c43ab96d0ac2124b550bf1f49c1ec) this function - * does nothing. Replace the call with empty statement. */ -#define nm_g_type_init() G_STMT_START { (void) 0; } G_STMT_END -#else -#define nm_g_type_init() G_STMT_START { g_type_init (); } G_STMT_END -#endif - - -/* g_test_initialized() is only available since glib 2.36. */ -#if !GLIB_CHECK_VERSION (2, 36, 0) -#define g_test_initialized() (g_test_config_vars->test_initialized) -#endif - /* g_assert_cmpmem() is only available since glib 2.46. */ #if !GLIB_CHECK_VERSION (2, 45, 7) #define g_assert_cmpmem(m1, l1, m2, l2) G_STMT_START {\ @@ -146,239 +68,6 @@ nm_glib_check_version (guint major, guint minor, guint micro) && glib_micro_version < micro)); } -/* g_test_skip() is only available since glib 2.38. Add a compatibility wrapper. */ -static inline void -__nmtst_g_test_skip (const gchar *msg) -{ -#if GLIB_CHECK_VERSION (2, 38, 0) - G_GNUC_BEGIN_IGNORE_DEPRECATIONS - g_test_skip (msg); - G_GNUC_END_IGNORE_DEPRECATIONS -#else - g_debug ("%s", msg); -#endif -} -#define g_test_skip __nmtst_g_test_skip - - -/* g_test_add_data_func_full() is only available since glib 2.34. Add a compatibility wrapper. */ -static inline void -__g_test_add_data_func_full (const char *testpath, - gpointer test_data, - GTestDataFunc test_func, - GDestroyNotify data_free_func) -{ -#if GLIB_CHECK_VERSION (2, 34, 0) - G_GNUC_BEGIN_IGNORE_DEPRECATIONS - g_test_add_data_func_full (testpath, test_data, test_func, data_free_func); - G_GNUC_END_IGNORE_DEPRECATIONS -#else - g_return_if_fail (testpath != NULL); - g_return_if_fail (testpath[0] == '/'); - g_return_if_fail (test_func != NULL); - - g_test_add_vtable (testpath, 0, test_data, NULL, - (GTestFixtureFunc) test_func, - (GTestFixtureFunc) data_free_func); -#endif -} -#define g_test_add_data_func_full __g_test_add_data_func_full - - -#if !GLIB_CHECK_VERSION (2, 34, 0) -#define G_DEFINE_QUARK(QN, q_n) \ -GQuark \ -q_n##_quark (void) \ -{ \ - static GQuark q; \ - \ - if G_UNLIKELY (q == 0) \ - q = g_quark_from_static_string (#QN); \ - \ - return q; \ -} -#endif - - -static inline gboolean -nm_g_hash_table_replace (GHashTable *hash, gpointer key, gpointer value) -{ - /* glib 2.40 added a return value indicating whether the key already existed - * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */ -#if GLIB_CHECK_VERSION(2, 40, 0) - return g_hash_table_replace (hash, key, value); -#else - gboolean contained = g_hash_table_contains (hash, key); - - g_hash_table_replace (hash, key, value); - return !contained; -#endif -} - -static inline gboolean -nm_g_hash_table_insert (GHashTable *hash, gpointer key, gpointer value) -{ - /* glib 2.40 added a return value indicating whether the key already existed - * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */ -#if GLIB_CHECK_VERSION(2, 40, 0) - return g_hash_table_insert (hash, key, value); -#else - gboolean contained = g_hash_table_contains (hash, key); - - g_hash_table_insert (hash, key, value); - return !contained; -#endif -} - -static inline gboolean -nm_g_hash_table_add (GHashTable *hash, gpointer key) -{ - /* glib 2.40 added a return value indicating whether the key already existed - * (910191597a6c2e5d5d460e9ce9efb4f47d9cc63c). */ -#if GLIB_CHECK_VERSION(2, 40, 0) - return g_hash_table_add (hash, key); -#else - gboolean contained = g_hash_table_contains (hash, key); - - g_hash_table_add (hash, key); - return !contained; -#endif -} - -#if !GLIB_CHECK_VERSION(2, 40, 0) || defined (NM_GLIB_COMPAT_H_TEST) -static inline void -_nm_g_ptr_array_insert (GPtrArray *array, - gint index_, - gpointer data) -{ - g_return_if_fail (array); - g_return_if_fail (index_ >= -1); - g_return_if_fail (index_ <= (gint) array->len); - - g_ptr_array_add (array, data); - - if (index_ != -1 && index_ != (gint) (array->len - 1)) { - memmove (&(array->pdata[index_ + 1]), - &(array->pdata[index_]), - (array->len - index_ - 1) * sizeof (gpointer)); - array->pdata[index_] = data; - } -} -#endif -#if !GLIB_CHECK_VERSION(2, 40, 0) -#define g_ptr_array_insert(array, index, data) G_STMT_START { _nm_g_ptr_array_insert (array, index, data); } G_STMT_END -#else -#define g_ptr_array_insert(array, index, data) \ - G_STMT_START { \ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ - g_ptr_array_insert (array, index, data); \ - G_GNUC_END_IGNORE_DEPRECATIONS \ - } G_STMT_END -#endif - - -#if !GLIB_CHECK_VERSION (2, 40, 0) -static inline gboolean -_g_key_file_save_to_file (GKeyFile *key_file, - const gchar *filename, - GError **error) -{ - gchar *contents; - gboolean success; - gsize length; - - g_return_val_if_fail (key_file != NULL, FALSE); - g_return_val_if_fail (filename != NULL, FALSE); - g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - - contents = g_key_file_to_data (key_file, &length, NULL); - g_assert (contents != NULL); - - success = g_file_set_contents (filename, contents, length, error); - g_free (contents); - - return success; -} -#define g_key_file_save_to_file(key_file, filename, error) \ - _g_key_file_save_to_file (key_file, filename, error) -#else -#define g_key_file_save_to_file(key_file, filename, error) \ - ({ \ - gboolean _success; \ - \ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ - _success = g_key_file_save_to_file (key_file, filename, error); \ - G_GNUC_END_IGNORE_DEPRECATIONS \ - _success; \ - }) -#endif - - -#if GLIB_CHECK_VERSION (2, 36, 0) -#define g_credentials_get_unix_pid(creds, error) \ - ({ \ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ - (g_credentials_get_unix_pid) ((creds), (error)); \ - G_GNUC_END_IGNORE_DEPRECATIONS \ - }) -#else -#define g_credentials_get_unix_pid(creds, error) \ - ({ \ - struct ucred *native_creds; \ - \ - native_creds = g_credentials_get_native ((creds), G_CREDENTIALS_TYPE_LINUX_UCRED); \ - g_assert (native_creds); \ - native_creds->pid; \ - }) -#endif - - -#if !GLIB_CHECK_VERSION(2, 40, 0) || defined (NM_GLIB_COMPAT_H_TEST) -static inline gpointer * -_nm_g_hash_table_get_keys_as_array (GHashTable *hash_table, - guint *length) -{ - GHashTableIter iter; - gpointer key, *ret; - guint i = 0; - - g_return_val_if_fail (hash_table, NULL); - - ret = g_new0 (gpointer, g_hash_table_size (hash_table) + 1); - g_hash_table_iter_init (&iter, hash_table); - - while (g_hash_table_iter_next (&iter, &key, NULL)) - ret[i++] = key; - - ret[i] = NULL; - - if (length) - *length = i; - - return ret; -} -#endif -#if !GLIB_CHECK_VERSION(2, 40, 0) -#define g_hash_table_get_keys_as_array(hash_table, length) \ - ({ \ - _nm_g_hash_table_get_keys_as_array (hash_table, length); \ - }) -#else -#define g_hash_table_get_keys_as_array(hash_table, length) \ - ({ \ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ - (g_hash_table_get_keys_as_array) ((hash_table), (length)); \ - G_GNUC_END_IGNORE_DEPRECATIONS \ - }) -#endif - -#ifndef g_info -/* g_info was only added with 2.39.2 */ -#define g_info(...) g_log (G_LOG_DOMAIN, \ - G_LOG_LEVEL_INFO, \ - __VA_ARGS__) -#endif - #if !GLIB_CHECK_VERSION(2, 44, 0) static inline gpointer g_steal_pointer (gpointer pp) @@ -420,70 +109,17 @@ _nm_g_strv_contains (const gchar * const *strv, } #define g_strv_contains _nm_g_strv_contains -static inline GVariant * -_nm_g_variant_new_take_string (gchar *string) -{ -#if !GLIB_CHECK_VERSION(2, 36, 0) - GVariant *value; - - g_return_val_if_fail (string != NULL, NULL); - g_return_val_if_fail (g_utf8_validate (string, -1, NULL), NULL); - - value = g_variant_new_string (string); - g_free (string); - return value; -#elif !GLIB_CHECK_VERSION(2, 38, 0) - GVariant *value; - GBytes *bytes; - - g_return_val_if_fail (string != NULL, NULL); - g_return_val_if_fail (g_utf8_validate (string, -1, NULL), NULL); - - bytes = g_bytes_new_take (string, strlen (string) + 1); - value = g_variant_new_from_bytes (G_VARIANT_TYPE_STRING, bytes, TRUE); - g_bytes_unref (bytes); - - return value; -#else - G_GNUC_BEGIN_IGNORE_DEPRECATIONS - return g_variant_new_take_string (string); - G_GNUC_END_IGNORE_DEPRECATIONS -#endif -} -#define g_variant_new_take_string _nm_g_variant_new_take_string - -#if !GLIB_CHECK_VERSION(2, 38, 0) -_nm_printf (1, 2) -static inline GVariant * -_nm_g_variant_new_printf (const char *format_string, ...) -{ - char *string; - va_list ap; - - g_return_val_if_fail (format_string, NULL); - - va_start (ap, format_string); - string = g_strdup_vprintf (format_string, ap); - va_end (ap); - - return g_variant_new_take_string (string); -} -#define g_variant_new_printf(...) _nm_g_variant_new_printf(__VA_ARGS__) -#else -#define g_variant_new_printf(...) \ - ({ \ - GVariant *_v; \ - \ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ - _v = g_variant_new_printf (__VA_ARGS__); \ - G_GNUC_END_IGNORE_DEPRECATIONS \ - _v; \ - }) -#endif - #if !GLIB_CHECK_VERSION (2, 56, 0) #define g_object_ref(Obj) ((typeof(Obj)) g_object_ref (Obj)) #define g_object_ref_sink(Obj) ((typeof(Obj)) g_object_ref_sink (Obj)) #endif +#ifndef g_autofree +/* we still don't rely on recent glib to provide g_autofree. Hence, we continue + * to use our gs_* free macros that we took from libgsystem. + * + * To ease migration towards g_auto*, add a compat define for g_autofree. */ +#define g_autofree gs_free +#endif + #endif /* __NM_GLIB_H__ */ diff --git a/shared/nm-utils/nm-hash-utils.c b/shared/nm-utils/nm-hash-utils.c index c563140e..8d8c21ce 100644 --- a/shared/nm-utils/nm-hash-utils.c +++ b/shared/nm-utils/nm-hash-utils.c @@ -28,6 +28,8 @@ #include "nm-shared-utils.h" #include "nm-random-utils.h" +#include "siphash24.c" + /*****************************************************************************/ #define HASH_KEY_SIZE 16u @@ -35,33 +37,77 @@ G_STATIC_ASSERT (sizeof (guint) * HASH_KEY_SIZE_GUINT >= HASH_KEY_SIZE); +static const guint8 *volatile global_seed = NULL; + static const guint8 * -_get_hash_key (void) +_get_hash_key_init (void) { - static const guint8 *volatile global_seed = NULL; + /* the returned hash is aligned to guin64, hence, it is safe + * to use it as guint* or guint64* pointer. */ + static union { + guint8 v8[HASH_KEY_SIZE]; + } g_arr _nm_alignas (guint64); + static gsize g_lock; const guint8 *g; + struct siphash siph_state; + uint64_t h; + guint *p; g = global_seed; - if (G_UNLIKELY (g == NULL)) { - /* the returned hash is aligned to guin64, hence, it is save - * to use it as guint* or guint64* pointer. */ - static union { - guint8 v8[HASH_KEY_SIZE]; - } g_arr _nm_alignas (guint64); - static gsize g_lock; - - if (g_once_init_enter (&g_lock)) { - nm_utils_random_bytes (g_arr.v8, sizeof (g_arr.v8)); - g_atomic_pointer_compare_and_exchange (&global_seed, NULL, g_arr.v8); - g = g_arr.v8; - g_once_init_leave (&g_lock, 1); - } else { - g = global_seed; - nm_assert (g); - } + if (G_LIKELY (g != NULL)) { + nm_assert (g == g_arr.v8); + return g; + } + + if (g_once_init_enter (&g_lock)) { + + nm_utils_random_bytes (g_arr.v8, sizeof (g_arr.v8)); + + /* use siphash() of the key-size, to mangle the first guint. Otherwise, + * the first guint has only the entropy that nm_utils_random_bytes() + * generated for the first 4 bytes and relies on a good random generator. */ + siphash24_init (&siph_state, g_arr.v8); + siphash24_compress (g_arr.v8, sizeof (g_arr.v8), &siph_state); + h = siphash24_finalize (&siph_state); + p = (guint *) g_arr.v8; + if (sizeof (guint) < sizeof (h)) + *p = *p ^ ((guint) (h & 0xFFFFFFFFu)) ^ ((guint) (h >> 32)); + else + *p = *p ^ ((guint) (h & 0xFFFFFFFFu)); + + g_atomic_pointer_compare_and_exchange (&global_seed, NULL, g_arr.v8); + g_once_init_leave (&g_lock, 1); } - return g; + nm_assert (global_seed == g_arr.v8); + return g_arr.v8; +} + +#define _get_hash_key() \ + ({ \ + const guint8 *_g; \ + \ + _g = global_seed; \ + if (G_UNLIKELY (_g == NULL)) \ + _g = _get_hash_key_init (); \ + _g; \ + }) + +guint +nm_hash_static (guint static_seed) +{ + /* note that we only xor the static_seed with the key. + * We don't use siphash24(), which would mix the bits better. + * Note that this doesn't matter, because static_seed is not + * supposed to be a value that you are hashing (for that, use + * full siphash24()). + * Instead, different callers may set a different static_seed + * so that nm_hash_str(NULL) != nm_hash_ptr(NULL). + * + * Also, ensure that we don't return zero. + */ + return ((*((const guint *) _get_hash_key ())) ^ static_seed) + ?: static_seed ?: 3679500967u; } void @@ -83,11 +129,10 @@ nm_hash_str (const char *str) { NMHashState h; - if (str) { - nm_hash_init (&h, 1867854211u); - nm_hash_update_str (&h, str); - } else - nm_hash_init (&h, 842995561u); + if (!str) + return nm_hash_static (1867854211u); + nm_hash_init (&h, 1867854211u); + nm_hash_update_str (&h, str); return nm_hash_complete (&h); } @@ -100,16 +145,13 @@ nm_str_hash (gconstpointer str) guint nm_hash_ptr (gconstpointer ptr) { - guint h; - - h = ((const guint *) _get_hash_key ())[0]; - - if (sizeof (ptr) <= sizeof (guint)) - h = h ^ ((guint) ((uintptr_t) ptr)); - else - h = h ^ ((guint) (((guint64) (uintptr_t) ptr) >> 32)) ^ ((guint) ((uintptr_t) ptr)); + NMHashState h; - return h ?: 2907677551u; + if (!ptr) + return nm_hash_static (2907677551u); + nm_hash_init (&h, 2907677551u); + nm_hash_update (&h, &ptr, sizeof (ptr)); + return nm_hash_complete (&h); } guint @@ -117,3 +159,27 @@ nm_direct_hash (gconstpointer ptr) { return nm_hash_ptr (ptr); } + +/*****************************************************************************/ + +guint +nm_pstr_hash (gconstpointer p) +{ + const char *const*s = p; + + if (!s) + return nm_hash_static (101061439u); + return nm_hash_str (*s); +} + +gboolean +nm_pstr_equal (gconstpointer a, gconstpointer b) +{ + const char *const*s1 = a; + const char *const*s2 = b; + + return (s1 == s2) + || ( s1 + && s2 + && nm_streq0 (*s1, *s2)); +} diff --git a/shared/nm-utils/nm-hash-utils.h b/shared/nm-utils/nm-hash-utils.h index 276e1ebe..3bd3f652 100644 --- a/shared/nm-utils/nm-hash-utils.h +++ b/shared/nm-utils/nm-hash-utils.h @@ -31,6 +31,8 @@ struct _NMHashState { typedef struct _NMHashState NMHashState; +guint nm_hash_static (guint static_seed); + void nm_hash_init (NMHashState *state, guint static_seed); static inline guint @@ -207,4 +209,15 @@ guint nm_direct_hash (gconstpointer str); guint nm_hash_str (const char *str); guint nm_str_hash (gconstpointer str); +/*****************************************************************************/ + +/* nm_pstr_*() are for hashing keys that are pointers to strings, + * that is, "const char *const*" types, using strcmp(). */ + +guint nm_pstr_hash (gconstpointer p); + +gboolean nm_pstr_equal (gconstpointer a, gconstpointer b); + +/*****************************************************************************/ + #endif /* __NM_HASH_UTILS_H__ */ diff --git a/shared/nm-utils/nm-jansson.h b/shared/nm-utils/nm-jansson.h new file mode 100644 index 00000000..b00c75c6 --- /dev/null +++ b/shared/nm-utils/nm-jansson.h @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright 2018 Red Hat, Inc. + */ + +#ifndef __NM_JANSSON_H__ +#define __NM_JANSSON_H__ + +/* you need to include at least "config.h" first, possibly "nm-default.h". */ + +#if WITH_JANSSON + +#include <jansson.h> + +/* Added in Jansson v2.7 */ +#ifndef json_boolean_value +#define json_boolean_value json_is_true +#endif + +/* Added in Jansson v2.8 */ +#ifndef json_object_foreach_safe +#define json_object_foreach_safe(object, n, key, value) \ + for(key = json_object_iter_key(json_object_iter(object)), \ + n = json_object_iter_next(object, json_object_key_to_iter(key)); \ + key && (value = json_object_iter_value(json_object_key_to_iter(key))); \ + key = json_object_iter_key(n), \ + n = json_object_iter_next(object, json_object_key_to_iter(key))) +#endif + +#endif /* WITH_JANSON */ + +#endif /* __NM_JANSSON_H__ */ diff --git a/shared/nm-utils/nm-macros-internal.h b/shared/nm-utils/nm-macros-internal.h index 29678bb6..cc7205a4 100644 --- a/shared/nm-utils/nm-macros-internal.h +++ b/shared/nm-utils/nm-macros-internal.h @@ -35,6 +35,12 @@ #define _nm_alignof(type) __alignof (type) #define _nm_alignas(type) _nm_align (_nm_alignof (type)) +#if __GNUC__ >= 7 +#define _nm_fallthrough __attribute__ ((fallthrough)) +#else +#define _nm_fallthrough +#endif + /*****************************************************************************/ #ifdef thread_local @@ -70,6 +76,30 @@ static inline int nm_close (int fd); GS_DEFINE_CLEANUP_FUNCTION(void*, _nm_auto_free_impl, free) static inline void +nm_free_secret (char *secret) +{ + if (secret) { + memset (secret, 0, strlen (secret)); + g_free (secret); + } +} + +static inline void +_nm_auto_free_secret_impl (char **v) +{ + nm_free_secret (*v); +} + +/** + * nm_auto_free_secret: + * + * Call g_free() on a variable location when it goes out of scope. + * Also, previously, calls memset(loc, 0, strlen(loc)) to clear out + * the secret. + */ +#define nm_auto_free_secret nm_auto(_nm_auto_free_secret_impl) + +static inline void _nm_auto_unset_gvalue_impl (GValue *v) { g_value_unset (v); @@ -241,7 +271,8 @@ NM_G_ERROR_MSG (GError *error) gsize _n = 0; \ \ if (_array) { \ - _nm_unused typeof (*(_array[0])) *_array_check = _array[0]; \ + _nm_unused gconstpointer _type_check_is_pointer = _array[0]; \ + \ while (_array[_n]) \ _n++; \ } \ @@ -359,6 +390,28 @@ NM_G_ERROR_MSG (GError *error) #define NM_CONSTCAST(type, obj, ...) \ NM_CONSTCAST_FULL(type, (obj), (obj), ##__VA_ARGS__) +#if _NM_CC_SUPPORT_GENERIC +#define NM_UNCONST_PTR(type, arg) \ + _Generic ((arg), \ + const type *: ((type *) (arg)), \ + type *: ((type *) (arg))) +#else +#define NM_UNCONST_PTR(type, arg) \ + ((type *) (arg)) +#endif + +#if _NM_CC_SUPPORT_GENERIC +#define NM_UNCONST_PPTR(type, arg) \ + _Generic ((arg), \ + const type * *: ((type **) (arg)), \ + type * *: ((type **) (arg)), \ + const type *const*: ((type **) (arg)), \ + type *const*: ((type **) (arg))) +#else +#define NM_UNCONST_PPTR(type, arg) \ + ((type **) (arg)) +#endif + #define NM_GOBJECT_CAST(type, obj, is_check, ...) \ ({ \ const void *_obj = (obj); \ @@ -388,6 +441,41 @@ NM_G_ERROR_MSG (GError *error) #endif #if _NM_CC_SUPPORT_GENERIC +/* these macros cast (value) to + * - "const char **" (for "MC", mutable-const) + * - "const char *const*" (for "CC", const-const) + * The point is to do this cast, but only accepting pointers + * that are compatible already. + * + * The problem is, if you add a function like g_strdupv(), the input + * argument is not modified (CC), but you want to make it work also + * for "char **". C doesn't allow this form of casting (for good reasons), + * so the function makes a choice like g_strdupv(char**). That means, + * every time you want to call ith with a const argument, you need to + * explicitly cast it. + * + * These macros do the cast, but they only accept a compatible input + * type, otherwise they will fail compilation. + */ +#define NM_CAST_STRV_MC(value) \ + (_Generic ((value), \ + const char * *: (const char * *) (value), \ + char * *: (const char * *) (value), \ + void *: (const char * *) (value))) +#define NM_CAST_STRV_CC(value) \ + (_Generic ((value), \ + const char *const*: (const char *const*) (value), \ + const char * *: (const char *const*) (value), \ + char *const*: (const char *const*) (value), \ + char * *: (const char *const*) (value), \ + const void *: (const char *const*) (value), \ + void *: (const char *const*) (value))) +#else +#define NM_CAST_STRV_MC(value) ((const char * *) (value)) +#define NM_CAST_STRV_CC(value) ((const char *const*) (value)) +#endif + +#if _NM_CC_SUPPORT_GENERIC #define NM_PROPAGATE_CONST(test_expr, ptr) \ (_Generic ((test_expr), \ const typeof (*(test_expr)) *: ((const typeof (*(ptr)) *) (ptr)), \ @@ -741,6 +829,32 @@ nm_g_object_unref (gpointer obj) _changed; \ }) +#define nm_clear_pointer(pp, destroy) \ + ({ \ + typeof (*(pp)) *_pp = (pp); \ + typeof (*_pp) _p; \ + gboolean _changed = FALSE; \ + \ + if ( _pp \ + && (_p = *_pp)) { \ + _nm_unused gconstpointer _p_check_is_pointer = _p; \ + \ + *_pp = NULL; \ + /* g_clear_pointer() assigns @destroy first to a local variable, so that + * you can call "g_clear_pointer (pp, (GDestroyNotify) destroy);" without + * gcc emitting a warning. We don't do that, hence, you cannot cast + * "destroy" first. + * + * On the upside: you are not supposed to cast fcn, because the pointer + * types are preserved. If you really need a cast, you should cast @pp. + * But that is hardly ever necessary. */ \ + (destroy) (_p); \ + \ + _changed = TRUE; \ + } \ + _changed; \ + }) + /* basically, replaces * g_clear_pointer (&location, g_free) * with @@ -751,42 +865,20 @@ nm_g_object_unref (gpointer obj) * pointer or points to a const-pointer. */ #define nm_clear_g_free(pp) \ - ({ \ - typeof (*(pp)) *_pp = (pp); \ - typeof (**_pp) *_p; \ - gboolean _changed = FALSE; \ - \ - if ( _pp \ - && (_p = *_pp)) { \ - *_pp = NULL; \ - g_free (_p); \ - _changed = TRUE; \ - } \ - _changed; \ - }) + nm_clear_pointer (pp, g_free) #define nm_clear_g_object(pp) \ - ({ \ - typeof (*(pp)) *_pp = (pp); \ - typeof (**_pp) *_p; \ - gboolean _changed = FALSE; \ - \ - if ( _pp \ - && (_p = *_pp)) { \ - nm_assert (G_IS_OBJECT (_p)); \ - *_pp = NULL; \ - g_object_unref (_p); \ - _changed = TRUE; \ - } \ - _changed; \ - }) + nm_clear_pointer (pp, g_object_unref) static inline gboolean nm_clear_g_source (guint *id) { - if (id && *id) { - g_source_remove (*id); + guint v; + + if ( id + && (v = *id)) { *id = 0; + g_source_remove (v); return TRUE; } return FALSE; @@ -795,9 +887,12 @@ nm_clear_g_source (guint *id) static inline gboolean nm_clear_g_signal_handler (gpointer self, gulong *id) { - if (id && *id) { - g_signal_handler_disconnect (self, *id); + gulong v; + + if ( id + && (v = *id)) { *id = 0; + g_signal_handler_disconnect (self, v); return TRUE; } return FALSE; @@ -806,9 +901,12 @@ nm_clear_g_signal_handler (gpointer self, gulong *id) static inline gboolean nm_clear_g_variant (GVariant **variant) { - if (variant && *variant) { - g_variant_unref (*variant); + GVariant *v; + + if ( variant + && (v = *variant)) { *variant = NULL; + g_variant_unref (v); return TRUE; } return FALSE; @@ -817,10 +915,13 @@ nm_clear_g_variant (GVariant **variant) static inline gboolean nm_clear_g_cancellable (GCancellable **cancellable) { - if (cancellable && *cancellable) { - g_cancellable_cancel (*cancellable); - g_object_unref (*cancellable); + GCancellable *v; + + if ( cancellable + && (v = *cancellable)) { *cancellable = NULL; + g_cancellable_cancel (v); + g_object_unref (v); return TRUE; } return FALSE; @@ -991,35 +1092,6 @@ nm_strcmp_p (gconstpointer a, gconstpointer b) return strcmp (s1, s2); } -/* like nm_strcmp_p(), suitable for g_ptr_array_sort_with_data(). - * g_ptr_array_sort() just casts nm_strcmp_p() to a function of different - * signature. I guess, in glib there are knowledgeable people that ensure - * that this additional argument doesn't cause problems due to different ABI - * for every architecture that glib supports. - * For NetworkManager, we'd rather avoid such stunts. - **/ -static inline int -nm_strcmp_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data) -{ - const char *s1 = *((const char **) a); - const char *s2 = *((const char **) b); - - return strcmp (s1, s2); -} - -static inline int -nm_cmp_uint32_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data) -{ - const guint32 a = *((const guint32 *) p_a); - const guint32 b = *((const guint32 *) p_b); - - if (a < b) - return -1; - if (a > b) - return 1; - return 0; -} - /*****************************************************************************/ /* Taken from systemd's UNIQ_T and UNIQ macros. */ @@ -1150,6 +1222,28 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) _buf; \ }) +/* aims to alloca() a buffer and fill it with printf(format, name). + * Note that format must not contain any format specifier except + * "%s". + * If the resulting string would be too large for stack allocation, + * it allocates a buffer with g_malloc() and assigns it to *p_val_to_free. */ +#define nm_construct_name_a(format, name, p_val_to_free) \ + ({ \ + const char *const _name = (name); \ + char **const _p_val_to_free = (p_val_to_free); \ + const gsize _name_len = strlen (_name); \ + char *_buf2; \ + \ + nm_assert (_p_val_to_free && !*_p_val_to_free); \ + if (NM_STRLEN (format) + _name_len < 200) \ + _buf2 = nm_sprintf_bufa (NM_STRLEN (format) + _name_len, format, _name); \ + else { \ + _buf2 = g_strdup_printf (format, _name); \ + *_p_val_to_free = _buf2; \ + } \ + (const char *) _buf2; \ + }) + /*****************************************************************************/ /** @@ -1225,6 +1319,25 @@ nm_decode_version (guint version, guint *major, guint *minor, guint *micro) /*****************************************************************************/ +/** + * nm_steal_int: + * @p_val: pointer to an int type. + * + * Returns: *p_val and sets *p_val to zero the same time. + * Accepts %NULL, in which case also numeric 0 will be returned. + */ +#define nm_steal_int(p_val) \ + ({ \ + typeof (p_val) const _p_val = (p_val); \ + typeof (*_p_val) _val = 0; \ + \ + if ( _p_val \ + && (_val = *_p_val)) { \ + *_p_val = 0; \ + } \ + _val; \ + }) + static inline int nm_steal_fd (int *p_fd) { diff --git a/shared/nm-utils/nm-obj.h b/shared/nm-utils/nm-obj.h index 1a9d4868..4edd1f3e 100644 --- a/shared/nm-utils/nm-obj.h +++ b/shared/nm-utils/nm-obj.h @@ -56,7 +56,7 @@ struct _NMObjBaseClass { * Note that it is also an abstract super class of GTypeInstance, that means * you may implement a NMObjBaseClass as a subtype of GTypeClass. * - * For that to work, you must properly set the GTypeClass instance (and it's + * For that to work, you must properly set the GTypeClass instance (and its * GType). * * Note that to implement a NMObjBaseClass that is *not* a GTypeClass, you wouldn't diff --git a/shared/nm-utils/nm-shared-utils.c b/shared/nm-utils/nm-shared-utils.c index 0b343afd..6937065c 100644 --- a/shared/nm-utils/nm-shared-utils.c +++ b/shared/nm-utils/nm-shared-utils.c @@ -499,6 +499,56 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma /*****************************************************************************/ +/* like nm_strcmp_p(), suitable for g_ptr_array_sort_with_data(). + * g_ptr_array_sort() just casts nm_strcmp_p() to a function of different + * signature. I guess, in glib there are knowledgeable people that ensure + * that this additional argument doesn't cause problems due to different ABI + * for every architecture that glib supports. + * For NetworkManager, we'd rather avoid such stunts. + **/ +int +nm_strcmp_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data) +{ + const char *s1 = *((const char **) a); + const char *s2 = *((const char **) b); + + return strcmp (s1, s2); +} + +int +nm_cmp_uint32_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data) +{ + const guint32 a = *((const guint32 *) p_a); + const guint32 b = *((const guint32 *) p_b); + + if (a < b) + return -1; + if (a > b) + return 1; + return 0; +} + +int +nm_cmp_int2ptr_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data) +{ + /* p_a and p_b are two pointers to a pointer, where the pointer is + * interpreted as a integer using GPOINTER_TO_INT(). + * + * That is the case of a hash-table that uses GINT_TO_POINTER() to + * convert integers as pointers, and the resulting keys-as-array + * array. */ + const int a = GPOINTER_TO_INT (*((gconstpointer *) p_a)); + const int b = GPOINTER_TO_INT (*((gconstpointer *) p_b)); + + if (a < b) + return -1; + if (a > b) + return 1; + return 0; +} + +/*****************************************************************************/ + /** * nm_utils_strsplit_set: * @str: the string to split. @@ -1115,3 +1165,197 @@ nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll) return 0; } + +NMUtilsNamedValue * +nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len) +{ + GHashTableIter iter; + NMUtilsNamedValue *values; + guint i, len; + + if ( !hash + || !(len = g_hash_table_size (hash))) { + NM_SET_OUT (out_len, 0); + return NULL; + } + + i = 0; + values = g_new (NMUtilsNamedValue, len + 1); + g_hash_table_iter_init (&iter, hash); + while (g_hash_table_iter_next (&iter, + (gpointer *) &values[i].name, + (gpointer *) &values[i].value_ptr)) + i++; + nm_assert (i == len); + values[i].name = NULL; + values[i].value_ptr = NULL; + + if (len > 1) { + g_qsort_with_data (values, len, sizeof (values[0]), + nm_utils_named_entry_cmp_with_data, NULL); + } + + NM_SET_OUT (out_len, len); + return values; +} + +gpointer * +nm_utils_hash_keys_to_array (GHashTable *hash, + GCompareDataFunc compare_func, + gpointer user_data, + guint *out_len) +{ + guint len; + gpointer *keys; + + /* by convention, we never return an empty array. In that + * case, always %NULL. */ + if ( !hash + || g_hash_table_size (hash) == 0) { + NM_SET_OUT (out_len, 0); + return NULL; + } + + keys = g_hash_table_get_keys_as_array (hash, &len); + if ( len > 1 + && compare_func) { + g_qsort_with_data (keys, + len, + sizeof (gpointer), + compare_func, + user_data); + } + NM_SET_OUT (out_len, len); + return keys; +} + +char ** +nm_utils_strv_make_deep_copied (const char **strv) +{ + gsize i; + + /* it takes a strv dictionary, and copies each + * strings. Note that this updates @strv *in-place* + * and returns it. */ + + if (!strv) + return NULL; + for (i = 0; strv[i]; i++) + strv[i] = g_strdup (strv[i]); + + return (char **) strv; +} + +/*****************************************************************************/ + +/** + * nm_utils_get_start_time_for_pid: + * @pid: the process identifier + * @out_state: return the state character, like R, S, Z. See `man 5 proc`. + * @out_ppid: parent process id + * + * Originally copied from polkit source (src/polkit/polkitunixprocess.c) + * and adjusted. + * + * Returns: the timestamp when the process started (by parsing /proc/$PID/stat). + * If an error occurs (e.g. the process does not exist), 0 is returned. + * + * The returned start time counts since boot, in the unit HZ (with HZ usually being (1/100) seconds) + **/ +guint64 +nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid) +{ + guint64 start_time; + char filename[256]; + gs_free gchar *contents = NULL; + size_t length; + gs_strfreev gchar **tokens = NULL; + guint num_tokens; + gchar *p; + char state = ' '; + gint64 ppid = 0; + + start_time = 0; + contents = NULL; + + g_return_val_if_fail (pid > 0, 0); + + nm_sprintf_buf (filename, "/proc/%"G_GUINT64_FORMAT"/stat", (guint64) pid); + + if (!g_file_get_contents (filename, &contents, &length, NULL)) + goto fail; + + /* start time is the token at index 19 after the '(process name)' entry - since only this + * field can contain the ')' character, search backwards for this to avoid malicious + * processes trying to fool us + */ + p = strrchr (contents, ')'); + if (p == NULL) + goto fail; + p += 2; /* skip ') ' */ + if (p - contents >= (int) length) + goto fail; + + state = p[0]; + + tokens = g_strsplit (p, " ", 0); + + num_tokens = g_strv_length (tokens); + + if (num_tokens < 20) + goto fail; + + if (out_ppid) { + ppid = _nm_utils_ascii_str_to_int64 (tokens[1], 10, 1, G_MAXINT, 0); + if (ppid == 0) + goto fail; + } + + start_time = _nm_utils_ascii_str_to_int64 (tokens[19], 10, 1, G_MAXINT64, 0); + if (start_time == 0) + goto fail; + + NM_SET_OUT (out_state, state); + NM_SET_OUT (out_ppid, ppid); + return start_time; + +fail: + NM_SET_OUT (out_state, ' '); + NM_SET_OUT (out_ppid, 0); + return 0; +} + +/*****************************************************************************/ + +/** + * _nm_utils_strv_sort: + * @strv: pointer containing strings that will be sorted + * in-place, %NULL is allowed, unless @len indicates + * that there are more elements. + * @len: the number of elements in strv. If negative, + * strv must be a NULL terminated array and the length + * will be calculated first. If @len is a positive + * number, all first @len elements in @strv must be + * non-NULL, valid strings. + * + * Ascending sort of the array @strv inplace, using plain strcmp() string + * comparison. + */ +void +_nm_utils_strv_sort (const char **strv, gssize len) +{ + gsize l; + + l = len < 0 ? (gsize) NM_PTRARRAY_LEN (strv) : (gsize) len; + + if (l <= 1) + return; + + nm_assert (l <= (gsize) G_MAXINT); + + g_qsort_with_data (strv, + l, + sizeof (const char *), + nm_strcmp_p_with_data, + NULL); +} diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h index d6d829cd..84325bb7 100644 --- a/shared/nm-utils/nm-shared-utils.h +++ b/shared/nm-utils/nm-shared-utils.h @@ -326,12 +326,18 @@ _nm_g_slice_free_fcn_define (16) /* If mem_size is a compile time constant, the compiler * will be able to optimize this. Hence, you don't want * to call this with a non-constant size argument. */ \ - switch (mem_size) { \ + G_STATIC_ASSERT_EXPR ( ((mem_size) == 1) \ + || ((mem_size) == 2) \ + || ((mem_size) == 4) \ + || ((mem_size) == 8) \ + || ((mem_size) == 12) \ + || ((mem_size) == 16)); \ + switch ((mem_size)) { \ case 1: _fcn = _nm_g_slice_free_fcn_1; break; \ case 2: _fcn = _nm_g_slice_free_fcn_2; break; \ case 4: _fcn = _nm_g_slice_free_fcn_4; break; \ case 8: _fcn = _nm_g_slice_free_fcn_8; break; \ - case 12: _fcn = _nm_g_slice_free_fcn_12; break; \ + case 12: _fcn = _nm_g_slice_free_fcn_12; break; \ case 16: _fcn = _nm_g_slice_free_fcn_16; break; \ default: g_assert_not_reached (); _fcn = NULL; break; \ } \ @@ -415,6 +421,29 @@ char *nm_utils_str_utf8safe_unescape_cp (const char *str); char *nm_utils_str_utf8safe_escape_take (char *str, NMUtilsStrUtf8SafeFlags flags); +static inline void +nm_g_variant_unref_floating (GVariant *var) +{ + /* often a function wants to keep a reference to an input variant. + * It uses g_variant_ref_sink() to either increase the ref-count, + * or take ownership of a possibly floating reference. + * + * If the function doesn't actually want to do anything with the + * input variant, it still must make sure that a passed in floating + * reference is consumed. Hence, this helper which: + * + * - does nothing if @var is not floating + * - unrefs (consumes) @var if it is floating. */ + if (g_variant_is_floating (var)) + g_variant_unref (var); +} + +/*****************************************************************************/ + +int nm_strcmp_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data); +int nm_cmp_uint32_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data); +int nm_cmp_int2ptr_p_with_data (gconstpointer p_a, gconstpointer p_b, gpointer user_data); + /*****************************************************************************/ typedef struct { @@ -435,6 +464,35 @@ typedef struct { #define nm_utils_named_entry_cmp nm_strcmp_p #define nm_utils_named_entry_cmp_with_data nm_strcmp_p_with_data +NMUtilsNamedValue *nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_len); + +gpointer *nm_utils_hash_keys_to_array (GHashTable *hash, + GCompareDataFunc compare_func, + gpointer user_data, + guint *out_len); + +static inline const char ** +nm_utils_strdict_get_keys (const GHashTable *hash, + gboolean sorted, + guint *out_length) +{ + return (const char **) nm_utils_hash_keys_to_array ((GHashTable *) hash, + sorted ? nm_strcmp_p_with_data : NULL, + NULL, + out_length); +} + +char **nm_utils_strv_make_deep_copied (const char **strv); + +static inline char ** +nm_utils_strv_make_deep_copied_nonnull (const char **strv) +{ + return nm_utils_strv_make_deep_copied (strv) ?: g_new0 (char *, 1); +} + +void _nm_utils_strv_sort (const char **strv, gssize len); +#define nm_utils_strv_sort(strv, len) _nm_utils_strv_sort (NM_CAST_STRV_MC (strv), len) + /*****************************************************************************/ #define NM_UTILS_NS_PER_SECOND ((gint64) 1000000000) @@ -449,4 +507,108 @@ int nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll) /*****************************************************************************/ +static inline const char * +nm_utils_dbus_normalize_object_path (const char *path) +{ + /* D-Bus does not allow an empty object path. Hence, whenever we mean NULL / no-object + * on D-Bus, it's path is actually "/". + * + * Normalize that away, and return %NULL in that case. */ + if (path && path[0] == '/' && path[1] == '\0') + return NULL; + return path; +} + +#define NM_DEFINE_GDBUS_ARG_INFO_FULL(name_, ...) \ + ((GDBusArgInfo *) (&((const GDBusArgInfo) { \ + .ref_count = -1, \ + .name = name_, \ + __VA_ARGS__ \ + }))) + +#define NM_DEFINE_GDBUS_ARG_INFO(name_, a_signature) \ + NM_DEFINE_GDBUS_ARG_INFO_FULL ( \ + name_, \ + .signature = a_signature, \ + ) + +#define NM_DEFINE_GDBUS_ARG_INFOS(...) \ + ((GDBusArgInfo **) ((const GDBusArgInfo *[]) { \ + __VA_ARGS__ \ + NULL, \ + })) + +#define NM_DEFINE_GDBUS_PROPERTY_INFO(name_, ...) \ + ((GDBusPropertyInfo *) (&((const GDBusPropertyInfo) { \ + .ref_count = -1, \ + .name = name_, \ + __VA_ARGS__ \ + }))) + +#define NM_DEFINE_GDBUS_PROPERTY_INFO_READABLE(name_, m_signature) \ + NM_DEFINE_GDBUS_PROPERTY_INFO ( \ + name_, \ + .signature = m_signature, \ + .flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE, \ + ) + +#define NM_DEFINE_GDBUS_PROPERTY_INFOS(...) \ + ((GDBusPropertyInfo **) ((const GDBusPropertyInfo *[]) { \ + __VA_ARGS__ \ + NULL, \ + })) + +#define NM_DEFINE_GDBUS_SIGNAL_INFO_INIT(name_, ...) \ + { \ + .ref_count = -1, \ + .name = name_, \ + __VA_ARGS__ \ + } + +#define NM_DEFINE_GDBUS_SIGNAL_INFO(name_, ...) \ + ((GDBusSignalInfo *) (&((const GDBusSignalInfo) NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (name_, __VA_ARGS__)))) + +#define NM_DEFINE_GDBUS_SIGNAL_INFOS(...) \ + ((GDBusSignalInfo **) ((const GDBusSignalInfo *[]) { \ + __VA_ARGS__ \ + NULL, \ + })) + +#define NM_DEFINE_GDBUS_METHOD_INFO_INIT(name_, ...) \ + { \ + .ref_count = -1, \ + .name = name_, \ + __VA_ARGS__ \ + } + +#define NM_DEFINE_GDBUS_METHOD_INFO(name_, ...) \ + ((GDBusMethodInfo *) (&((const GDBusMethodInfo) NM_DEFINE_GDBUS_METHOD_INFO_INIT (name_, __VA_ARGS__)))) + +#define NM_DEFINE_GDBUS_METHOD_INFOS(...) \ + ((GDBusMethodInfo **) ((const GDBusMethodInfo *[]) { \ + __VA_ARGS__ \ + NULL, \ + })) + +#define NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(name_, ...) \ + { \ + .ref_count = -1, \ + .name = name_, \ + __VA_ARGS__ \ + } + +#define NM_DEFINE_GDBUS_INTERFACE_INFO(name_, ...) \ + ((GDBusInterfaceInfo *) (&((const GDBusInterfaceInfo) NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (name_, __VA_ARGS__)))) + +#define NM_DEFINE_GDBUS_INTERFACE_VTABLE(...) \ + ((GDBusInterfaceVTable *) (&((const GDBusInterfaceVTable) { \ + __VA_ARGS__ \ + }))) + +/*****************************************************************************/ + +guint64 nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid); + +/*****************************************************************************/ + #endif /* __NM_SHARED_UTILS_H__ */ diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h index 126546ec..cc33a1ae 100644 --- a/shared/nm-utils/nm-test-utils.h +++ b/shared/nm-utils/nm-test-utils.h @@ -21,6 +21,10 @@ #ifndef __NM_TEST_UTILS_H__ #define __NM_TEST_UTILS_H__ +#if defined(NETWORKMANAGER_COMPILATION) && !defined(NETWORKMANAGER_COMPILATION_TEST) +#error Need to mark the compilation with NETWORKMANAGER_COMPILATION_TEST. +#endif + /******************************************************************************* * HOWTO run tests. * @@ -158,6 +162,14 @@ g_assert_not_reached (); \ } G_STMT_END +#define nmtst_assert_nonnull(command) \ + ({ \ + typeof (*(command)) *_ptr = (command); \ + \ + g_assert (_ptr && (TRUE || (command))); \ + _ptr; \ + }) + #define nmtst_assert_success(success, error) \ G_STMT_START { \ g_assert_no_error (error); \ @@ -328,8 +340,6 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ __nmtst_internal.assert_logging = !!assert_logging; - nm_g_type_init (); - is_debug = g_test_verbose (); nmtst_debug = g_getenv ("NMTST_DEBUG"); @@ -424,6 +434,11 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ g_array_append_val (debug_messages, msg); } } else { + /* We're intentionally assigning a value to static variables + * s_tests_x and p_tests_x without using it afterwards, just + * so that valgrind doesn't complain about the leak. */ + NM_PRAGMA_WARNING_DISABLE("-Wunused-but-set-variable") + /* g_test_init() is a variadic function, so we cannot pass it * (variadic) arguments. If you need to pass additional parameters, * call nmtst_init() with argc==NULL and call g_test_init() yourself. */ @@ -497,6 +512,8 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ s_tests = NULL; } } + + NM_PRAGMA_WARNING_REENABLE } if (test_quick_set) @@ -529,13 +546,8 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ *out_set_logging = TRUE; #endif g_assert (success); -#if GLIB_CHECK_VERSION(2,34,0) if (__nmtst_internal.no_expect_message) g_log_set_always_fatal (G_LOG_FATAL_MASK); -#else - /* g_test_expect_message() is a NOP, so allow any messages */ - g_log_set_always_fatal (G_LOG_FATAL_MASK); -#endif } else if (__nmtst_internal.no_expect_message) { /* We have a test that would be assert_logging, but the user specified no_expect_message. * This transforms g_test_expect_message() into a NOP, but we also have to relax @@ -555,14 +567,9 @@ __nmtst_init (int *argc, char ***argv, gboolean assert_logging, const char *log_ } #endif } else { -#if GLIB_CHECK_VERSION(2,34,0) /* We were called not to set logging levels. This means, that the user * expects to assert against (all) messages. Any uncought message is fatal. */ g_log_set_always_fatal (G_LOG_LEVEL_MASK); -#else - /* g_test_expect_message() is a NOP, so allow any messages */ - g_log_set_always_fatal (G_LOG_FATAL_MASK); -#endif } if ((!__nmtst_internal.assert_logging || (__nmtst_internal.assert_logging && __nmtst_internal.no_expect_message)) && @@ -629,7 +636,6 @@ nmtst_test_quick (void) return __nmtst_internal.test_quick; } -#if GLIB_CHECK_VERSION(2,34,0) #undef g_test_expect_message #define g_test_expect_message(...) \ G_STMT_START { \ @@ -637,9 +643,7 @@ nmtst_test_quick (void) if (__nmtst_internal.assert_logging && __nmtst_internal.no_expect_message) { \ g_debug ("nmtst: assert-logging: g_test_expect_message %s", G_STRINGIFY ((__VA_ARGS__))); \ } else { \ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ g_test_expect_message (__VA_ARGS__); \ - G_GNUC_END_IGNORE_DEPRECATIONS \ } \ } G_STMT_END #undef g_test_assert_expected_messages_internal @@ -653,10 +657,21 @@ nmtst_test_quick (void) if (__nmtst_internal.assert_logging && __nmtst_internal.no_expect_message) \ g_debug ("nmtst: assert-logging: g_test_assert_expected_messages(%s, %s:%d, %s)", _domain?:"", _file?:"", _line, _func?:""); \ \ - G_GNUC_BEGIN_IGNORE_DEPRECATIONS \ g_test_assert_expected_messages_internal (_domain, _file, _line, _func); \ - G_GNUC_END_IGNORE_DEPRECATIONS \ } G_STMT_END + +#define NMTST_EXPECT(domain, level, msg) g_test_expect_message (domain, level, msg) + +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL +#define NMTST_EXPECT_LIBNM_U(level, msg) NMTST_EXPECT ("libnm-util", level, msg) +#define NMTST_EXPECT_LIBNM_G(level, msg) NMTST_EXPECT ("libnm-glib", level, msg) + +#define NMTST_EXPECT_LIBNM_U_CRITICAL(msg) NMTST_EXPECT_LIBNM_U (G_LOG_LEVEL_CRITICAL, msg) +#define NMTST_EXPECT_LIBNM_G_CRITICAL(msg) NMTST_EXPECT_LIBNM_G (G_LOG_LEVEL_CRITICAL, msg) +#else +#define NMTST_EXPECT_LIBNM(level, msg) NMTST_EXPECT ("libnm", level, msg) + +#define NMTST_EXPECT_LIBNM_CRITICAL(msg) NMTST_EXPECT_LIBNM (G_LOG_LEVEL_CRITICAL, msg) #endif /*****************************************************************************/ @@ -801,6 +816,12 @@ nmtst_get_rand_int (void) return g_rand_int (nmtst_get_rand ()); } +static inline gboolean +nmtst_get_rand_bool (void) +{ + return nmtst_get_rand_int () % 2; +} + static inline gpointer nmtst_rand_buf (GRand *rand, gpointer buffer, gsize buffer_length) { @@ -912,7 +933,7 @@ _nmtst_main_loop_run_timeout (gpointer user_data) } static inline gboolean -nmtst_main_loop_run (GMainLoop *loop, int timeout_ms) +nmtst_main_loop_run (GMainLoop *loop, guint timeout_ms) { GSource *source = NULL; guint id = 0; @@ -928,6 +949,9 @@ nmtst_main_loop_run (GMainLoop *loop, int timeout_ms) g_main_loop_run (loop); + if (source && loopx) + g_source_destroy (source); + /* if the timeout was reached, return FALSE. */ return loopx != NULL; } @@ -1499,13 +1523,12 @@ _nmtst_connection_normalize (NMConnection *connection, ...) static inline NMConnection * _nmtst_connection_duplicate_and_normalize (NMConnection *connection, ...) { - gboolean was_modified; va_list args; connection = nmtst_clone_connection (connection); va_start (args, connection); - was_modified = _nmtst_connection_normalize_v (connection, args); + _nmtst_connection_normalize_v (connection, args); va_end (args); return connection; @@ -1697,7 +1720,7 @@ nmtst_assert_setting_verifies (NMSetting *setting) g_assert (success); } -#if defined(__NM_SIMPLE_CONNECTION_H__) +#if defined(__NM_SIMPLE_CONNECTION_H__) && NM_CHECK_VERSION (1, 10, 0) && (!defined (NM_VERSION_MAX_ALLOWED) || NM_VERSION_MAX_ALLOWED >= NM_VERSION_1_10) static inline void _nmtst_assert_connection_has_settings (NMConnection *connection, gboolean has_at_least, gboolean has_at_most, ...) { @@ -1715,7 +1738,7 @@ _nmtst_assert_connection_has_settings (NMConnection *connection, gboolean has_at va_start (ap, has_at_most); while ((name = va_arg (ap, const char *))) { - if (!nm_g_hash_table_add (names, (gpointer) name)) + if (!g_hash_table_add (names, (gpointer) name)) g_assert_not_reached (); g_ptr_array_add (names_arr, (gpointer) name); } @@ -1751,8 +1774,7 @@ _nmtst_assert_connection_has_settings (NMConnection *connection, gboolean has_at #define nmtst_assert_connection_has_settings(connection, ...) _nmtst_assert_connection_has_settings ((connection), TRUE, TRUE, __VA_ARGS__, NULL) #define nmtst_assert_connection_has_settings_at_least(connection, ...) _nmtst_assert_connection_has_settings ((connection), TRUE, FALSE, __VA_ARGS__, NULL) #define nmtst_assert_connection_has_settings_at_most(connection, ...) _nmtst_assert_connection_has_settings ((connection), FALSE, TRUE, __VA_ARGS__, NULL) - -#endif /* __NM_SIMPLE_CONNECTION_H__ */ +#endif static inline void nmtst_assert_setting_verify_fails (NMSetting *setting, diff --git a/shared/nm-utils/nm-udev-utils.c b/shared/nm-utils/nm-udev-utils.c index 79d4426d..709f7590 100644 --- a/shared/nm-utils/nm-udev-utils.c +++ b/shared/nm-utils/nm-udev-utils.c @@ -257,7 +257,7 @@ nm_udev_client_new (const char *const*subsystems, channel = g_io_channel_unix_new (udev_monitor_get_fd (self->monitor)); self->watch_source = g_io_create_watch (channel, G_IO_IN); g_io_channel_unref (channel); - g_source_set_callback (self->watch_source, (GSourceFunc) monitor_event, self, NULL); + g_source_set_callback (self->watch_source, (GSourceFunc)(void (*) (void)) monitor_event, self, NULL); g_source_attach (self->watch_source, g_main_context_get_thread_default ()); g_source_unref (self->watch_source); } diff --git a/shared/nm-utils/siphash24.c b/shared/nm-utils/siphash24.c index 3a5a635d..8e59afb2 100644 --- a/shared/nm-utils/siphash24.c +++ b/shared/nm-utils/siphash24.c @@ -19,7 +19,8 @@ #include "nm-default.h" -#define assert(cond) nm_assert (cond) +#define assert(cond) nm_assert (cond) +#define _fallthrough_ _nm_fallthrough #include <stdio.h> @@ -130,25 +131,25 @@ void siphash24_compress(const void *_in, size_t inlen, struct siphash *state) { switch (left) { case 7: state->padding |= ((uint64_t) in[6]) << 48; - /* fall through */ + _fallthrough_; case 6: state->padding |= ((uint64_t) in[5]) << 40; - /* fall through */ + _fallthrough_; case 5: state->padding |= ((uint64_t) in[4]) << 32; - /* fall through */ + _fallthrough_; case 4: state->padding |= ((uint64_t) in[3]) << 24; - /* fall through */ + _fallthrough_; case 3: state->padding |= ((uint64_t) in[2]) << 16; - /* fall through */ + _fallthrough_; case 2: state->padding |= ((uint64_t) in[1]) << 8; - /* fall through */ + _fallthrough_; case 1: state->padding |= ((uint64_t) in[0]); - /* fall through */ + _fallthrough_; case 0: break; } diff --git a/shared/nm-utils/unaligned.h b/shared/nm-utils/unaligned.h index 7c847a3c..73302b42 100644 --- a/shared/nm-utils/unaligned.h +++ b/shared/nm-utils/unaligned.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ #pragma once /*** @@ -25,89 +26,77 @@ /* BE */ static inline uint16_t unaligned_read_be16(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; - return (((uint16_t) u[0]) << 8) | - ((uint16_t) u[1]); + return be16toh(u->x); } static inline uint32_t unaligned_read_be32(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; - return (((uint32_t) unaligned_read_be16(u)) << 16) | - ((uint32_t) unaligned_read_be16(u + 2)); + return be32toh(u->x); } static inline uint64_t unaligned_read_be64(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; - return (((uint64_t) unaligned_read_be32(u)) << 32) | - ((uint64_t) unaligned_read_be32(u + 4)); + return be64toh(u->x); } static inline void unaligned_write_be16(void *_u, uint16_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; - u[0] = (uint8_t) (a >> 8); - u[1] = (uint8_t) a; + u->x = be16toh(a); } static inline void unaligned_write_be32(void *_u, uint32_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; - unaligned_write_be16(u, (uint16_t) (a >> 16)); - unaligned_write_be16(u + 2, (uint16_t) a); + u->x = be32toh(a); } static inline void unaligned_write_be64(void *_u, uint64_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; - unaligned_write_be32(u, (uint32_t) (a >> 32)); - unaligned_write_be32(u + 4, (uint32_t) a); + u->x = be64toh(a); } /* LE */ static inline uint16_t unaligned_read_le16(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; - return (((uint16_t) u[1]) << 8) | - ((uint16_t) u[0]); + return le16toh(u->x); } static inline uint32_t unaligned_read_le32(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; - return (((uint32_t) unaligned_read_le16(u + 2)) << 16) | - ((uint32_t) unaligned_read_le16(u)); + return le32toh(u->x); } static inline uint64_t unaligned_read_le64(const void *_u) { - const uint8_t *u = _u; + const struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; - return (((uint64_t) unaligned_read_le32(u + 4)) << 32) | - ((uint64_t) unaligned_read_le32(u)); + return le64toh(u->x); } static inline void unaligned_write_le16(void *_u, uint16_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint16_t x; } *u = _u; - u[0] = (uint8_t) a; - u[1] = (uint8_t) (a >> 8); + u->x = le16toh(a); } static inline void unaligned_write_le32(void *_u, uint32_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint32_t x; } *u = _u; - unaligned_write_le16(u, (uint16_t) a); - unaligned_write_le16(u + 2, (uint16_t) (a >> 16)); + u->x = le32toh(a); } static inline void unaligned_write_le64(void *_u, uint64_t a) { - uint8_t *u = _u; + struct __attribute__((packed, may_alias)) { uint64_t x; } *u = _u; - unaligned_write_le32(u, (uint32_t) a); - unaligned_write_le32(u + 4, (uint32_t) (a >> 32)); + u->x = le64toh(a); } #if __BYTE_ORDER == __BIG_ENDIAN diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h index b309997c..777bff80 100644 --- a/shared/nm-version-macros.h +++ b/shared/nm-version-macros.h @@ -37,7 +37,7 @@ * Evaluates to the minor version number of NetworkManager which this source * is compiled against. */ -#define NM_MINOR_VERSION (10) +#define NM_MINOR_VERSION (11) /** * NM_MICRO_VERSION: @@ -45,7 +45,7 @@ * Evaluates to the micro version number of NetworkManager which this source * compiled against. */ -#define NM_MICRO_VERSION (6) +#define NM_MICRO_VERSION (3) /** * NM_CHECK_VERSION: @@ -72,12 +72,25 @@ #define NM_VERSION_1_6 (NM_ENCODE_VERSION (1, 6, 0)) #define NM_VERSION_1_8 (NM_ENCODE_VERSION (1, 8, 0)) #define NM_VERSION_1_10 (NM_ENCODE_VERSION (1, 10, 0)) -#define NM_VERSION_1_10_2 (NM_ENCODE_VERSION (1, 10, 2)) -#define NM_VERSION_1_10_4 (NM_ENCODE_VERSION (1, 10, 4)) -#define NM_VERSION_1_10_6 (NM_ENCODE_VERSION (1, 10, 6)) +#define NM_VERSION_1_12 (NM_ENCODE_VERSION (1, 12, 0)) -#define NM_VERSION_CUR_STABLE NM_VERSION_1_10_6 -#define NM_VERSION_NEXT_STABLE NM_VERSION_1_10_6 +/* For releases, NM_API_VERSION is equal to NM_VERSION. + * + * For development builds, NM_API_VERSION is the next + * stable API after NM_VERSION. When you run a development + * version, you are already using the future API, even if + * it is not yet release. Hence, the currently used API + * version is the future one. */ +#define NM_API_VERSION \ + (((NM_MINOR_VERSION % 2) == 1) \ + ? NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION + 1, 0 ) \ + : NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION , ((NM_MICRO_VERSION + 1) / 2) * 2)) + +/* deprecated. */ +#define NM_VERSION_CUR_STABLE NM_API_VERSION + +/* deprecated. */ +#define NM_VERSION_NEXT_STABLE NM_API_VERSION #define NM_VERSION NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION, NM_MICRO_VERSION) diff --git a/shared/nm-version-macros.h.in b/shared/nm-version-macros.h.in index f92bed82..8d07fc82 100644 --- a/shared/nm-version-macros.h.in +++ b/shared/nm-version-macros.h.in @@ -72,12 +72,25 @@ #define NM_VERSION_1_6 (NM_ENCODE_VERSION (1, 6, 0)) #define NM_VERSION_1_8 (NM_ENCODE_VERSION (1, 8, 0)) #define NM_VERSION_1_10 (NM_ENCODE_VERSION (1, 10, 0)) -#define NM_VERSION_1_10_2 (NM_ENCODE_VERSION (1, 10, 2)) -#define NM_VERSION_1_10_4 (NM_ENCODE_VERSION (1, 10, 4)) -#define NM_VERSION_1_10_6 (NM_ENCODE_VERSION (1, 10, 6)) +#define NM_VERSION_1_12 (NM_ENCODE_VERSION (1, 12, 0)) -#define NM_VERSION_CUR_STABLE NM_VERSION_1_10_6 -#define NM_VERSION_NEXT_STABLE NM_VERSION_1_10_6 +/* For releases, NM_API_VERSION is equal to NM_VERSION. + * + * For development builds, NM_API_VERSION is the next + * stable API after NM_VERSION. When you run a development + * version, you are already using the future API, even if + * it is not yet release. Hence, the currently used API + * version is the future one. */ +#define NM_API_VERSION \ + (((NM_MINOR_VERSION % 2) == 1) \ + ? NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION + 1, 0 ) \ + : NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION , ((NM_MICRO_VERSION + 1) / 2) * 2)) + +/* deprecated. */ +#define NM_VERSION_CUR_STABLE NM_API_VERSION + +/* deprecated. */ +#define NM_VERSION_NEXT_STABLE NM_API_VERSION #define NM_VERSION NM_ENCODE_VERSION (NM_MAJOR_VERSION, NM_MINOR_VERSION, NM_MICRO_VERSION) |