diff options
Diffstat (limited to 'shared')
142 files changed, 13327 insertions, 1325 deletions
diff --git a/shared/c-rbtree/src/c-rbtree-private.h b/shared/c-rbtree/src/c-rbtree-private.h index 25b9ba01..c9befbf3 100644 --- a/shared/c-rbtree/src/c-rbtree-private.h +++ b/shared/c-rbtree/src/c-rbtree-private.h @@ -6,16 +6,11 @@ * which are used by our test-suite. */ +#include <c-stdaux.h> #include <stddef.h> #include "c-rbtree.h" /* - * Macros - */ - -#define _public_ __attribute__((__visibility__("default"))) - -/* * Nodes */ diff --git a/shared/c-rbtree/src/c-rbtree.c b/shared/c-rbtree/src/c-rbtree.c index 31d74300..aacdcc29 100644 --- a/shared/c-rbtree/src/c-rbtree.c +++ b/shared/c-rbtree/src/c-rbtree.c @@ -24,11 +24,11 @@ */ #include <assert.h> +#include <c-stdaux.h> #include <stdalign.h> #include <stddef.h> - -#include "c-rbtree-private.h" #include "c-rbtree.h" +#include "c-rbtree-private.h" /* * We use alignas(8) to enforce 64bit alignment of structure fields. This is @@ -53,7 +53,7 @@ static_assert(alignof(CRBTree) >= 8, "Invalid CRBTree alignment"); * * Return: Pointer to leftmost child, or NULL. */ -_public_ CRBNode *c_rbnode_leftmost(CRBNode *n) { +_c_public_ CRBNode *c_rbnode_leftmost(CRBNode *n) { if (n) while (n->left) n = n->left; @@ -72,7 +72,7 @@ _public_ CRBNode *c_rbnode_leftmost(CRBNode *n) { * * Return: Pointer to rightmost child, or NULL. */ -_public_ CRBNode *c_rbnode_rightmost(CRBNode *n) { +_c_public_ CRBNode *c_rbnode_rightmost(CRBNode *n) { if (n) while (n->right) n = n->right; @@ -94,7 +94,7 @@ _public_ CRBNode *c_rbnode_rightmost(CRBNode *n) { * * Return: Pointer to left-deepest child, or NULL. */ -_public_ CRBNode *c_rbnode_leftdeepest(CRBNode *n) { +_c_public_ CRBNode *c_rbnode_leftdeepest(CRBNode *n) { if (n) { for (;;) { if (n->left) @@ -123,7 +123,7 @@ _public_ CRBNode *c_rbnode_leftdeepest(CRBNode *n) { * * Return: Pointer to right-deepest child, or NULL. */ -_public_ CRBNode *c_rbnode_rightdeepest(CRBNode *n) { +_c_public_ CRBNode *c_rbnode_rightdeepest(CRBNode *n) { if (n) { for (;;) { if (n->right) @@ -149,7 +149,7 @@ _public_ CRBNode *c_rbnode_rightdeepest(CRBNode *n) { * * Return: Pointer to next node, or NULL. */ -_public_ CRBNode *c_rbnode_next(CRBNode *n) { +_c_public_ CRBNode *c_rbnode_next(CRBNode *n) { CRBNode *p; if (!c_rbnode_is_linked(n)) @@ -175,7 +175,7 @@ _public_ CRBNode *c_rbnode_next(CRBNode *n) { * * Return: Pointer to previous node, or NULL. */ -_public_ CRBNode *c_rbnode_prev(CRBNode *n) { +_c_public_ CRBNode *c_rbnode_prev(CRBNode *n) { CRBNode *p; if (!c_rbnode_is_linked(n)) @@ -209,7 +209,7 @@ _public_ CRBNode *c_rbnode_prev(CRBNode *n) { * * Return: Pointer to next node, or NULL. */ -_public_ CRBNode *c_rbnode_next_postorder(CRBNode *n) { +_c_public_ CRBNode *c_rbnode_next_postorder(CRBNode *n) { CRBNode *p; if (!c_rbnode_is_linked(n)) @@ -253,7 +253,7 @@ _public_ CRBNode *c_rbnode_next_postorder(CRBNode *n) { * * Return: Pointer to previous node in post-order, or NULL. */ -_public_ CRBNode *c_rbnode_prev_postorder(CRBNode *n) { +_c_public_ CRBNode *c_rbnode_prev_postorder(CRBNode *n) { CRBNode *p; if (!c_rbnode_is_linked(n)) @@ -283,8 +283,8 @@ _public_ CRBNode *c_rbnode_prev_postorder(CRBNode *n) { * * Return: Pointer to first node, or NULL. */ -_public_ CRBNode *c_rbtree_first(CRBTree *t) { - assert(t); +_c_public_ CRBNode *c_rbtree_first(CRBTree *t) { + c_assert(t); return c_rbnode_leftmost(t->root); } @@ -299,8 +299,8 @@ _public_ CRBNode *c_rbtree_first(CRBTree *t) { * * Return: Pointer to last node, or NULL. */ -_public_ CRBNode *c_rbtree_last(CRBTree *t) { - assert(t); +_c_public_ CRBNode *c_rbtree_last(CRBTree *t) { + c_assert(t); return c_rbnode_rightmost(t->root); } @@ -319,8 +319,8 @@ _public_ CRBNode *c_rbtree_last(CRBTree *t) { * * Return: Pointer to first node in post-order, or NULL. */ -_public_ CRBNode *c_rbtree_first_postorder(CRBTree *t) { - assert(t); +_c_public_ CRBNode *c_rbtree_first_postorder(CRBTree *t) { + c_assert(t); return c_rbnode_leftdeepest(t->root); } @@ -338,8 +338,8 @@ _public_ CRBNode *c_rbtree_first_postorder(CRBTree *t) { * * Return: Pointer to last node in post-order, or NULL. */ -_public_ CRBNode *c_rbtree_last_postorder(CRBTree *t) { - assert(t); +_c_public_ CRBNode *c_rbtree_last_postorder(CRBTree *t) { + c_assert(t); return t->root; } @@ -452,15 +452,14 @@ static inline void c_rbnode_swap_child(CRBNode *old, CRBNode *new) { * Note that this operates in O(1) time. Only the root-entry is updated to * point to the new tree-root. */ -_public_ void c_rbtree_move(CRBTree *to, CRBTree *from) { +_c_public_ void c_rbtree_move(CRBTree *to, CRBTree *from) { CRBTree *t; - assert(!to->root); + c_assert(!to->root); if (from->root) { t = c_rbnode_pop_root(from->root); - assert(t == from); - (void)t; + c_assert(t == from); to->root = from->root; from->root = NULL; @@ -487,10 +486,10 @@ static inline void c_rbtree_paint_terminal(CRBNode *n) { g = c_rbnode_parent(p); gg = c_rbnode_parent(g); - assert(c_rbnode_is_red(p)); - assert(c_rbnode_is_black(g)); - assert(p == g->left || !g->left || c_rbnode_is_black(g->left)); - assert(p == g->right || !g->right || c_rbnode_is_black(g->right)); + c_assert(c_rbnode_is_red(p)); + c_assert(c_rbnode_is_black(g)); + c_assert(p == g->left || !g->left || c_rbnode_is_black(g->left)); + c_assert(p == g->right || !g->right || c_rbnode_is_black(g->right)); if (p == g->left) { if (n == p->right) { @@ -674,11 +673,11 @@ static inline void c_rbtree_paint(CRBNode *n) { * In most cases you are better off using c_rbtree_add(). See there for details * how tree-insertion works. */ -_public_ void c_rbnode_link(CRBNode *p, CRBNode **l, CRBNode *n) { - assert(p); - assert(l); - assert(n); - assert(l == &p->left || l == &p->right); +_c_public_ void c_rbnode_link(CRBNode *p, CRBNode **l, CRBNode *n) { + c_assert(p); + c_assert(l); + c_assert(n); + c_assert(l == &p->left || l == &p->right); c_rbnode_set_parent_and_flags(n, p, C_RBNODE_RED); c_rbtree_store(&n->left, NULL); @@ -739,12 +738,12 @@ _public_ void c_rbnode_link(CRBNode *p, CRBNode **l, CRBNode *n) { * than c_rbnode_unlink_stale()). In those cases, you should validate that a * node is unlinked before you call c_rbtree_add(). */ -_public_ void c_rbtree_add(CRBTree *t, CRBNode *p, CRBNode **l, CRBNode *n) { - assert(t); - assert(l); - assert(n); - assert(!p || l == &p->left || l == &p->right); - assert(p || l == &t->root); +_c_public_ void c_rbtree_add(CRBTree *t, CRBNode *p, CRBNode **l, CRBNode *n) { + c_assert(t); + c_assert(l); + c_assert(n); + c_assert(!p || l == &p->left || l == &p->right); + c_assert(p || l == &t->root); c_rbnode_set_parent_and_flags(n, p, C_RBNODE_RED); c_rbtree_store(&n->left, NULL); @@ -796,7 +795,7 @@ static inline void c_rbnode_rebalance_terminal(CRBNode *p, CRBNode *previous) { * Note that the parent must be red, otherwise * it must have been handled by our caller. */ - assert(c_rbnode_is_red(p)); + c_assert(c_rbnode_is_red(p)); c_rbnode_set_parent_and_flags(s, p, c_rbnode_flags(s) | C_RBNODE_RED); c_rbnode_set_parent_and_flags(p, c_rbnode_parent(p), c_rbnode_flags(p) & ~C_RBNODE_RED); return; @@ -856,7 +855,7 @@ static inline void c_rbnode_rebalance_terminal(CRBNode *p, CRBNode *previous) { if (!x || c_rbnode_is_black(x)) { y = s->right; if (!y || c_rbnode_is_black(y)) { - assert(c_rbnode_is_red(p)); + c_assert(c_rbnode_is_red(p)); c_rbnode_set_parent_and_flags(s, p, c_rbnode_flags(s) | C_RBNODE_RED); c_rbnode_set_parent_and_flags(p, c_rbnode_parent(p), c_rbnode_flags(p) & ~C_RBNODE_RED); return; @@ -963,11 +962,11 @@ static inline void c_rbnode_rebalance(CRBNode *n) { * This does *NOT* reset @n to being unlinked. If you need this, use * c_rbtree_unlink(). */ -_public_ void c_rbnode_unlink_stale(CRBNode *n) { +_c_public_ void c_rbnode_unlink_stale(CRBNode *n) { CRBTree *t; - assert(n); - assert(c_rbnode_is_linked(n)); + c_assert(n); + c_assert(c_rbnode_is_linked(n)); /* * There are three distinct cases during node removal of a tree: diff --git a/shared/c-siphash/src/c-siphash.c b/shared/c-siphash/src/c-siphash.c index 5cea6f2b..fae3abad 100644 --- a/shared/c-siphash/src/c-siphash.c +++ b/shared/c-siphash/src/c-siphash.c @@ -11,12 +11,11 @@ * C_siphash_finalize_Y() can be easily provided, if required. */ +#include <c-stdaux.h> #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) | @@ -68,7 +67,7 @@ static inline void c_siphash_sipround(CSipHash *state) { * 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]) { +_c_public_ void c_siphash_init(CSipHash *state, const uint8_t seed[16]) { uint64_t k0, k1; k0 = c_siphash_read_le64(seed); @@ -105,7 +104,7 @@ _public_ void c_siphash_init(CSipHash *state, const uint8_t seed[16]) { * 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) { +_c_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; @@ -195,7 +194,7 @@ _public_ void c_siphash_append(CSipHash *state, const uint8_t *bytes, size_t n_b * * Return: 64bit hash value */ -_public_ uint64_t c_siphash_finalize(CSipHash *state) { +_c_public_ uint64_t c_siphash_finalize(CSipHash *state) { uint64_t b; b = state->padding | (((uint64_t) state->n_bytes) << 56); @@ -236,7 +235,7 @@ _public_ uint64_t c_siphash_finalize(CSipHash *state) { * * Return: 64bit hash value */ -_public_ uint64_t c_siphash_hash(const uint8_t seed[16], const uint8_t *bytes, size_t n_bytes) { +_c_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); diff --git a/shared/c-stdaux/src/c-stdaux.h b/shared/c-stdaux/src/c-stdaux.h new file mode 100644 index 00000000..08d155ce --- /dev/null +++ b/shared/c-stdaux/src/c-stdaux.h @@ -0,0 +1,546 @@ +#pragma once + +/* + * Auxiliary macros and functions for the C standard library + * + * The `c-stdaux.h` header contains a collection of auxiliary macros and helper + * functions around the functionality provided by the different C standard + * library implementations, as well as other specifications implemented by + * them. + * + * Most of the helpers provided here provide aliases for common library and + * compiler features. Furthermore, several helpers simply provide other calling + * conventions than their standard counterparts (e.g., they allow for NULL to + * be passed with an object length of 0 where it makes sense to accept empty + * input). + * + * The namespace used by this project is: + * + * * `c_*` for all common C symbols or definitions that behave like proper C + * entities (e.g., macros that protect against double-evaluation would use + * lower-case names) + * + * * `C_*` for all constants, as well as macros that may not be safe against + * double evaluation. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <assert.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <stdalign.h> +#include <stdarg.h> +#if 0 /* NM_IGNORED */ +#include <stdatomic.h> +#endif /* NM_IGNORED */ +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdnoreturn.h> +#include <string.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +/* + * Shortcuts for gcc attributes. See GCC manual for details. They're 1-to-1 + * mappings to the GCC equivalents. No additional magic here. They are + * supported by other compilers as well. + */ +#define _c_cleanup_(_x) __attribute__((__cleanup__(_x))) +#define _c_const_ __attribute__((__const__)) +#define _c_deprecated_ __attribute__((__deprecated__)) +#define _c_hidden_ __attribute__((__visibility__("hidden"))) +#define _c_likely_(_x) (__builtin_expect(!!(_x), 1)) +#define _c_packed_ __attribute__((__packed__)) +#define _c_printf_(_a, _b) __attribute__((__format__(printf, _a, _b))) +#define _c_public_ __attribute__((__visibility__("default"))) +#define _c_pure_ __attribute__((__pure__)) +#define _c_sentinel_ __attribute__((__sentinel__)) +#define _c_unlikely_(_x) (__builtin_expect(!!(_x), 0)) +#define _c_unused_ __attribute__((__unused__)) + +/** + * C_EXPR_ASSERT() - create expression with assertion + * @_expr: expression to evaluate to + * @_assertion: arbitrary assertion + * @_message: message associated with the assertion + * + * This macro simply evaluates to @_expr. That is, it can be used in any + * context that expects an expression like @_expr. Additionally, it takes an + * assertion as @_assertion and evaluates it through _Static_assert(), using + * @_message as debug message. + * + * The _Static_assert() builtin of C11 is defined as statement and thus cannot + * be used in expressions. This macro circumvents this restriction. + * + * Return: Evaluates to @_expr. + */ +#define C_EXPR_ASSERT(_expr, _assertion, _message) \ + /* indentation and line-split to get better diagnostics */ \ + (__builtin_choose_expr( \ + !!(1 + 0 * sizeof( \ + struct { \ +_Static_assert(_assertion, _message); \ + } \ + )), \ + (_expr), \ + ((void)0) \ + )) + +/** + * C_STRINGIFY() - stringify a token, but evaluate it first + * @_x: token to evaluate and stringify + * + * Return: Evaluates to a constant string literal + */ +#define C_STRINGIFY(_x) C_INTERNAL_STRINGIFY(_x) +#define C_INTERNAL_STRINGIFY(_x) #_x + +/** + * C_CONCATENATE() - concatenate two tokens, but evaluate them first + * @_x: first token + * @_y: second token + * + * Return: Evaluates to a constant identifier + */ +#define C_CONCATENATE(_x, _y) C_INTERNAL_CONCATENATE(_x, _y) +#define C_INTERNAL_CONCATENATE(_x, _y) _x ## _y + +/** + * C_EXPAND() - expand a tuple to a series of its values + * @_x: tuple to expand + * + * Return: Evaluates to the expanded tuple + */ +#define C_EXPAND(_x) C_INTERNAL_EXPAND _x +#define C_INTERNAL_EXPAND(...) __VA_ARGS__ + +/** + * C_VAR() - generate unique variable name + * @_x: name of variable, optional + * @_uniq: unique prefix, usually provided by __COUNTER__, optional + * + * This macro shall be used to generate unique variable names, that will not be + * shadowed by recursive macro invocations. It is effectively a + * C_CONCATENATE of both arguments, but also provides a globally separated + * prefix and makes the code better readable. + * + * The second argument is optional. If not given, __LINE__ is implied, and as + * such the macro will generate the same identifier if used multiple times on + * the same code-line (or within a macro). This should be used if recursive + * calls into the macro are not expected. In fact, no argument is necessary in + * this case, as a mere `C_VAR` will evaluate to a valid variable name. + * + * This helper may be used by macro implementations that might reasonable well + * be called in a stacked fasion, like: + * + * c_max(foo, c_max(bar, baz)) + * + * Such a stacked call of c_max() might cause compiler warnings of shadowed + * variables in the definition of c_max(). By using C_VAR(), such warnings + * can be silenced as each evaluation of c_max() uses unique variable names. + * + * Return: This evaluates to a constant identifier. + */ +#define C_VAR(...) C_INTERNAL_VAR(__VA_ARGS__, 2, 1) +#define C_INTERNAL_VAR(_x, _uniq, _num, ...) C_VAR ## _num (_x, _uniq) +#define C_VAR1(_x, _unused) C_VAR2(_x, C_CONCATENATE(line, __LINE__)) +#define C_VAR2(_x, _uniq) C_CONCATENATE(c_internal_var_unique_, C_CONCATENATE(_uniq, _x)) + +/** + * C_CC_MACRO1() - provide safe environment to a macro + * @_call: macro to call + * @_x1: first argument + * @...: further arguments to forward unmodified to @_call + * + * This function simplifies the implementation of macros. Whenever you + * implement a macro, provide the internal macro name as @_call and its + * argument as @_x1. Inside of your internal macro, you... + * + * - ...are safe against multiple evaluation errors, since C_CC_MACRO1 will + * store the initial parameters in temporary variables. + * + * - ...support constant folding, as C_CC_MACRO1 takes care to invoke your + * macro with the original values, if they are compile-time constant. + * + * - ...have unique variable names for recursive callers and will not run into + * variable-shadowing-warnings accidentally. + * + * - ...have properly typed arguments as C_CC_MACRO1 stores the original + * arguments in an `__auto_type` temporary variable. + * + * Return: Result of @_call is returned. + */ +#define C_CC_MACRO1(_call, _x1, ...) C_INTERNAL_CC_MACRO1(_call, __COUNTER__, (_x1), ## __VA_ARGS__) +#define C_INTERNAL_CC_MACRO1(_call, _x1q, _x1, ...) \ + __builtin_choose_expr( \ + __builtin_constant_p(_x1), \ + _call(_x1, ## __VA_ARGS__), \ + __extension__ ({ \ + const __auto_type C_VAR(X1, _x1q) = (_x1); \ + _call(C_VAR(X1, _x1q), ## __VA_ARGS__); \ + })) + +/** + * C_CC_MACRO2() - provide safe environment to a macro + * @_call: macro to call + * @_x1: first argument + * @_x2: second argument + * @...: further arguments to forward unmodified to @_call + * + * This is the 2-argument equivalent of C_CC_MACRO1(). + * + * Return: Result of @_call is returned. + */ +#define C_CC_MACRO2(_call, _x1, _x2, ...) C_INTERNAL_CC_MACRO2(_call, __COUNTER__, (_x1), __COUNTER__, (_x2), ## __VA_ARGS__) +#define C_INTERNAL_CC_MACRO2(_call, _x1q, _x1, _x2q, _x2, ...) \ + __builtin_choose_expr( \ + (__builtin_constant_p(_x1) && __builtin_constant_p(_x2)), \ + _call((_x1), (_x2), ## __VA_ARGS__), \ + __extension__ ({ \ + const __auto_type C_VAR(X1, _x1q) = (_x1); \ + const __auto_type C_VAR(X2, _x2q) = (_x2); \ + _call(C_VAR(X1, _x1q), C_VAR(X2, _x2q), ## __VA_ARGS__); \ + })) + +/** + * C_CC_MACRO3() - provide safe environment to a macro + * @_call: macro to call + * @_x1: first argument + * @_x2: second argument + * @_x3: third argument + * @...: further arguments to forward unmodified to @_call + * + * This is the 3-argument equivalent of C_CC_MACRO1(). + * + * Return: Result of @_call is returned. + */ +#define C_CC_MACRO3(_call, _x1, _x2, _x3, ...) C_INTERNAL_CC_MACRO3(_call, __COUNTER__, (_x1), __COUNTER__, (_x2), __COUNTER__, (_x3), ## __VA_ARGS__) +#define C_INTERNAL_CC_MACRO3(_call, _x1q, _x1, _x2q, _x2, _x3q, _x3, ...) \ + __builtin_choose_expr( \ + (__builtin_constant_p(_x1) && __builtin_constant_p(_x2) && __builtin_constant_p(_x3)), \ + _call((_x1), (_x2), (_x3), ## __VA_ARGS__), \ + __extension__ ({ \ + const __auto_type C_VAR(X1, _x1q) = (_x1); \ + const __auto_type C_VAR(X2, _x2q) = (_x2); \ + const __auto_type C_VAR(X3, _x3q) = (_x3); \ + _call(C_VAR(X1, _x1q), C_VAR(X2, _x2q), C_VAR(X3, _x3q), ## __VA_ARGS__); \ + })) + +/** + * C_ARRAY_SIZE() - calculate number of array elements at compile time + * @_x: array to calculate size of + * + * Return: Evaluates to a constant integer expression. + */ +#define C_ARRAY_SIZE(_x) \ + C_EXPR_ASSERT(sizeof(_x) / sizeof((_x)[0]), \ + /* \ + * Verify that `_x' is an array, not a pointer. Rely on \ + * `&_x[0]' degrading arrays to pointers. \ + */ \ + !__builtin_types_compatible_p( \ + __typeof__(_x), \ + __typeof__(&(*(__typeof__(_x)*)0)[0]) \ + ), \ + "C_ARRAY_SIZE() called with non-array argument" \ + ) + +/** + * C_DECIMAL_MAX() - calculate maximum length of the decimal + * representation of an integer + * @_type: integer variable/type + * + * This calculates the bytes required for the decimal representation of an + * integer of the given type. It accounts for a possible +/- prefix, but it + * does *NOT* include the trailing terminating zero byte. + * + * Return: Evaluates to a constant integer expression + */ +#define C_DECIMAL_MAX(_arg) \ + (_Generic((__typeof__(_arg)){ 0 }, \ + char: C_INTERNAL_DECIMAL_MAX(sizeof(char)), \ + signed char: C_INTERNAL_DECIMAL_MAX(sizeof(signed char)), \ + unsigned char: C_INTERNAL_DECIMAL_MAX(sizeof(unsigned char)), \ + signed short: C_INTERNAL_DECIMAL_MAX(sizeof(signed short)), \ + unsigned short: C_INTERNAL_DECIMAL_MAX(sizeof(unsigned short)), \ + signed int: C_INTERNAL_DECIMAL_MAX(sizeof(signed int)), \ + unsigned int: C_INTERNAL_DECIMAL_MAX(sizeof(unsigned int)), \ + signed long: C_INTERNAL_DECIMAL_MAX(sizeof(signed long)), \ + unsigned long: C_INTERNAL_DECIMAL_MAX(sizeof(unsigned long)), \ + signed long long: C_INTERNAL_DECIMAL_MAX(sizeof(signed long long)), \ + unsigned long long: C_INTERNAL_DECIMAL_MAX(sizeof(unsigned long long)))) +#define C_INTERNAL_DECIMAL_MAX(_bytes) \ + C_EXPR_ASSERT( \ + 1 + ((_bytes) <= 1 ? 3 : \ + (_bytes) <= 2 ? 5 : \ + (_bytes) <= 4 ? 10 : \ + 20), \ + (_bytes) <= 8, \ + "Invalid use of C_INTERNAL_DECIMAL_MAX()" \ + ) + +/** + * c_container_of() - cast a member of a structure out to the containing structure + * @_ptr: pointer to the member or NULL + * @_type: type of the container struct this is embedded in + * @_member: name of the member within the struct + * + * This uses `offsetof(3)` to turn a pointer to a structure-member into a + * pointer to the surrounding structure. + * + * Return: Pointer to the surrounding object. + */ +#define c_container_of(_ptr, _type, _member) C_CC_MACRO1(C_CONTAINER_OF, (_ptr), _type, _member) +#define C_CONTAINER_OF(_ptr, _type, _member) \ + __extension__ ({ \ + /* trigger warning if types do not match */ \ + (void)(&((_type *)0)->_member == (_ptr)); \ + _ptr ? (_type*)( (char*)_ptr - offsetof(_type, _member) ) : NULL; \ + }) + +/** + * c_max() - compute maximum of two values + * @_a: value A + * @_b: value B + * + * Calculate the maximum of both passed values. Both arguments are evaluated + * exactly once, under all circumstances. Furthermore, if both values are + * constant expressions, the result will be constant as well. + * + * The comparison of their values is performed with the types given by the + * caller. It is the caller's responsibility to convert them to suitable types + * if necessary. + * + * Return: Maximum of both values is returned. + */ +#define c_max(_a, _b) C_CC_MACRO2(C_MAX, (_a), (_b)) +#define C_MAX(_a, _b) ((_a) > (_b) ? (_a) : (_b)) + +/** + * c_min() - compute minimum of two values + * @_a: value A + * @_b: value B + * + * Calculate the minimum of both passed values. Both arguments are evaluated + * exactly once, under all circumstances. Furthermore, if both values are + * constant expressions, the result will be constant as well. + * + * The comparison of their values is performed with the types given by the + * caller. It is the caller's responsibility to convert them to suitable types + * if necessary. + * + * Return: Minimum of both values is returned. + */ +#define c_min(_a, _b) C_CC_MACRO2(C_MIN, (_a), (_b)) +#define C_MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b)) + +/** + * c_less_by() - calculate clamped difference of two values + * @_a: minuend + * @_b: subtrahend + * + * Calculate [_a - _b], but clamp the result to 0. Both arguments are evaluated + * exactly once, under all circumstances. Furthermore, if both values are + * constant expressions, the result will be constant as well. + * + * The comparison of their values is performed with the types given by the + * caller. It is the caller's responsibility to convert them to suitable types + * if necessary. + * + * Return: This computes [_a - _b], if [_a > _b]. Otherwise, 0 is returned. + */ +#define c_less_by(_a, _b) C_CC_MACRO2(C_LESS_BY, (_a), (_b)) +#define C_LESS_BY(_a, _b) ((_a) > (_b) ? (_a) - (_b) : 0) + +/** + * c_clamp() - clamp value to lower and upper boundary + * @_x: value to clamp + * @_low: lower boundary + * @_high: higher boundary + * + * This clamps @_x to the lower and higher bounds given as @_low and @_high. + * All arguments are evaluated exactly once, and yield a constant expression if + * all arguments are constant as well. + * + * The comparison of their values is performed with the types given by the + * caller. It is the caller's responsibility to convert them to suitable types + * if necessary. + * + * Return: Clamped integer value. + */ +#define c_clamp(_x, _low, _high) C_CC_MACRO3(C_CLAMP, (_x), (_low), (_high)) +#define C_CLAMP(_x, _low, _high) ((_x) > (_high) ? (_high) : (_x) < (_low) ? (_low) : (_x)) + +/** + * c_div_round_up() - calculate integer quotient but round up + * @_x: dividend + * @_y: divisor + * + * Calculates [x / y] but rounds up the result to the next integer. All + * arguments are evaluated exactly once, and yield a constant expression if all + * arguments are constant. + * + * Note: + * [(x + y - 1) / y] suffers from an integer overflow, even though the + * computation should be possible in the given type. Therefore, we use + * [x / y + !!(x % y)]. Note that on most CPUs a division returns both the + * quotient and the remainder, so both should be equally fast. Furthermore, if + * the divisor is a power of two, the compiler will optimize it, anyway. + * + * The operationsare performed with the types given by the caller. It is the + * caller's responsibility to convert the arguments to suitable types if + * necessary. + * + * Return: The quotient is returned. + */ +#define c_div_round_up(_x, _y) C_CC_MACRO2(C_DIV_ROUND_UP, (_x), (_y)) +#define C_DIV_ROUND_UP(_x, _y) ((_x) / (_y) + !!((_x) % (_y))) + +/** + * c_align_to() - align value to a multiple + * @_val: value to align + * @_to: align to multiple of this + * + * This aligns @_val to a multiple of @_to. If @_val is already a multiple of + * @_to, @_val is returned unchanged. This function operates within the + * boundaries of the type of @_val and @_to. Make sure to cast them if needed. + * + * The arguments of this macro are evaluated exactly once. If both arguments + * are a constant expression, this also yields a constant return value. + * + * Note that @_to must be a power of 2, otherwise the behavior will not match + * expectations. + * + * Return: @_val aligned to a multiple of @_to + */ +#define c_align_to(_val, _to) C_CC_MACRO2(C_ALIGN_TO, (_val), (_to)) +#define C_ALIGN_TO(_val, _to) (((_val) + (_to) - 1) & ~((_to) - 1)) + +/** + * c_assert() - runtime assertions + * @expr_result: result of an expression + * + * This function behaves like the standard `assert(3)` macro. That is, if + * `NDEBUG` is defined, it is a no-op. In all other cases it will assert that + * the result of the passed expression is true. + * + * Unlike the standard `assert(3)` macro, this function always evaluates its + * argument. This means side-effects will always be evaluated! However, if the + * macro is used with constant expressions, the compiler will be able to + * optimize it away. + */ +#define c_assert(_x) ({ \ + const _c_unused_ bool c_assert_result = (_x); \ + assert(c_assert_result && #_x); \ + }) + +/** + * c_errno() - return valid errno + * + * This helper should be used to shut up gcc if you know 'errno' is valid (ie., + * errno is > 0). Instead of "return -errno;", use + * "return -c_errno();" It will suppress bogus gcc warnings in case it assumes + * 'errno' might be 0 (or <0) and thus the caller's error-handling might not be + * triggered. + * + * This helper should be avoided whenever possible. However, occasionally we + * really want to shut up gcc (especially with static/inline functions). In + * those cases, gcc usually cannot deduce that some error paths are guaranteed + * to be taken. Hence, making the return value explicit allows gcc to better + * optimize the code. + * + * Note that you really should never use this helper to work around broken libc + * calls or syscalls, not setting 'errno' correctly. + * + * Return: Positive error code is returned. + */ +static inline int c_errno(void) { + return _c_likely_(errno > 0) ? errno : ENOTRECOVERABLE; +} + +/* + * Common Destructors + * + * Followingly, there're a bunch of common 'static inline' destructors, which + * simply call the function that they're named after, but return "INVALID" + * instead of "void". This allows direct assignment to any member-field and/or + * variable they're defined in, like: + * + * foo = c_free(foo); + * + * or + * + * foo->bar = c_close(foo->bar); + * + * Furthermore, all those destructors can be safely called with the "INVALID" + * value as argument, and they will be a no-op. + */ + +static inline void *c_free(void *p) { + free(p); + return NULL; +} + +static inline int c_close(int fd) { + if (fd >= 0) + close(fd); + return -1; +} + +static inline FILE *c_fclose(FILE *f) { + if (f) + fclose(f); + return NULL; +} + +static inline DIR *c_closedir(DIR *d) { + if (d) + closedir(d); + return NULL; +} + +/* + * Common Cleanup Helpers + * + * A bunch of _c_cleanup_(foobarp) helpers that are used all over the place. + * Note that all of those have the "if (IS_INVALID(foobar))" check inline, so + * compilers can optimize most of the cleanup-paths in a function. However, if + * the function they call already does this _inline_, then it might be skipped. + */ + +#define C_DEFINE_CLEANUP(_type, _func) \ + static inline void _func ## p(_type *p) { \ + if (*p) \ + _func(*p); \ + } struct c_internal_trailing_semicolon + +#define C_DEFINE_DIRECT_CLEANUP(_type, _func) \ + static inline void _func ## p(_type *p) { \ + _func(*p); \ + } struct c_internal_trailing_semicolon + +static inline void c_freep(void *p) { + /* + * `foobar **` does not coerce to `void **`, so we need `void *` as + * argument type, and then we dereference manually. + */ + c_free(*(void **)p); +} + +C_DEFINE_DIRECT_CLEANUP(int, c_close); +C_DEFINE_CLEANUP(FILE *, c_fclose); +C_DEFINE_CLEANUP(DIR *, c_closedir); + +#ifdef __cplusplus +} +#endif diff --git a/shared/meson.build b/shared/meson.build index ed9bf03f..af903d3c 100644 --- a/shared/meson.build +++ b/shared/meson.build @@ -2,8 +2,23 @@ shared_inc = include_directories('.') ############################################################################### +shared_c_stdaux = static_library( + 'c-stdaux', + c_args: '-std=c11', + sources: files('c-stdaux/src/c-stdaux.h'), +) + +shared_c_stdaux_dep = declare_dependency( + include_directories: shared_inc, +) + +############################################################################### + shared_c_siphash = static_library( 'c-siphash', + include_directories: [ + include_directories('c-stdaux/src'), + ], sources: 'c-siphash/src/c-siphash.c', ) @@ -17,6 +32,9 @@ shared_c_siphash_dep = declare_dependency( shared_c_rbtree = static_library( 'c-rbtree', c_args: '-std=c11', + include_directories: [ + include_directories('c-stdaux/src'), + ], sources: files('c-rbtree/src/c-rbtree.c', 'c-rbtree/src/c-rbtree.h', 'c-rbtree/src/c-rbtree-private.h'), @@ -52,6 +70,7 @@ shared_n_acd = static_library( '-Wno-vla', ], include_directories: [ + include_directories('c-stdaux/src'), include_directories('c-siphash/src'), include_directories('c-list/src'), include_directories('c-rbtree/src'), @@ -69,6 +88,43 @@ shared_n_acd_dep = declare_dependency( ############################################################################### +shared_n_dhcp4 = static_library( + 'n-dhcp4', + sources: files('n-dhcp4/src/n-dhcp4-c-connection.c', + 'n-dhcp4/src/n-dhcp4-c-lease.c', + 'n-dhcp4/src/n-dhcp4-c-probe.c', + 'n-dhcp4/src/n-dhcp4-client.c', + 'n-dhcp4/src/n-dhcp4-incoming.c', + 'n-dhcp4/src/n-dhcp4-outgoing.c', + 'n-dhcp4/src/n-dhcp4-private.h', + 'n-dhcp4/src/n-dhcp4-socket.c', + 'n-dhcp4/src/n-dhcp4.h', + 'n-dhcp4/src/util/packet.c', + 'n-dhcp4/src/util/packet.h', + 'n-dhcp4/src/util/socket.c', + 'n-dhcp4/src/util/socket.h'), + c_args: [ + '-D_GNU_SOURCE', + '-Wno-declaration-after-statement', + '-Wno-pointer-arith', + ], + include_directories: [ + include_directories('c-list/src'), + include_directories('c-siphash/src'), + include_directories('c-stdaux/src'), + ], + dependencies: [ + shared_c_siphash_dep, + ], +) + +shared_n_dhcp4_dep = declare_dependency( + include_directories: shared_inc, + link_with: shared_n_dhcp4, +) + +############################################################################### + version_conf = configuration_data() version_conf.set('NM_MAJOR_VERSION', nm_major_version) version_conf.set('NM_MINOR_VERSION', nm_minor_version) @@ -120,11 +176,14 @@ shared_nm_glib_aux_c_args = [ shared_nm_glib_aux = static_library( 'nm-utils-base', - sources: files('nm-glib-aux/nm-dedup-multi.c', + sources: files('nm-glib-aux/nm-dbus-aux.c', + 'nm-glib-aux/nm-dedup-multi.c', 'nm-glib-aux/nm-enum-utils.c', 'nm-glib-aux/nm-errno.c', 'nm-glib-aux/nm-hash-utils.c', 'nm-glib-aux/nm-io-utils.c', + 'nm-glib-aux/nm-json-aux.c', + 'nm-glib-aux/nm-keyfile-aux.c', 'nm-glib-aux/nm-random-utils.c', 'nm-glib-aux/nm-secret-utils.c', 'nm-glib-aux/nm-shared-utils.c', @@ -192,6 +251,7 @@ libnm_systemd_shared = static_library( 'systemd/src/basic/extract-word.c', 'systemd/src/basic/fd-util.c', 'systemd/src/basic/fileio.c', + 'systemd/src/basic/format-util.c', 'systemd/src/basic/fs-util.c', 'systemd/src/basic/hash-funcs.c', 'systemd/src/basic/hashmap.c', @@ -211,15 +271,18 @@ libnm_systemd_shared = static_library( 'systemd/src/basic/string-table.c', 'systemd/src/basic/string-util.c', 'systemd/src/basic/strv.c', + 'systemd/src/basic/strxcpyx.c', 'systemd/src/basic/time-util.c', 'systemd/src/basic/tmpfile-util.c', 'systemd/src/basic/utf8.c', 'systemd/src/basic/util.c', + 'systemd/src/shared/dns-domain.c', 'systemd/nm-sd-utils-shared.c', ), include_directories: include_directories( 'systemd/sd-adapt-shared', 'systemd/src/basic', + 'systemd/src/shared', ), dependencies: shared_nm_glib_aux_dep, c_args: [ @@ -232,6 +295,7 @@ libnm_systemd_shared_dep = declare_dependency( include_directories: include_directories( 'systemd/sd-adapt-shared', 'systemd/src/basic', + 'systemd/src/shared', ), dependencies: [ shared_nm_glib_aux_dep, @@ -268,18 +332,22 @@ libnm_systemd_shared_no_logging_dep = declare_dependency( ############################################################################### -test_shared_general = executable( - 'nm-utils/tests/test-shared-general', - [ 'nm-utils/tests/test-shared-general.c', ], - c_args: [ - '-DNETWORKMANAGER_COMPILATION_TEST', - '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG)', +exe = executable( + 'nm-utils/tests/test-shared-general', + [ 'nm-utils/tests/test-shared-general.c' ], + c_args: [ + '-DNETWORKMANAGER_COMPILATION_TEST', + '-DNETWORKMANAGER_COMPILATION=(NM_NETWORKMANAGER_COMPILATION_GLIB|NM_NETWORKMANAGER_COMPILATION_WITH_GLIB_I18N_PROG)', + ], + dependencies: [ + shared_nm_glib_aux_dep, + libnm_systemd_shared_no_logging_dep, + shared_c_siphash_dep, ], - dependencies: shared_nm_glib_aux_dep, - link_with: shared_c_siphash, ) + test( - 'shared/nm-utils/test-shared-general', - test_script, - args: test_args + [test_shared_general.full_path()] + 'shared/nm-utils/tests/test-shared-general', + test_script, + args: test_args + [exe.full_path()] ) diff --git a/shared/n-acd/src/n-acd-bpf-fallback.c b/shared/n-acd/src/n-acd-bpf-fallback.c index 7270cfd9..3cf4eb06 100644 --- a/shared/n-acd/src/n-acd-bpf-fallback.c +++ b/shared/n-acd/src/n-acd-bpf-fallback.c @@ -7,6 +7,7 @@ * See n-acd-bpf.c for documentation. */ +#include <c-stdaux.h> #include <stddef.h> #include "n-acd-private.h" diff --git a/shared/n-acd/src/n-acd-bpf.c b/shared/n-acd/src/n-acd-bpf.c index 771a28ee..57b29ddf 100644 --- a/shared/n-acd/src/n-acd-bpf.c +++ b/shared/n-acd/src/n-acd-bpf.c @@ -12,6 +12,7 @@ * that have already been removed. */ +#include <c-stdaux.h> #include <errno.h> #include <inttypes.h> #include <linux/bpf.h> diff --git a/shared/n-acd/src/n-acd-private.h b/shared/n-acd/src/n-acd-private.h index 3f207912..4583c018 100644 --- a/shared/n-acd/src/n-acd-private.h +++ b/shared/n-acd/src/n-acd-private.h @@ -2,6 +2,7 @@ #include <c-list.h> #include <c-rbtree.h> +#include <c-stdaux.h> #include <errno.h> #include <inttypes.h> #include <netinet/if_ether.h> @@ -13,9 +14,6 @@ typedef struct NAcdEventNode NAcdEventNode; -#define _cleanup_(_x) __attribute__((__cleanup__(_x))) -#define _public_ __attribute__((__visibility__("default"))) - /* This augments the error-codes with internal ones that are never exposed. */ enum { _N_ACD_INTERNAL = _N_ACD_E_N, @@ -150,23 +148,7 @@ int n_acd_bpf_compile(int *progfdp, int mapfd, struct ether_addr *mac); /* inline helpers */ -static inline 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 inline void n_acd_event_node_freep(NAcdEventNode **node) { if (*node) n_acd_event_node_free(*node); } - -static inline void n_acd_closep(int *fdp) { - if (*fdp >= 0) - close(*fdp); -} diff --git a/shared/n-acd/src/n-acd-probe.c b/shared/n-acd/src/n-acd-probe.c index d4da0fd5..43dd344c 100644 --- a/shared/n-acd/src/n-acd-probe.c +++ b/shared/n-acd/src/n-acd-probe.c @@ -1,9 +1,14 @@ /* * IPv4 Address Conflict Detection + * + * This file implements the probe object. A probe is basically the + * state-machine of a single ACD run. It takes an address to probe for, checks + * for conflicts and then defends it once configured. */ #include <assert.h> #include <c-rbtree.h> +#include <c-stdaux.h> #include <endian.h> #include <errno.h> #include <inttypes.h> @@ -19,8 +24,8 @@ #include "n-acd-private.h" /* - * These parameters and timing intervals specified in RFC-5227. The original - * values are: + * These parameters and timing intervals are specified in RFC-5227. The + * original values are: * * PROBE_NUM 3 * PROBE_WAIT 1s @@ -66,10 +71,19 @@ #define N_ACD_RFC_DEFEND_INTERVAL_NSEC (UINT64_C(10000000000)) /* 10s */ /** - * XXX + * n_acd_probe_config_new() - create probe configuration + * @configp: output argument for new probe configuration + * + * This creates a new probe configuration. It will be returned in @configp to + * the caller, which upon return fully owns the object. + * + * A probe configuration collects parameters for probes. It never validates the + * input, but this is left to the consumer of the configuration to do. + * + * Return: 0 on success, negative error code on failure. */ -_public_ int n_acd_probe_config_new(NAcdProbeConfig **configp) { - _cleanup_(n_acd_probe_config_freep) NAcdProbeConfig *config = NULL; +_c_public_ int n_acd_probe_config_new(NAcdProbeConfig **configp) { + _c_cleanup_(n_acd_probe_config_freep) NAcdProbeConfig *config = NULL; config = malloc(sizeof(*config)); if (!config) @@ -83,9 +97,15 @@ _public_ int n_acd_probe_config_new(NAcdProbeConfig **configp) { } /** - * XXX + * n_acd_probe_config_free() - destroy probe configuration + * @config: configuration to operate on, or NULL + * + * This destroys the probe configuration and all associated objects. If @config + * is NULL, this is a no-op. + * + * Return: NULL is returned. */ -_public_ NAcdProbeConfig *n_acd_probe_config_free(NAcdProbeConfig *config) { +_c_public_ NAcdProbeConfig *n_acd_probe_config_free(NAcdProbeConfig *config) { if (!config) return NULL; @@ -95,16 +115,45 @@ _public_ NAcdProbeConfig *n_acd_probe_config_free(NAcdProbeConfig *config) { } /** - * XXX + * n_acd_probe_config_set_ip() - set ip property + * @config: configuration to operate on + * @ip: ip to set + * + * This sets the IP property to the value `ip`. The address is copied into the + * configuration object. No validation is performed. + * + * The IP property selects the IP address that a probe checks for. It is the + * caller's responsibility to guarantee the address is valid and can be used. */ -_public_ void n_acd_probe_config_set_ip(NAcdProbeConfig *config, struct in_addr ip) { +_c_public_ void n_acd_probe_config_set_ip(NAcdProbeConfig *config, struct in_addr ip) { config->ip = ip; } /** - * XXX + * n_acd_probe_config_set_timeout() - set timeout property + * @config: configuration to operate on + * @msecs: timeout to set, in milliseconds + * + * This sets the timeout to use for a conflict detection probe. The + * specification default is provided as `N_ACD_TIMEOUT_RFC5227` and corresponds + * to 9 seconds. + * + * If set to 0, conflict detection is skipped and the address is immediately + * advertised and defended. + * + * Depending on the transport used, the API user should select a suitable + * timeout. Since `ACD` only operates on the link layer, timeouts in the + * hundreds of milliseconds range should be more than enough for any modern + * network. Note that increasing this value directly affects the time it takes + * to connect to a network, since an address should not be used unless conflict + * detection finishes. + * + * Using the specification default is **discouraged**. It is way too slow and + * not appropriate for modern networks. + * + * Default value is `N_ACD_TIMEOUT_RFC5227`. */ -_public_ void n_acd_probe_config_set_timeout(NAcdProbeConfig *config, uint64_t msecs) { +_c_public_ void n_acd_probe_config_set_timeout(NAcdProbeConfig *config, uint64_t msecs) { config->timeout_msecs = msecs; } @@ -214,15 +263,14 @@ static void n_acd_probe_unlink(NAcdProbe *probe) { */ if (n_acd_probe_is_unique(probe)) { r = n_acd_bpf_map_remove(probe->acd->fd_bpf_map, &probe->ip); - assert(r >= 0); - (void)r; + c_assert(r >= 0); --probe->acd->n_bpf_map; } c_rbnode_unlink(&probe->ip_node); } int n_acd_probe_new(NAcdProbe **probep, NAcd *acd, NAcdProbeConfig *config) { - _cleanup_(n_acd_probe_freep) NAcdProbe *probe = NULL; + _c_cleanup_(n_acd_probe_freep) NAcdProbe *probe = NULL; int r; if (!config->ip.s_addr) @@ -286,9 +334,20 @@ int n_acd_probe_new(NAcdProbe **probep, NAcd *acd, NAcdProbeConfig *config) { } /** - * XXX + * n_acd_probe_free() - destroy a probe + * @probe: probe to operate on, or NULL + * + * This destroys the probe specified by @probe. All operations are immediately + * ceded and all associated objects are released. + * + * If @probe is NULL, this is a no-op. + * + * This function will flush all events associated with @probe from the event + * queue. That is, no events will be returned for this @probe anymore. + * + * Return: NULL is returned. */ -_public_ NAcdProbe *n_acd_probe_free(NAcdProbe *probe) { +_c_public_ NAcdProbe *n_acd_probe_free(NAcdProbe *probe) { NAcdEventNode *node, *t_node; if (!probe) @@ -306,7 +365,7 @@ _public_ NAcdProbe *n_acd_probe_free(NAcdProbe *probe) { } int n_acd_probe_raise(NAcdProbe *probe, NAcdEventNode **nodep, unsigned int event) { - _cleanup_(n_acd_event_node_freep) NAcdEventNode *node = NULL; + _c_cleanup_(n_acd_event_node_freep) NAcdEventNode *node = NULL; int r; r = n_acd_raise(probe->acd, &node, event); @@ -327,8 +386,8 @@ int n_acd_probe_raise(NAcdProbe *probe, NAcdEventNode **nodep, unsigned int even node->event.conflict.probe = probe; break; default: - assert(0); - return -EIO; + c_assert(0); + return -ENOTRECOVERABLE; } c_list_link_tail(&probe->event_list, &node->probe_link); @@ -451,8 +510,8 @@ int n_acd_probe_handle_timeout(NAcdProbe *probe) { * There are no timeouts in these states. If we trigger one, * something is fishy. */ - assert(0); - return -EIO; + c_assert(0); + return -ENOTRECOVERABLE; } return 0; @@ -583,31 +642,47 @@ int n_acd_probe_handle_packet(NAcdProbe *probe, struct ether_arp *packet, bool h * We are not listening for packets in these states. If we receive one, * something is fishy. */ - assert(0); - return -EIO; + c_assert(0); + return -ENOTRECOVERABLE; } return 0; } /** - * n_acd_probe_set_userdata - XXX + * n_acd_probe_set_userdata - set userdata + * @probe: probe to operate on + * @userdata: userdata pointer + * + * This can be used to set a caller-controlled user-data pointer on @probe. The + * value of the pointer is never inspected or used by `n-acd` and is fully + * under control of the caller. + * + * The default value is NULL. */ -_public_ void n_acd_probe_set_userdata(NAcdProbe *probe, void *userdata) { +_c_public_ void n_acd_probe_set_userdata(NAcdProbe *probe, void *userdata) { probe->userdata = userdata; } /** - * n_acd_probe_get_userdata - XXX + * n_acd_probe_get_userdata - get userdata + * @probe: probe to operate on + * + * This queries the userdata pointer that was previously set through + * n_acd_probe_set_userdata(). + * + * The default value is NULL. + * + * Return: The stored userdata pointer is returned. */ -_public_ void n_acd_probe_get_userdata(NAcdProbe *probe, void **userdatap) { +_c_public_ void n_acd_probe_get_userdata(NAcdProbe *probe, void **userdatap) { *userdatap = probe->userdata; } /** * n_acd_probe_announce() - announce the configured IP address - * @probe: probe object - * @defend: defence policy + * @probe: probe to operate on + * @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, @@ -619,7 +694,7 @@ _public_ void n_acd_probe_get_userdata(NAcdProbe *probe, void **userdatap) { * Return: 0 on success, N_ACD_E_INVALID_ARGUMENT in case the defence policy * is invalid, negative error code on failure. */ -_public_ int n_acd_probe_announce(NAcdProbe *probe, unsigned int defend) { +_c_public_ int n_acd_probe_announce(NAcdProbe *probe, unsigned int defend) { if (defend >= _N_ACD_DEFEND_N) return N_ACD_E_INVALID_ARGUMENT; diff --git a/shared/n-acd/src/n-acd.c b/shared/n-acd/src/n-acd.c index def56a21..a0a48c58 100644 --- a/shared/n-acd/src/n-acd.c +++ b/shared/n-acd/src/n-acd.c @@ -1,11 +1,56 @@ /* * IPv4 Address Conflict Detection + * + * This file contains the main context initialization and management functions, + * as well as a bunch of utilities used through the n-acd modules. + */ + +/** + * DOC: IPv4 Address Conflict Detection + * + * The `n-acd` project implements the IPv4 Address Conflict Detection protocol + * as defined in RFC-5227. The protocol originates in the IPv4 Link Local + * Address selection but was later on generalized and resulted in `ACD`. The + * idea is to use `ARP` to query a link for an address to see whether it + * already exists on the network, as well as defending an address that is in + * use on a network interface. Furthermore, `ACD` provides passive diagnostics + * for administrators, as it will detect address conflicts automatically, which + * then can be logged or shown to a user. + * + * The main context object of `n-acd` is the `NAcd` structure. It is a passive + * ref-counted context object which drives `ACD` probes running on it. A + * context is specific to a linux network device and transport. If multiple + * network devices are used, then separate `NAcd` contexts must be deployed. + * + * The `NAcdProbe` object drives a single `ACD` state-machine. A probe is + * created on an `NAcd` context by providing an address to probe for. The probe + * will then raise notifications whether the address conflict detection found + * something, or whether the address is ready to be used. Optionally, the probe + * will then enter into passive mode and defend the address as long as it is + * kept active. + * + * Note that the `n-acd` project only implements the networking protocol. It + * never queries or modifies network interfaces. It completely relies on the + * API user to react to notifications and update network interfaces + * respectively. `n-acd` uses an event-mechanism on every context object. All + * events raise by any probe or operation on a given context will queue all + * events on that context object. The event-queue can then be drained by the + * API user. All events are properly asynchronous and designed in a way that no + * synchronous reaction to any event is required. That is, the events are + * carefully designed to allow forwarding via IPC (or even networks) to a + * controller that handles them and specifies how to react. Furthermore, none + * of the function calls of `n-acd` require synchronous error handling. + * Instead, functions only ever return values on fatal errors. Everything else + * is queued as events, thus guaranteeing that synchronous handling of return + * values is not required. Exceptions are functions that do not affect internal + * state or do not have an associated context object. */ #include <assert.h> #include <c-list.h> #include <c-rbtree.h> #include <c-siphash.h> +#include <c-stdaux.h> #include <endian.h> #include <errno.h> #include <inttypes.h> @@ -55,7 +100,7 @@ static int n_acd_get_random(unsigned int *random) { r = clock_gettime(CLOCK_MONOTONIC, &ts); if (r < 0) - return -n_acd_errno(); + return -c_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)); @@ -76,19 +121,19 @@ static int n_acd_socket_new(int *fdp, int fd_bpf_prog, NAcdConfig *config) { s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); if (s < 0) { - r = -n_acd_errno(); + r = -c_errno(); goto error; } if (fd_bpf_prog >= 0) { r = setsockopt(s, SOL_SOCKET, SO_ATTACH_BPF, &fd_bpf_prog, sizeof(fd_bpf_prog)); if (r < 0) - return -n_acd_errno(); + return -c_errno(); } r = bind(s, (struct sockaddr *)&address, sizeof(address)); if (r < 0) { - r = -n_acd_errno(); + r = -c_errno(); goto error; } @@ -103,10 +148,21 @@ error: } /** - * XXX + * n_acd_config_new() - create configuration object + * @configp: output argument for new configuration + * + * This creates a new configuration object and provides it to the caller. The + * object is fully owned by the caller upon function return. + * + * A configuration object is a passive structure that is used to collect + * information that is then passed to a constructor or other function. A + * configuration never validates the data, but it is up to the consumer of a + * configuration to do that. + * + * Return: 0 on success, negative error code on failure. */ -_public_ int n_acd_config_new(NAcdConfig **configp) { - _cleanup_(n_acd_config_freep) NAcdConfig *config = NULL; +_c_public_ int n_acd_config_new(NAcdConfig **configp) { + _c_cleanup_(n_acd_config_freep) NAcdConfig *config = NULL; config = malloc(sizeof(*config)); if (!config) @@ -120,9 +176,15 @@ _public_ int n_acd_config_new(NAcdConfig **configp) { } /** - * XXX + * n_acd_config_free() - destroy configuration object + * @config: configuration to operate on, or NULL + * + * This destroys the configuration object @config. If @config is NULL, this is + * a no-op. + * + * Return: NULL is returned. */ -_public_ NAcdConfig *n_acd_config_free(NAcdConfig *config) { +_c_public_ NAcdConfig *n_acd_config_free(NAcdConfig *config) { if (!config) return NULL; @@ -132,23 +194,55 @@ _public_ NAcdConfig *n_acd_config_free(NAcdConfig *config) { } /** - * XXX + * n_acd_config_set_ifindex() - set ifindex property + * @config: configuration to operate on + * @ifindex: ifindex to set + * + * This sets the @ifindex property of the configuration object. Any previous + * value is overwritten. + * + * A valid ifindex is a 32bit integer greater than 0. Any other value is + * treated as unspecified. + * + * The ifindex corresponds to the interface index provided by the linux kernel. + * It specifies the network device to be used. */ -_public_ void n_acd_config_set_ifindex(NAcdConfig *config, int ifindex) { +_c_public_ void n_acd_config_set_ifindex(NAcdConfig *config, int ifindex) { config->ifindex = ifindex; } /** - * XXX + * n_acd_config_set_transport() - set transport property + * @config: configuration to operate on + * @transport: transport to set + * + * This specifies the transport to use. A transport must be one of the + * `N_ACD_TRANSPORT_*` identifiers. It selects which transport protocol `n-acd` + * will run on. */ -_public_ void n_acd_config_set_transport(NAcdConfig *config, unsigned int transport) { +_c_public_ void n_acd_config_set_transport(NAcdConfig *config, unsigned int transport) { config->transport = transport; } /** - * XXX + * n_acd_config_set_mac() - set mac property + * @config: configuration to operate on + * @mac: mac to set + * + * This specifies the hardware address (also referred to as `MAC Address`) to + * use. Any hardware address can be specified. It is the caller's + * responsibility to make sure the address can actually be used. + * + * The address in @mac is copied into @config. It does not have to be retained + * by the caller. */ -_public_ void n_acd_config_set_mac(NAcdConfig *config, const uint8_t *mac, size_t n_mac) { +_c_public_ void n_acd_config_set_mac(NAcdConfig *config, const uint8_t *mac, size_t n_mac) { + /* + * We truncate the address at the maximum we support. We still remember + * the original length, so any consumer of this configuration can then + * complain about an unsupported address length. This allows us to + * avoid a memory allocation here and having to return `int`. + */ config->n_mac = n_mac; memcpy(config->mac, mac, n_mac > ETH_ALEN ? ETH_ALEN : n_mac); } @@ -179,7 +273,7 @@ NAcdEventNode *n_acd_event_node_free(NAcdEventNode *node) { int n_acd_ensure_bpf_map_space(NAcd *acd) { NAcdProbe *probe; - _cleanup_(n_acd_closep) int fd_map = -1, fd_prog = -1; + _c_cleanup_(c_closep) int fd_map = -1, fd_prog = -1; size_t max_map; int r; @@ -205,7 +299,7 @@ int n_acd_ensure_bpf_map_space(NAcd *acd) { if (fd_prog >= 0) { r = setsockopt(acd->fd_socket, SOL_SOCKET, SO_ATTACH_BPF, &fd_prog, sizeof(fd_prog)); if (r) - return -n_acd_errno(); + return -c_errno(); } if (acd->fd_bpf_map >= 0) @@ -218,16 +312,20 @@ int n_acd_ensure_bpf_map_space(NAcd *acd) { /** * n_acd_new() - create a new ACD context - * @acdp: output argument for context - * @config: configuration parameters + * @acdp: output argument for new context object + * @config: configuration parameters * - * Create a new ACD context and return it in @acdp. + * Create a new ACD context and return it in @acdp. The configuration @config + * must be initialized by the caller and must specify a valid network + * interface, transport mechanism, as well as hardware address compatible with + * the selected transport. The configuration is copied into the context. The + * @config object thus does not have to be retained by the caller. * - * Return: 0 on success, or a negative error code on failure. + * Return: 0 on success, negative error code on failure. */ -_public_ int n_acd_new(NAcd **acdp, NAcdConfig *config) { - _cleanup_(n_acd_unrefp) NAcd *acd = NULL; - _cleanup_(n_acd_closep) int fd_bpf_prog = -1; +_c_public_ int n_acd_new(NAcd **acdp, NAcdConfig *config) { + _c_cleanup_(n_acd_unrefp) NAcd *acd = NULL; + _c_cleanup_(c_closep) int fd_bpf_prog = -1; int r; if (config->ifindex <= 0 || @@ -250,7 +348,7 @@ _public_ int n_acd_new(NAcd **acdp, NAcdConfig *config) { acd->fd_epoll = epoll_create1(EPOLL_CLOEXEC); if (acd->fd_epoll < 0) - return -n_acd_errno(); + return -c_errno(); r = timer_init(&acd->timer); if (r < 0) @@ -276,7 +374,7 @@ _public_ int n_acd_new(NAcd **acdp, NAcdConfig *config) { .data.u32 = N_ACD_EPOLL_TIMER, }); if (r < 0) - return -n_acd_errno(); + return -c_errno(); r = epoll_ctl(acd->fd_epoll, EPOLL_CTL_ADD, acd->fd_socket, &(struct epoll_event){ @@ -284,14 +382,14 @@ _public_ int n_acd_new(NAcd **acdp, NAcdConfig *config) { .data.u32 = N_ACD_EPOLL_SOCKET, }); if (r < 0) - return -n_acd_errno(); + return -c_errno(); *acdp = acd; acd = NULL; return 0; } -static void n_acd_free(NAcd *acd) { +static void n_acd_free_internal(NAcd *acd) { NAcdEventNode *node, *t_node; if (!acd) @@ -300,10 +398,10 @@ static void n_acd_free(NAcd *acd) { c_list_for_each_entry_safe(node, t_node, &acd->event_list, acd_link) n_acd_event_node_free(node); - assert(c_rbtree_is_empty(&acd->ip_tree)); + c_assert(c_rbtree_is_empty(&acd->ip_tree)); if (acd->fd_socket >= 0) { - assert(acd->fd_epoll >= 0); + c_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; @@ -315,7 +413,7 @@ static void n_acd_free(NAcd *acd) { } if (acd->timer.fd >= 0) { - assert(acd->fd_epoll >= 0); + c_assert(acd->fd_epoll >= 0); epoll_ctl(acd->fd_epoll, EPOLL_CTL_DEL, acd->timer.fd, NULL); timer_deinit(&acd->timer); } @@ -329,20 +427,32 @@ static void n_acd_free(NAcd *acd) { } /** - * XXX + * n_acd_ref() - acquire reference + * @acd: context to operate on, or NULL + * + * This acquires a single reference to the context specified as @acd. If @acd + * is NULL, this is a no-op. + * + * Return: @acd is returned. */ -_public_ NAcd *n_acd_ref(NAcd *acd) { +_c_public_ NAcd *n_acd_ref(NAcd *acd) { if (acd) ++acd->n_refs; return acd; } /** - * XXX + * n_acd_unref() - release reference + * @acd: context to operate on, or NULL + * + * This releases a single reference to the context @acd. If this is the last + * reference, the context is torn down and deallocated. + * + * Return: NULL is returned. */ -_public_ NAcd *n_acd_unref(NAcd *acd) { +_c_public_ NAcd *n_acd_unref(NAcd *acd) { if (acd && !--acd->n_refs) - n_acd_free(acd); + n_acd_free_internal(acd); return NULL; } @@ -426,7 +536,7 @@ int n_acd_send(NAcd *acd, const struct in_addr *tpa, const struct in_addr *spa) * Random network error. We treat this as fatal and propagate * the error, so it is noticed and can be investigated. */ - return -n_acd_errno(); + return -c_errno(); } else if (l != (ssize_t)sizeof(arp)) { /* * Ugh, the kernel modified the packet. This is unexpected. We @@ -440,13 +550,22 @@ int n_acd_send(NAcd *acd, const struct in_addr *tpa, const struct in_addr *spa) /** * n_acd_get_fd() - get pollable file descriptor - * @acd: ACD context - * @fdp: output argument for file descriptor + * @acd: context object to operate on + * @fdp: output argument for file descriptor + * + * This returns the backing file-descriptor of the context object @acd. The + * file-descriptor is owned by @acd and valid as long as @acd is. The + * file-descriptor never changes, so it can be cached by the caller as long as + * they hold a reference to @acd. + * + * The file-descriptor is internal to the @acd context and should not be + * modified by the caller. It is only exposed to allow the caller to poll on + * it. Whenever the file-descriptor polls readable, n_acd_dispatch() should be + * called. * - * Returns a file descriptor in @fdp. This file descriptor can be polled by - * the caller to indicate when the ACD context can be dispatched. + * Currently, the file-descriptor is an epoll-fd. */ -_public_ void n_acd_get_fd(NAcd *acd, int *fdp) { +_c_public_ void n_acd_get_fd(NAcd *acd, int *fdp) { *fdp = acd->fd_epoll; } @@ -597,7 +716,7 @@ static int n_acd_dispatch_timer(NAcd *acd, struct epoll_event *event) { if (r <= 0) return r; - assert(r == TIMER_E_TRIGGERED); + c_assert(r == TIMER_E_TRIGGERED); /* * A timer triggered, handle all pending timeouts at a given @@ -710,7 +829,7 @@ static int n_acd_dispatch_socket(NAcd *acd, struct epoll_event *event) { * Something went wrong. Propagate the error-code, so * this can be investigated. */ - return -n_acd_errno(); + return -c_errno(); } } else if (n >= (ssize_t)n_batch) { /* @@ -750,16 +869,34 @@ static int n_acd_dispatch_socket(NAcd *acd, struct epoll_event *event) { } /** - * XXX + * n_acd_dispatch() - dispatch context + * @acd: context object to operate on + * + * This dispatches the internal state-machine of all probes and operations + * running on the context @acd. + * + * Any outside effect or event triggered by this dispatcher will be queued on + * the event-queue of @acd. Whenever the dispatcher returns, the caller is + * required to drain the event-queue via n_acd_pop_event() until it is empty. + * + * This function dispatches as many events as possible up to a static limit to + * prevent stalling execution. If the static limit is reached, this function + * will return with N_ACD_E_PREEMPTED, otherwise 0 is returned. In most cases + * preemption can be ignored, because level-triggered event notification + * handles it automatically. However, in case of edge-triggered event + * mechanisms, the caller must make sure to call the dispatcher again. + * + * Return: 0 on success, N_ACD_E_PREEMPTED on preemption, negative error code + * on failure. */ -_public_ int n_acd_dispatch(NAcd *acd) { +_c_public_ int n_acd_dispatch(NAcd *acd) { struct epoll_event events[2]; int n, i, r = 0; n = epoll_wait(acd->fd_epoll, events, sizeof(events) / sizeof(*events), 0); if (n < 0) { /* Linux never returns EINTR if `timeout == 0'. */ - return -n_acd_errno(); + return -c_errno(); } acd->preempted = false; @@ -773,7 +910,7 @@ _public_ int n_acd_dispatch(NAcd *acd) { r = n_acd_dispatch_socket(acd, events + i); break; default: - assert(0); + c_assert(0); r = 0; break; } @@ -787,8 +924,8 @@ _public_ int n_acd_dispatch(NAcd *acd) { /** * n_acd_pop_event() - get the next pending event - * @acd: ACD context - * @eventp: output argument for the event + * @acd: context object to operate on + * @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() @@ -840,7 +977,7 @@ _public_ int n_acd_dispatch(NAcd *acd) { * @eventp and 0 is returned. If an error is returned, @eventp is left * untouched. */ -_public_ int n_acd_pop_event(NAcd *acd, NAcdEvent **eventp) { +_c_public_ int n_acd_pop_event(NAcd *acd, NAcdEvent **eventp) { NAcdEventNode *node, *t_node; c_list_for_each_entry_safe(node, t_node, &acd->event_list, acd_link) { @@ -859,8 +996,29 @@ _public_ int n_acd_pop_event(NAcd *acd, NAcdEvent **eventp) { } /** - * XXX + * n_acd_probe() - start new probe + * @acd: context object to operate on + * @probep: output argument for new probe + * @config: probe configuration + * + * This creates a new probe on the context @acd and returns the probe in + * @probep. The configuration @config must provide valid probe parameters. At + * least a valid IP address must be provided through the configuration. + * + * This function does not reject duplicate probes for the same address. It is + * the caller's decision whether duplicates are allowed or not. But note that + * duplicate probes on the same context will not conflict each other. That is, + * running a probe for the same address twice on the same context will not + * cause them to consider each other a duplicate. + * + * Probes are rather lightweight objects. They do not create any + * file-descriptors or other kernel objects. Probes always re-use the + * infrastructure provided by the context object @acd. This allows running many + * probes simultaneously without exhausting resources. + * + * Return: 0 on success, N_ACD_E_INVALID_ARGUMENT on invalid configuration + * parameters, negative error code on failure. */ -_public_ int n_acd_probe(NAcd *acd, NAcdProbe **probep, NAcdProbeConfig *config) { +_c_public_ int n_acd_probe(NAcd *acd, NAcdProbe **probep, NAcdProbeConfig *config) { return n_acd_probe_new(probep, acd, config); } diff --git a/shared/n-acd/src/n-acd.h b/shared/n-acd/src/n-acd.h index 74b0aacb..e2b01270 100644 --- a/shared/n-acd/src/n-acd.h +++ b/shared/n-acd/src/n-acd.h @@ -13,7 +13,9 @@ extern "C" { #endif #include <netinet/in.h> +#include <inttypes.h> #include <stdbool.h> +#include <stdlib.h> typedef struct NAcd NAcd; typedef struct NAcdConfig NAcdConfig; diff --git a/shared/n-acd/src/util/timer.c b/shared/n-acd/src/util/timer.c index 07dbf34e..3c9570a1 100644 --- a/shared/n-acd/src/util/timer.c +++ b/shared/n-acd/src/util/timer.c @@ -4,6 +4,7 @@ #include <assert.h> #include <c-rbtree.h> +#include <c-stdaux.h> #include <errno.h> #include <stdlib.h> #include <sys/timerfd.h> @@ -30,7 +31,7 @@ int timer_init(Timer *timer) { } void timer_deinit(Timer *timer) { - assert(c_rbtree_is_empty(&timer->tree)); + c_assert(c_rbtree_is_empty(&timer->tree)); if (timer->fd >= 0) { close(timer->fd); @@ -43,8 +44,7 @@ void timer_now(Timer *timer, uint64_t *nowp) { int r; r = clock_gettime(timer->clock, &ts); - assert(r >= 0); - (void)r; + c_assert(r >= 0); *nowp = ts.tv_sec * UINT64_C(1000000000) + ts.tv_nsec; } @@ -60,7 +60,7 @@ void timer_rearm(Timer *timer) { */ timeout = c_rbnode_entry(c_rbtree_first(&timer->tree), Timeout, node); - assert(!timeout || timeout->timeout); + c_assert(!timeout || timeout->timeout); time = timeout ? timeout->timeout : 0; @@ -74,8 +74,7 @@ void timer_rearm(Timer *timer) { }, }, NULL); - assert(r >= 0); - (void)r; + c_assert(r >= 0); timer->scheduled_timeout = time; } @@ -134,8 +133,7 @@ int timer_pop_timeout(Timer *timer, uint64_t until, Timeout **timeoutp) { } void timeout_schedule(Timeout *timeout, Timer *timer, uint64_t time) { - - assert(time); + c_assert(time); /* * In case @timeout was already scheduled, remove it from the diff --git a/shared/n-acd/src/util/timer.h b/shared/n-acd/src/util/timer.h index 2acc99e3..d01b2741 100644 --- a/shared/n-acd/src/util/timer.h +++ b/shared/n-acd/src/util/timer.h @@ -1,6 +1,7 @@ #pragma once #include <c-rbtree.h> +#include <c-stdaux.h> #include <inttypes.h> #include <stdlib.h> #include <time.h> diff --git a/shared/n-dhcp4/src/n-dhcp4-c-connection.c b/shared/n-dhcp4/src/n-dhcp4-c-connection.c new file mode 100644 index 00000000..5c50dacf --- /dev/null +++ b/shared/n-dhcp4/src/n-dhcp4-c-connection.c @@ -0,0 +1,1150 @@ +/* + * DHCPv4 Client Connection + * + * XXX + */ + +#include <assert.h> +#include <c-stdaux.h> +#include <errno.h> +#include <limits.h> +#include <sys/socket.h> /* needed by linux/netdevice.h */ +#include <linux/netdevice.h> +#include <net/if_arp.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include "n-dhcp4-private.h" +#include "util/packet.h" + +/** + * n_dhcp4_c_connection_init() - initialize client connection + * @connection: connection to operate on + * @client_config: client configuration to use + * @probe_config: client probe configuration to use + * @fd_epoll: epoll context to attach to, or -1 + * + * This initializes a new client connection using the configuration given in + * @client_config and @probe_config. + * + * The client-configuration given as @client_config must survive the lifetime + * of @connection. It is pinned in the connection and used all over the place. + * The caller must guarantee that the configuration is not deallocated in the + * meantime. Same is true for @probe_config. + * + * The new connection automatically attaches to the epoll context given as + * @fd_epoll. The epoll FD is retained in the connection and the caller must + * guarantee that it lives as long as the connection. + * The caller is explicitly allowed to pass -1 as @fd_epoll, in which case the + * connection will initialize correctly, but will not be in a usable state. + * That is, any call to n_dhcp4_c_connection_listen() will fail, since it will + * be unable to attach to the epoll context. Such a connection can be used to + * get a detached object that behaves sound, but provides no runtime. + * + * Return: 0 on success, negative error code on failure. + */ +int n_dhcp4_c_connection_init(NDhcp4CConnection *connection, + NDhcp4ClientConfig *client_config, + NDhcp4ClientProbeConfig *probe_config, + int fd_epoll) { + *connection = (NDhcp4CConnection)N_DHCP4_C_CONNECTION_NULL(*connection); + connection->client_config = client_config; + connection->probe_config = probe_config; + connection->fd_epoll = fd_epoll; + + /* + * We explicitly allow initializing connections with an invalid + * epoll-fd. The resulting connection immediately transitions into the + * CLOSED state. This allows the caller to create dummy connections + * useful to provide asynchronous constructor-feedback in the API. + * + * The effect of this is as if you immediately call + * n_dhcp4_c_connection_close() on the new connection. However, by + * directly passing -1 in the constructor, you are guaranteed not even + * the constructor can ever mess with your epoll-set. + */ + if (connection->fd_epoll < 0) + connection->state = N_DHCP4_C_CONNECTION_STATE_CLOSED; + + return 0; +} + +/** + * n_dhcp4_c_connection_deinit() - deinitialize client connection + * @connection: connection to operate on + * + * This deinitializes a connection that was previously initialized via + * n_dhcp4_c_connection_init(). It will tear down all allocated state and + * release it. + * + * Once this function returns, @connection is re-initialized to + * N_DHCP4_C_CONNECTION_NULL. If this function is called on a deinitialized + * connection, it is a no-op. + */ +void n_dhcp4_c_connection_deinit(NDhcp4CConnection *connection) { + n_dhcp4_c_connection_close(connection); + n_dhcp4_outgoing_free(connection->request); + *connection = (NDhcp4CConnection)N_DHCP4_C_CONNECTION_NULL(*connection); +} + +static void n_dhcp4_c_connection_outgoing_set_secs(NDhcp4Outgoing *message) { + uint32_t secs; + + /* + * This function sets the `secs` field for outgoing messages. It + * expects the base-time and start-time to be already set by the + * caller. + * For a given outgoing message, its `secs` field describes the time + * (in seconds) between the start of the transaction this message is + * part of and the start of the operational process (also called the + * base time here). + * + * The operational process in the DHCP sense describes the entire + * process of requesting a lease and acquiring it. That is, it starts + * with the caller's intent to request a lease, and it ends when we + * got granted a lease. The act of refreshing a lease is, in itself, a + * new operational process. The base-time describes the start-time + * recorded when such a process as initiated. + * + * A transaction in the DHCP sense describes a request+reply + * combination, in most cases. That is, the time a request is sent is + * the start-time of a transaction. In the ideal case, the start-time + * of the first transaction in an operational process matches the + * base-time. However, transactions are often delayed with a randomized + * offset to reduce traffic during network bursts. + * In some cases, however, transactions are composed out of multiple + * requests+reply combinations. This includes, for instance, the SELECT + * message following an OFFER. The specification clearly says that + * those must be considered a single transaction and thus share the + * transaction start-time. + * + * The `secs` field, thus, describes how long a client has been busy + * requesting a lease. DHCP servers and proxies do use it to prioritize + * clients. + * + * Note: Some DHCP relays reject a `secs` value of 0 (which might look + * like it is uninitialized). Hence, we always clamp the value to + * the range `[1, INF[`. + */ + + secs = message->userdata.base_time - message->userdata.start_time; + secs /= 1000ULL * 1000ULL * 1000ULL; /* nsecs to secs */ + secs = secs ?: 1; /* clamp to `[1, INF[` */ + + n_dhcp4_outgoing_set_secs(message, secs); +} + +int n_dhcp4_c_connection_listen(NDhcp4CConnection *connection) { + _c_cleanup_(c_closep) int fd_packet = -1; + int r; + + c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_INIT); + + r = n_dhcp4_c_socket_packet_new(&fd_packet, connection->client_config->ifindex); + if (r) + return r; + + r = epoll_ctl(connection->fd_epoll, + EPOLL_CTL_ADD, + fd_packet, + &(struct epoll_event){ + .events = EPOLLIN, + .data = { .u32 = N_DHCP4_CLIENT_EPOLL_IO }, + }); + if (r < 0) + return -errno; + + connection->state = N_DHCP4_C_CONNECTION_STATE_PACKET; + connection->fd_packet = fd_packet; + fd_packet = -1; + return 0; +} + +int n_dhcp4_c_connection_connect(NDhcp4CConnection *connection, + const struct in_addr *client, + const struct in_addr *server) { + int r, fd_udp; + + c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_PACKET); + + r = n_dhcp4_c_socket_udp_new(&fd_udp, + connection->client_config->ifindex, + client, + server); + if (r) + return r; + + r = epoll_ctl(connection->fd_epoll, + EPOLL_CTL_ADD, + fd_udp, + &(struct epoll_event){ + .events = EPOLLIN, + .data = { .u32 = N_DHCP4_CLIENT_EPOLL_IO }, + }); + if (r < 0) { + r = -errno; + goto exit_fd; + } + + r = packet_shutdown(connection->fd_packet); + if (r < 0) + goto exit_epoll; + + connection->state = N_DHCP4_C_CONNECTION_STATE_DRAINING; + connection->fd_udp = fd_udp; + connection->client_ip = client->s_addr; + connection->server_ip = server->s_addr; + fd_udp = -1; + return 0; + +exit_epoll: + epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, fd_udp, NULL); +exit_fd: + close(fd_udp); + return r; +} + +void n_dhcp4_c_connection_close(NDhcp4CConnection *connection) { + if (connection->fd_udp >= 0) { + epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_udp, NULL); + connection->fd_udp = c_close(connection->fd_udp); + } + + if (connection->fd_packet >= 0) { + epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_packet, NULL); + connection->fd_packet = c_close(connection->fd_packet); + } + + connection->fd_epoll = -1; + connection->state = N_DHCP4_C_CONNECTION_STATE_CLOSED; +} + +static int n_dhcp4_c_connection_verify_incoming(NDhcp4CConnection *connection, + NDhcp4Incoming *message, + uint8_t *typep) { + NDhcp4Header *header = n_dhcp4_incoming_get_header(message); + uint8_t type; + uint32_t request_xid; + uint8_t *id; + size_t n_id; + int r; + + r = n_dhcp4_incoming_query_message_type(message, &type); + if (r) { + if (r == N_DHCP4_E_UNSET) + return N_DHCP4_E_MALFORMED; + else + return r; + } + + switch (type) { + case N_DHCP4_MESSAGE_OFFER: + case N_DHCP4_MESSAGE_ACK: + case N_DHCP4_MESSAGE_NAK: + /* + * Only accept replies if there is a pending request, and it + * has a matching transaction id. + */ + if (!connection->request) + return N_DHCP4_E_UNEXPECTED; + + n_dhcp4_outgoing_get_xid(connection->request, &request_xid); + if (header->xid != request_xid) + return N_DHCP4_E_UNEXPECTED; + + break; + case N_DHCP4_MESSAGE_FORCERENEW: + /* + * Force renew messages are triggered by a server, and do not + * match a pending request. + */ + break; + default: + return N_DHCP4_E_UNEXPECTED; + } + + /* + * In case our transport makes use of the 'chaddr' field, make sure it + * matches exactly our address. + */ + switch (connection->client_config->transport) { + case N_DHCP4_TRANSPORT_ETHERNET: + c_assert(connection->client_config->n_mac == ETH_ALEN); + + if (header->hlen != ETH_ALEN) + return N_DHCP4_E_UNEXPECTED; + if (memcmp(header->chaddr, connection->client_config->mac, ETH_ALEN) != 0) + return N_DHCP4_E_UNEXPECTED; + + break; + case N_DHCP4_TRANSPORT_INFINIBAND: + if (header->hlen != 0) + return N_DHCP4_E_UNEXPECTED; + + break; + } + + /* + * If a server passes us back a client ID, it must be the one we + * provided. We ignore any packets that have mismatching client-ids. + */ + id = NULL; + n_id = 0; + r = n_dhcp4_incoming_query(message, N_DHCP4_OPTION_CLIENT_IDENTIFIER, &id, &n_id); + if (r) { + if (r != N_DHCP4_E_UNSET) + return r; + } else { + if (n_id != connection->client_config->n_client_id) + return N_DHCP4_E_UNEXPECTED; + if (memcmp(id, connection->client_config->client_id, n_id) != 0) + return N_DHCP4_E_UNEXPECTED; + } + + *typep = type; + return 0; +} + +void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection, + uint64_t *timeoutp) { + uint64_t timeout; + size_t n_send; + + if (!connection->request) { + *timeoutp = 0; + return; + } + + switch (connection->request->userdata.type) { + case N_DHCP4_C_MESSAGE_DISCOVER: + case N_DHCP4_C_MESSAGE_SELECT: + case N_DHCP4_C_MESSAGE_REBOOT: + case N_DHCP4_C_MESSAGE_INFORM: + /* + * Resend with an exponential backoff and a one second random + * slack, from a minimum of two seconds to a maximum of sixty + * four. + * + * Note that the RFC says to start at four rather than two + * seconds, and use [-1,1] slack, rather than [0,1]. + */ + n_send = connection->request->userdata.n_send; + if (n_send >= 6) + n_send = 6; + + timeout = connection->request->userdata.send_time + ((1ULL << n_send) * 1000000000ULL) + connection->request->userdata.send_jitter; + + break; + case N_DHCP4_C_MESSAGE_REBIND: + case N_DHCP4_C_MESSAGE_RENEW: + /* + * Resend every sixty seconds with a one second random slack. + * + * Note that the RFC says to do this at most once, but we do + * it until we are cancelled. + */ + timeout = connection->request->userdata.send_time + (60ULL * 1000000000ULL) + connection->request->userdata.send_jitter; + + break; + case N_DHCP4_C_MESSAGE_DECLINE: + case N_DHCP4_C_MESSAGE_RELEASE: + /* XXX make sure these message types are never pinned? */ + timeout = 0; + break; + default: + c_assert(0); + } + + *timeoutp = timeout; +} + +static int n_dhcp4_c_connection_packet_broadcast(NDhcp4CConnection *connection, + NDhcp4Outgoing *message) { + int r; + + c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_PACKET); + + r = n_dhcp4_c_socket_packet_send(connection->fd_packet, + connection->client_config->ifindex, + connection->client_config->broadcast_mac, + connection->client_config->n_broadcast_mac, + message); + if (r) + return r; + + return 0; +} + +static int n_dhcp4_c_connection_udp_broadcast(NDhcp4CConnection *connection, + NDhcp4Outgoing *message) { + int r; + + c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_DRAINING || + connection->state == N_DHCP4_C_CONNECTION_STATE_UDP); + + r = n_dhcp4_c_socket_udp_broadcast(connection->fd_udp, message); + if (r) + return r; + + return 0; +} + +static int n_dhcp4_c_connection_udp_send(NDhcp4CConnection *connection, + NDhcp4Outgoing *message) { + int r; + + c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_DRAINING || + connection->state == N_DHCP4_C_CONNECTION_STATE_UDP); + + r = n_dhcp4_c_socket_udp_send(connection->fd_udp, message); + if (r) + return r; + + return 0; +} + +static void n_dhcp4_c_connection_init_header(NDhcp4CConnection *connection, + NDhcp4Header *header) { + bool broadcast = connection->client_config->request_broadcast; + + header->op = N_DHCP4_OP_BOOTREQUEST; + + switch (connection->client_config->transport) { + case N_DHCP4_TRANSPORT_ETHERNET: + c_assert(connection->client_config->n_mac == ETH_ALEN); + + header->htype = ARPHRD_ETHER; + header->hlen = ETH_ALEN; + memcpy(header->chaddr, connection->client_config->mac, ETH_ALEN); + break; + case N_DHCP4_TRANSPORT_INFINIBAND: + header->htype = ARPHRD_INFINIBAND; + header->hlen = 0; + + /* infiniband mandates to request broadcasts */ + broadcast = true; + break; + default: + abort(); + break; + } + + if (connection->client_ip != INADDR_ANY) { + header->ciaddr = connection->client_ip; + } else { + /* + * When the IP stack has not been configured, we may + * not be able to receive unicast packets, depending + * on the hardware. If that is the case we must request + * replies from the server to be broadcast. + * + * Once the IP stack has been configured, receiving + * unicast packets is never a problem, so the broadcast + * flag should not be set. + */ + if (broadcast) + header->flags |= N_DHCP4_MESSAGE_FLAG_BROADCAST; + } +} + +static int n_dhcp4_c_connection_new_message(NDhcp4CConnection *connection, + NDhcp4Outgoing **messagep, + uint8_t type) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL; + NDhcp4Header *header; + uint8_t message_type; + bool via_packet_socket = false; + int r; + + switch (type) { + case N_DHCP4_C_MESSAGE_DISCOVER: + message_type = N_DHCP4_MESSAGE_DISCOVER; + via_packet_socket = true; + break; + case N_DHCP4_C_MESSAGE_INFORM: + message_type = N_DHCP4_MESSAGE_INFORM; + break; + case N_DHCP4_C_MESSAGE_SELECT: + message_type = N_DHCP4_MESSAGE_REQUEST; + via_packet_socket = true; + break; + case N_DHCP4_C_MESSAGE_RENEW: + message_type = N_DHCP4_MESSAGE_REQUEST; + break; + case N_DHCP4_C_MESSAGE_REBIND: + message_type = N_DHCP4_MESSAGE_REQUEST; + break; + case N_DHCP4_C_MESSAGE_REBOOT: + message_type = N_DHCP4_MESSAGE_REQUEST; + via_packet_socket = true; + break; + case N_DHCP4_C_MESSAGE_RELEASE: + message_type = N_DHCP4_MESSAGE_RELEASE; + break; + case N_DHCP4_C_MESSAGE_DECLINE: + message_type = N_DHCP4_MESSAGE_DECLINE; + via_packet_socket = true; + break; + default: + abort(); + return -ENOTRECOVERABLE; + } + + /* + * We explicitly pass 0 as maximum message size, which makes + * NDhcp4Outgoing use the mandated default value from the spec (see its + * implementation). While the transport and like layers might support + * bigger MTUs (and we very likely know about them through + * n_dhcp4_client_update_mtu()), we cannot assume the target DHCP + * server supports parsing packets bigger than the minimum (and it is + * allowed to refuse bigger IP packets, even if the network supports + * transmission of them). + * + * We could theoretically increase this for packets other than the + * initial discovery. However, clients are unlikely to ever send large + * packets, so we just keep the same default for all outgoing packets. + */ + r = n_dhcp4_outgoing_new(&message, 0, N_DHCP4_OVERLOAD_FILE | N_DHCP4_OVERLOAD_SNAME); + if (r) + return r; + + header = n_dhcp4_outgoing_get_header(message); + n_dhcp4_c_connection_init_header(connection, header); + + message->userdata.type = type; + + /* + * Note that some implementations expect the MESSAGE_TYPE option to be + * the first option, and possibly even hard-code access to it. Hence, + * we really should make sure to pass it first as well. + */ + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_MESSAGE_TYPE, &message_type, sizeof(message_type)); + if (r) + return r; + + r = n_dhcp4_outgoing_append(message, + N_DHCP4_OPTION_CLIENT_IDENTIFIER, + connection->client_config->client_id, + connection->client_config->n_client_id); + if (r) + return r; + + switch (message_type) { + case N_DHCP4_MESSAGE_DISCOVER: + case N_DHCP4_MESSAGE_REQUEST: + case N_DHCP4_MESSAGE_INFORM: { + uint16_t mtu; + + if (connection->probe_config->n_request_parameters > 0) { + r = n_dhcp4_outgoing_append(message, + N_DHCP4_OPTION_PARAMETER_REQUEST_LIST, + connection->probe_config->request_parameters, + connection->probe_config->n_request_parameters); + if (r) + return r; + } + + if (via_packet_socket) { + /* + * In case of packet sockets, we do not support + * fragmentation. Hence, our maximum message size + * equals the transport MTU. In case no mtu is given, + * we use the minimum size mandated by the IP spec. If + * we omit the field, some implementations will + * interpret this to mean any packet size is supported, + * which we rather not want as default behavior (we can + * always support suppressing this field, if that is + * what the caller wants). + */ + mtu = htons(connection->mtu ?: N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE); + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE, &mtu, sizeof(mtu)); + if (r) + return r; + } else { + /* + * Once we use UDP sockets, we support fragmentation + * through the kernel IP stack. This means, the biggest + * message we can receive is the maximum UDP size plus + * the possible IP header. This would sum up to + * 2^16-1 + 20 (or even 2^16-1 + 60 if pedantic) and + * thus exceed the option field. Hence, we simply set + * the option to the maximum possible value. + */ + mtu = htons(UINT16_MAX); + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE, &mtu, sizeof(mtu)); + if (r) + return r; + } + + break; + } + default: + break; + } + + *messagep = message; + message = NULL; + return 0; +} + +/* + * RFC2131 3.1 + * + * The client broadcasts a DHCPDISCOVER message on its local physical + * subnet. The DHCPDISCOVER message MAY include options that suggest + * values for the network address and lease duration. BOOTP relay + * agents may pass the message on to DHCP servers not on the same + * physical subnet. + * + * RFC2131 3.5 + * + * [...] in its initial DHCPDISCOVER or DHCPREQUEST message, a client + * may provide the server with a list of specific parameters the + * client is interested in. If the client includes a list of + * parameters in a DHCPDISCOVER message, it MUST include that list in + * any subsequent DHCPREQUEST messages. + * + * [...] + * + * In addition, the client may suggest values for the network address + * and lease time in the DHCPDISCOVER message. The client may include + * the 'requested IP address' option to suggest that a particular IP + * address be assigned, and may include the 'IP address lease time' + * option to suggest the lease time it would like. Other options + * representing "hints" at configuration parameters are allowed in a + * DHCPDISCOVER or DHCPREQUEST message. + * + * RFC2131 4.4.1 + * + * The client generates and records a random transaction identifier and + * inserts that identifier into the 'xid' field. The client records its + * own local time for later use in computing the lease expiration. The + * client then broadcasts the DHCPDISCOVER on the local hardware + * broadcast address to the 0xffffffff IP broadcast address and 'DHCP + * server' UDP port. + * + * If the 'xid' of an arriving DHCPOFFER message does not match the + * 'xid' of the most recent DHCPDISCOVER message, the DHCPOFFER message + * must be silently discarded. Any arriving DHCPACK messages must be + * silently discarded. + */ +int n_dhcp4_c_connection_discover_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **requestp) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL; + int r; + + r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_DISCOVER); + if (r) + return r; + + *requestp = message; + message = NULL; + return 0; +} + +/* + * + * RFC2131 4.1.1 + * + * The DHCPREQUEST message contains the same 'xid' as the DHCPOFFER + * message. + * + * RFC2131 4.3.2 + * + * Client inserts the address of the selected server in 'server + * identifier', 'ciaddr' MUST be zero, 'requested IP address' MUST be + * filled in with the yiaddr value from the chosen DHCPOFFER. + */ +int n_dhcp4_c_connection_select_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **requestp, + NDhcp4Incoming *offer) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL; + struct in_addr client; + struct in_addr server; + uint32_t xid; + int r; + + n_dhcp4_incoming_get_yiaddr(offer, &client); + + r = n_dhcp4_incoming_query_server_identifier(offer, &server); + if (r) + return r; + + r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_SELECT); + if (r) + return r; + + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, &client, sizeof(client)); + if (r) + return r; + + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, &server, sizeof(server)); + if (r) + return r; + + /* + * SELECT continues the transaction started by DISCOVER, and as such + * keeps the same start time. We also have to preserve the base time + * of the selected lease as well as the transaction ID. + */ + message->userdata.start_time = offer->userdata.start_time; + message->userdata.base_time = offer->userdata.base_time; + n_dhcp4_incoming_get_xid(offer, &xid); + n_dhcp4_outgoing_set_xid(message, xid); + + *requestp = message; + message = NULL; + return 0; +} + +/* + * RFC2131 4.3.2 + * + * 'server identifier' MUST NOT be filled in, 'requested IP address' + * option MUST be filled in with client's notion of its previously + * assigned address. 'ciaddr' MUST be zero. The client is seeking to + * verify a previously allocated, cached configuration. Server SHOULD + * send a DHCPNAK message to the client if the 'requested IP address' + * is incorrect, or is on the wrong network. + */ +int n_dhcp4_c_connection_reboot_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **requestp, + const struct in_addr *client) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL; + int r; + + r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_REBOOT); + if (r) + return r; + + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, client, sizeof(*client)); + if (r) + return r; + + *requestp = message; + message = NULL; + return 0; +} + +/* + * RFC2131 4.3.2 + * + * 'server identifier' MUST NOT be filled in, 'requested IP address' + * option MUST NOT be filled in, 'ciaddr' MUST be filled in with + * client's IP address. In this situation, the client is completely + * configured, and is trying to extend its lease. This message will + * be unicast, so no relay agents will be involved in its + * transmission. Because 'giaddr' is therefore not filled in, the + * DHCP server will trust the value in 'ciaddr', and use it when + * replying to the client. + * + * A client MAY choose to renew or extend its lease prior to T1. The + * server may choose not to extend the lease (as a policy decision by + * the network administrator), but should return a DHCPACK message + * regardless. + * + * RFC2131 4.4.5 + * + * At time T1 the client moves to RENEWING state and sends (via unicast) + * a DHCPREQUEST message to the server to extend its lease. The client + * sets the 'ciaddr' field in the DHCPREQUEST to its current network + * address. The client records the local time at which the DHCPREQUEST + * message is sent for computation of the lease expiration time. The + * client MUST NOT include a 'server identifier' in the DHCPREQUEST + * message. + */ +int n_dhcp4_c_connection_renew_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **requestp) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL; + int r; + + r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_RENEW); + if (r) + return r; + + *requestp = message; + message = NULL; + return 0; +} + +/* + * RFC2131 4.3.2 + * + * 'server identifier' MUST NOT be filled in, 'requested IP address' + * option MUST NOT be filled in, 'ciaddr' MUST be filled in with + * client's IP address. In this situation, the client is completely + * configured, and is trying to extend its lease. This message MUST + * be broadcast to the 0xffffffff IP broadcast address. The DHCP + * server SHOULD check 'ciaddr' for correctness before replying to + * the DHCPREQUEST. + * + * RFC2131 4.4.5 + * + * If no DHCPACK arrives before time T2, the client moves to REBINDING + * state and sends (via broadcast) a DHCPREQUEST message to extend its + * lease. The client sets the 'ciaddr' field in the DHCPREQUEST to its + * current network address. The client MUST NOT include a 'server + * identifier' in the DHCPREQUEST message. + */ +int n_dhcp4_c_connection_rebind_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **requestp) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL; + int r; + + r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_REBIND); + if (r) + return r; + + *requestp = message; + message = NULL; + return 0; +} + +/* + * RFC2131 3.2 + * + * If the client detects that the IP address in the DHCPACK message + * is already in use, the client MUST send a DHCPDECLINE message to the + * server and restarts the configuration process by requesting a + * new network address. + * + * RFC2131 4.4.4 + * + * Because the client is declining the use of the IP address supplied by + * the server, the client broadcasts DHCPDECLINE messages. + */ +int n_dhcp4_c_connection_decline_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **requestp, + NDhcp4Incoming *ack, + const char *error) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL; + struct in_addr client; + struct in_addr server; + int r; + + n_dhcp4_incoming_get_yiaddr(ack, &client); + + r = n_dhcp4_incoming_query_server_identifier(ack, &server); + if (r) + return r; + + r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_DECLINE); + if (r) + return r; + + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, &client, sizeof(client)); + if (r) + return r; + + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, &server, sizeof(server)); + if (r) + return r; + + if (error) { + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_ERROR_MESSAGE, error, strlen(error) + 1); + if (r) + return r; + } + + *requestp = message; + message = NULL; + return 0; +} + +/* + * RFC2131 3.4 + * + * If a client has obtained a network address through some other means + * (e.g., manual configuration), it may use a DHCPINFORM request message + * to obtain other local configuration parameters. + * + * RFC2131 4.4 + * + * The DHCPINFORM message is not shown in figure 5. A client simply + * sends the DHCPINFORM and waits for DHCPACK messages. Once the client + * has selected its parameters, it has completed the configuration + * process. + * + * RFC2131 4.4.3 + * + * The client sends a DHCPINFORM message. The client may request + * specific configuration parameters by including the 'parameter request + * list' option. The client generates and records a random transaction + * identifier and inserts that identifier into the 'xid' field. The + * client places its own network address in the 'ciaddr' field. The + * client SHOULD NOT request lease time parameters. + * + * The client then unicasts the DHCPINFORM to the DHCP server if it + * knows the server's address, otherwise it broadcasts the message to + * the limited (all 1s) broadcast address. DHCPINFORM messages MUST be + * directed to the 'DHCP server' UDP port. + */ +int n_dhcp4_c_connection_inform_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **requestp) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL; + int r; + + r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_INFORM); + if (r) + return r; + + *requestp = message; + message = NULL; + return 0; +} + +/* + * RFC2131 3.1 + * + * The client may choose to relinquish its lease on a network address + * by sending a DHCPRELEASE message to the server. The client + * identifies the lease to be released with its 'client identifier', + * or 'chaddr' and network address in the DHCPRELEASE message. If the + * client used a 'client identifier' when it obtained the lease, it + * MUST use the same 'client identifier' in the DHCPRELEASE message. + * + * RFC2131 3.2 + * + * The client may choose to relinquish its lease on a network + * address by sending a DHCPRELEASE message to the server. The + * client identifies the lease to be released with its + * 'client identifier', or 'chaddr' and network address in the + * DHCPRELEASE message. + * + * Note that in this case, where the client retains its network + * address locally, the client will not normally relinquish its + * lease during a graceful shutdown. Only in the case where the + * client explicitly needs to relinquish its lease, e.g., the client + * is about to be moved to a different subnet, will the client send + * a DHCPRELEASE message. + * + * RFC2131 4.4.4 + * + * The client unicasts DHCPRELEASE messages to the server. + * + * RFC2131 4.4.6 + * + * If the client no longer requires use of its assigned network address + * (e.g., the client is gracefully shut down), the client sends a + * DHCPRELEASE message to the server. Note that the correct operation + * of DHCP does not depend on the transmission of DHCPRELEASE messages. + */ +int n_dhcp4_c_connection_release_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **requestp, + const char *error) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL; + int r; + + r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_RELEASE); + if (r) + return r; + + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, &connection->server_ip, sizeof(connection->server_ip)); + if (r) + return r; + + if (error) { + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_ERROR_MESSAGE, error, strlen(error) + 1); + if (r) + return r; + } + + *requestp = message; + message = NULL; + return 0; +} + +static int n_dhcp4_c_connection_send_request(NDhcp4CConnection *connection, + NDhcp4Outgoing *request, + uint64_t timestamp) { + int r; + + /* + * Increment the base time and reset the xid field, + * where applicable. We never alter the header on + * resends of SELECT, as it must always match the + * OFFER message they are in reply to. + */ + switch (request->userdata.type) { + case N_DHCP4_C_MESSAGE_DISCOVER: + case N_DHCP4_C_MESSAGE_INFORM: + case N_DHCP4_C_MESSAGE_REBOOT: + case N_DHCP4_C_MESSAGE_REBIND: + case N_DHCP4_C_MESSAGE_RENEW: + request->userdata.base_time = timestamp; + n_dhcp4_outgoing_set_xid(request, n_dhcp4_client_probe_config_get_random(connection->probe_config)); + + break; + case N_DHCP4_C_MESSAGE_SELECT: + case N_DHCP4_C_MESSAGE_DECLINE: + case N_DHCP4_C_MESSAGE_RELEASE: + break; + default: + c_assert(0); + } + + request->userdata.send_time = timestamp; + request->userdata.send_jitter = (n_dhcp4_client_probe_config_get_random(connection->probe_config) % 1000000000ULL); + n_dhcp4_c_connection_outgoing_set_secs(request); + + switch (request->userdata.type) { + case N_DHCP4_C_MESSAGE_DISCOVER: + case N_DHCP4_C_MESSAGE_SELECT: + case N_DHCP4_C_MESSAGE_REBOOT: + case N_DHCP4_C_MESSAGE_DECLINE: + r = n_dhcp4_c_connection_packet_broadcast(connection, request); + if (r) + return r; + break; + case N_DHCP4_C_MESSAGE_INFORM: + case N_DHCP4_C_MESSAGE_REBIND: + r = n_dhcp4_c_connection_udp_broadcast(connection, request); + if (r) + return r; + + break; + case N_DHCP4_C_MESSAGE_RENEW: + case N_DHCP4_C_MESSAGE_RELEASE: + r = n_dhcp4_c_connection_udp_send(connection, request); + if (r) + return r; + + break; + default: + c_assert(0); + } + + ++request->userdata.n_send; + return 0; +} + +int n_dhcp4_c_connection_start_request(NDhcp4CConnection *connection, + NDhcp4Outgoing *request, + uint64_t timestamp) { + int r; + + /* + * This function starts a request, but in the case of SELECT it + * continues a previous transaction, so we do not want to reset + * the start time. Only set the start time if it was not already + * set. + */ + if (request->userdata.start_time == 0) + request->userdata.start_time = timestamp; + + n_dhcp4_outgoing_free(connection->request); + connection->request = request; + + r = n_dhcp4_c_connection_send_request(connection, request, timestamp); + if (r) + return r; + + return 0; +} + +int n_dhcp4_c_connection_dispatch_timer(NDhcp4CConnection *connection, + uint64_t timestamp) { + uint64_t timeout; + int r; + + if (!connection->request) + return 0; + + n_dhcp4_c_connection_get_timeout(connection, &timeout); + + if (timeout > timestamp) + return 0; + + r = n_dhcp4_c_connection_send_request(connection, connection->request, timestamp); + if (r) + return r; + + return 0; +} + +int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection, + NDhcp4Incoming **messagep) { + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL; + uint8_t type; + int r; + + switch (connection->state) { + case N_DHCP4_C_CONNECTION_STATE_PACKET: + r = n_dhcp4_c_socket_packet_recv(connection->fd_packet, + connection->scratch_buffer, + sizeof(connection->scratch_buffer), + &message); + if (r) + return r; + + break; + case N_DHCP4_C_CONNECTION_STATE_DRAINING: + r = n_dhcp4_c_socket_packet_recv(connection->fd_packet, + connection->scratch_buffer, + sizeof(connection->scratch_buffer), + &message); + if (!r) + break; + else if (r != N_DHCP4_E_AGAIN) + return r; + + /* + * The UDP socket is open and the packet socket has been shut down + * and drained, clean up the packet socket and fall through to + * dispatching the UDP socket. + */ + r = epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_packet, NULL); + c_assert(!r); + connection->fd_packet = c_close(connection->fd_packet); + connection->state = N_DHCP4_C_CONNECTION_STATE_UDP; + + /* fall-through */ + case N_DHCP4_C_CONNECTION_STATE_UDP: + r = n_dhcp4_c_socket_udp_recv(connection->fd_udp, + connection->scratch_buffer, + sizeof(connection->scratch_buffer), + &message); + if (r) + return r; + + break; + default: + abort(); + return -ENOTRECOVERABLE; + } + + r = n_dhcp4_c_connection_verify_incoming(connection, message, &type); + if (r) + return r; + + switch (type) { + case N_DHCP4_MESSAGE_OFFER: + case N_DHCP4_MESSAGE_ACK: + case N_DHCP4_MESSAGE_NAK: + /* + * Remember the start time of the transaction, and the base + * time of any relative timestamps from the pending request. + * Thes same times applies to the response, and sholud be + * copied over. + */ + message->userdata.start_time = connection->request->userdata.start_time; + message->userdata.base_time = connection->request->userdata.base_time; + + if (type != N_DHCP4_MESSAGE_OFFER) { + /* + * We only allow one reply to ACK or NAK, but for OFFER we must + * accept several, so we do not free the pinned request. + */ + connection->request = n_dhcp4_outgoing_free(connection->request); + } + + break; + default: + break; + } + + *messagep = message; + message = NULL; + return 0; +} diff --git a/shared/n-dhcp4/src/n-dhcp4-c-lease.c b/shared/n-dhcp4/src/n-dhcp4-c-lease.c new file mode 100644 index 00000000..db1ffbae --- /dev/null +++ b/shared/n-dhcp4/src/n-dhcp4-c-lease.c @@ -0,0 +1,356 @@ +/* + * DHCP4 Client Leases + * + * This implements the public API wrapping DHCP4 client leases. A lease object + * conists of the information given to us from the server, together with the + * timestamp recording the start of the validity of the lease. + * + * A probe may yield many OFFERS, each of which contains a lease object. One of + * these offers may be SELECTED, which implicitly rejects all the others. + * The server may then ACK or NAK the lease which tells us whether or not we + * are permitted to start using it. Once an ACK has been received, we can + * configure the address, and only then can we SELECT the lease. If we + * determine that the offered lease was not appropriate after all we + * may DECLINE it instead. + */ + +#include <assert.h> +#include <c-list.h> +#include <c-stdaux.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include "n-dhcp4.h" +#include "n-dhcp4-private.h" + +/* + * Compute the absolute timeouts from an incoming message. A message contains relative timeouts and the userdata + * of the incoming message is set to the offset we must apply to get the absolute values. + * + * The special value UINT64_MAX is returned to indicate no or infinite timeouts. In case the given timeouts + * are invalid relative to each other, we recompute T1 and/or T2 to take their default values. Later timeouts + * take predecende above earlier ones (T1 is adjusted if it conflicts with T2, etc). + */ +static int n_dhcp4_incoming_get_timeouts(NDhcp4Incoming *message, uint64_t *t1p, uint64_t *t2p, uint64_t *lifetimep) { + uint64_t lifetime, t2, t1; + uint32_t u32; + int r; + + r = n_dhcp4_incoming_query_lifetime(message, &u32); + if (r == N_DHCP4_E_UNSET) { + lifetime = UINT64_MAX; + } else if (r) { + return r; + } else if (u32 == UINT32_MAX) { + lifetime = UINT64_MAX; + } else { + if (u32 == UINT32_MAX) + lifetime = UINT64_MAX; + else + lifetime = u32 * (1000000000ULL); + } + + r = n_dhcp4_incoming_query_t2(message, &u32); + if (r == N_DHCP4_E_UNSET) { + if (lifetime == UINT64_MAX) + t2 = UINT64_MAX; + else + t2 = (lifetime * 7) / 8; + } else if (r) { + return r; + } else { + if (u32 == UINT32_MAX) + t2 = UINT64_MAX; + else + t2 = u32 * (1000000000ULL); + + if (t2 > lifetime) + t2 = (lifetime * 7) / 8; + } + + r = n_dhcp4_incoming_query_t1(message, &u32); + if (r == N_DHCP4_E_UNSET) { + if (t2 == UINT64_MAX) + t1 = UINT64_MAX; + else + t1 = (t2 * 4) / 7; + } else if (r) { + return r; + } else { + if (u32 == UINT32_MAX) + t1 = UINT64_MAX; + else + t1 = u32 * (1000000000ULL); + + if (t1 > t2) + t1 = (t2 * 4) / 7; + } + + if (lifetime != UINT64_MAX) + lifetime += message->userdata.base_time; + if (t2 != UINT64_MAX) + t2 += message->userdata.base_time; + if (t1 != UINT64_MAX) + t1 += message->userdata.base_time; + + *lifetimep = lifetime; + *t2p = t2; + *t1p = t1; + return 0; +} + +/** + * n_dhcp4_client_lease_new() - allocate new client lease object + * @leasep: output argumnet for new client lease object + * @message: incoming message representing the lease + * + * This creates a new client lease object. Client lease objects are simple + * wrappers around an incoming message representing a lease. + * + * Return: 0 on success, negative error code on failure. + */ +int n_dhcp4_client_lease_new(NDhcp4ClientLease **leasep, NDhcp4Incoming *message) { + _c_cleanup_(n_dhcp4_client_lease_unrefp) NDhcp4ClientLease *lease = NULL; + int r; + + c_assert(leasep); + + lease = malloc(sizeof(*lease)); + if (!lease) + return -ENOMEM; + + *lease = (NDhcp4ClientLease)N_DHCP4_CLIENT_LEASE_NULL(*lease); + + r = n_dhcp4_incoming_get_timeouts(message, &lease->t1, &lease->t2, &lease->lifetime); + if (r) + return r; + + lease->message = message; + *leasep = lease; + lease = NULL; + return 0; +} + +static void n_dhcp4_client_lease_free(NDhcp4ClientLease *lease) { + n_dhcp4_client_lease_unlink(lease); + n_dhcp4_incoming_free(lease->message); + free(lease); +} + +/** + * n_dhcp4_client_lease_ref() - reference client lease + * @lease: the client lease object to reference + * + * Take a new reference to a client lease. + * + * Return: the lease. + */ +_c_public_ NDhcp4ClientLease *n_dhcp4_client_lease_ref(NDhcp4ClientLease *lease) { + if (lease) + ++lease->n_refs; + return lease; +} + +/** + * n_dhcp4_client_lease_unref() - dereference client lease + * @lease: the client lease object to dereference + * + * Relase a reference to a client lease. + * + * Return: NULL. + */ +_c_public_ NDhcp4ClientLease *n_dhcp4_client_lease_unref(NDhcp4ClientLease *lease) { + if (lease && !--lease->n_refs) + n_dhcp4_client_lease_free(lease); + return NULL; +} + +/** + * n_dhcp4_client_lease_link() - link lease into probe + * @lease: the lease to operate on + * @probe: the probe to link the lease into + * + * Associate a lease with a probe. The lease may not already be linked. + */ +void n_dhcp4_client_lease_link(NDhcp4ClientLease *lease, NDhcp4ClientProbe *probe) { + c_assert(!lease->probe); + c_assert(!c_list_is_linked(&lease->probe_link)); + + lease->probe = probe; + c_list_link_tail(&probe->lease_list, &lease->probe_link); +} + +/** + * n_dhcp4_client_lease_unlink() - unlinke lease from its probe + * @lease: the lease to operate on + * + * Dissassociate a lease from a probe if it is associated with one. Otherwise, + * this is a noop. + */ +void n_dhcp4_client_lease_unlink(NDhcp4ClientLease *lease) { + lease->probe = NULL; + c_list_unlink(&lease->probe_link); +} + +/** + * n_dhcp4_client_lease_get_yiaddr() - get the IP address + * @lease: the lease to operate on + * @yiaddr: return argument for the IP address + * + * Gets the IP address cotained in the lease. Or INADDR_ANY if the lease + * does not contain an IP address. + */ +_c_public_ void n_dhcp4_client_lease_get_yiaddr(NDhcp4ClientLease *lease, struct in_addr *yiaddr) { + NDhcp4Header *header = n_dhcp4_incoming_get_header(lease->message); + + yiaddr->s_addr = header->yiaddr; +} + +/** + * n_dhcp4_client_lease_get_lifetime() - get the lifetime + * @lease: the lease to operate on + * @ns_lifetimep: return argument for the lifetime in nano seconds + * + * Gets the end of the lease's lifetime in nanoseconds according to CLOCK_BOOTTIME, + * or (uint64_t)-1 for permanent leases. + */ +_c_public_ void n_dhcp4_client_lease_get_lifetime(NDhcp4ClientLease *lease, uint64_t *ns_lifetimep) { + *ns_lifetimep = lease->lifetime; +} + +/** + * n_dhcp4_client_lease_query() - query the lease for an option + * @lease: the lease to operate on + * @option: the DHCP4 option code + * @datap: return argument of the data pointer + * @n_datap: return argument of data length in bytes + * + * Query the lease for a given option. Options internal to the DHCP protocol cannot + * be queried, and only options that were explicitly requested can be queried. + * + * Return: 0 on success, + * N_DCHP4_E_INTERNAL if an invalid option is queried, + * N_DHCP4_E_UNSET if the lease did not contain the option, or + * a negative error code on failure. + */ +_c_public_ int n_dhcp4_client_lease_query(NDhcp4ClientLease *lease, uint8_t option, uint8_t **datap, size_t *n_datap) { + switch (option) { + case N_DHCP4_OPTION_PAD: + case N_DHCP4_OPTION_REQUESTED_IP_ADDRESS: + case N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME: + case N_DHCP4_OPTION_OVERLOAD: + case N_DHCP4_OPTION_MESSAGE_TYPE: + case N_DHCP4_OPTION_SERVER_IDENTIFIER: + case N_DHCP4_OPTION_PARAMETER_REQUEST_LIST: + case N_DHCP4_OPTION_ERROR_MESSAGE: + case N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE: + case N_DHCP4_OPTION_RENEWAL_T1_TIME: + case N_DHCP4_OPTION_REBINDING_T2_TIME: + case N_DHCP4_OPTION_END: + return N_DHCP4_E_INTERNAL; + } + + /* XXX: refuse to return options that were not requested */ + + return n_dhcp4_incoming_query(lease->message, option, datap, n_datap); +} + +/** + * n_dhcp4_client_lease_select() - select an offered lease + * @lease: lease to operate on + * + * Select a lease. This must be a lease that was offered, once + * one of the leases that were offered in response to a probe was + * selected none of the others can be. + * + * Return: 0 on success, or a negative error code on failure. + */ +_c_public_ int n_dhcp4_client_lease_select(NDhcp4ClientLease *lease) { + NDhcp4ClientLease *l, *t_l; + NDhcp4ClientProbe *probe; + int r; + + /* XXX error handling, this must be an OFFER */ + + if (!lease->probe) + return -ENOTRECOVERABLE; + if (lease->probe->current_lease) + return -ENOTRECOVERABLE; + + r = n_dhcp4_client_probe_transition_select(lease->probe, lease->message, n_dhcp4_gettime(CLOCK_BOOTTIME)); + if (r) + return r; + + /* + * Only one of the offered leases can be selected, so flush the list. + * All offered lease, including this one are now dead. + */ + probe = lease->probe; + c_list_for_each_entry_safe(l, t_l, &probe->lease_list, probe_link) + n_dhcp4_client_lease_unlink(l); + + return 0; +} + +/** + * n_dhcp4_client_lease_accept() - accept an ack'ed lease + * @lease: lease to operate on + * + * Accept a lease. This must be a lease that was ack'ed by the + * server. + * + * The offered IP address must be fully configured before the lease + * can be accepted. + * + * Return: 0 on success, or a negative error code on failure. + */ +_c_public_ int n_dhcp4_client_lease_accept(NDhcp4ClientLease *lease) { + int r; + + /* XXX error handling, this must be an ACK */ + + if (!lease->probe) + return -ENOTRECOVERABLE; + if (lease->probe->current_lease != lease) + return -ENOTRECOVERABLE; + + r = n_dhcp4_client_probe_transition_accept(lease->probe, lease->message); + if (r) + return r; + + n_dhcp4_client_lease_unlink(lease); + + return 0; +} + +/** + * n_dhcp4_client_lease_decline() - decline an ack'ed lease + * @lease: lease to operate on + * + * Decline a lease. This must be a lease that was ack'ed by the + * server. + * + * The offered IP address must not be used once the lease has been + * decline. + * + * Return: 0 on success, or a negative error code on failure. + */ +_c_public_ int n_dhcp4_client_lease_decline(NDhcp4ClientLease *lease, const char *error) { + int r; + + /* XXX: error handling, this must be an ACK */ + + if (!lease->probe) + return -ENOTRECOVERABLE; + if (lease->probe->current_lease != lease) + return -ENOTRECOVERABLE; + + r = n_dhcp4_client_probe_transition_decline(lease->probe, lease->message, error, n_dhcp4_gettime(CLOCK_BOOTTIME)); + if (r) + return r; + + lease->probe->current_lease = n_dhcp4_client_lease_unref(lease->probe->current_lease); + n_dhcp4_client_lease_unlink(lease); + + return 0; +} diff --git a/shared/n-dhcp4/src/n-dhcp4-c-probe.c b/shared/n-dhcp4/src/n-dhcp4-c-probe.c new file mode 100644 index 00000000..308cff83 --- /dev/null +++ b/shared/n-dhcp4/src/n-dhcp4-c-probe.c @@ -0,0 +1,1203 @@ +/* + * DHCPv4 Client Probes + * + * The probe object is used to represent the lifetime of a DHCP client session. + * A running probe discovers DHCP servers, requests a lease, and maintains that + * lease. + */ + +#include <assert.h> +#include <c-list.h> +#include <c-siphash.h> +#include <c-stdaux.h> +#include <errno.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/auxv.h> +#include "n-dhcp4.h" +#include "n-dhcp4-private.h" + + +static int n_dhcp4_client_probe_option_new(NDhcp4ClientProbeOption **optionp, + uint8_t option, + const void *data, + uint8_t n_data) { + NDhcp4ClientProbeOption *op; + + op = malloc(sizeof(op) + n_data); + if (!op) + return -ENOMEM; + + op->option = option; + op->n_data = n_data; + memcpy(op->data, data, n_data); + + *optionp = op; + return 0; +} + +static void n_dhcp4_client_probe_option_free(NDhcp4ClientProbeOption *option) { + if (option) + free(option); +} + +/** + * n_dhcp4_client_probe_config_new() - create new probe configuration + * @configp: output argument to store new configuration + * + * This creates a new probe configuration object. The object is a collection of + * parameters for probes. No data verification is done by the configuration + * object. Instead, when passing the configuration to the constructor of a + * probe, this constructor will perform parameter validation. + * + * A probe configuration is an unlinked object only used to pass information to + * a probe constructor. The caller fully owns the returned configuration object + * and is responsible to free it when no longer needed. + * + * Return: 0 on success, negative error code on failure. + */ +_c_public_ int n_dhcp4_client_probe_config_new(NDhcp4ClientProbeConfig **configp) { + _c_cleanup_(n_dhcp4_client_probe_config_freep) NDhcp4ClientProbeConfig *config = NULL; + + config = calloc(1, sizeof(*config)); + if (!config) + return -ENOMEM; + + *config = (NDhcp4ClientProbeConfig)N_DHCP4_CLIENT_PROBE_CONFIG_NULL(*config); + + *configp = config; + config = NULL; + return 0; +} + +/** + * n_dhcp4_client_probe_config_free() - destroy probe configuration + * @config: configuration to operate on, or NULL + * + * This destroys a probe configuration object and deallocates all its + * resources. + * + * If @config is NULL, this is a no-op. + * + * Return: NULL is returned. + */ +_c_public_ NDhcp4ClientProbeConfig *n_dhcp4_client_probe_config_free(NDhcp4ClientProbeConfig *config) { + if (!config) + return NULL; + + for (unsigned int i = 0; i <= UINT8_MAX; ++i) + n_dhcp4_client_probe_option_free(config->options[i]); + + free(config); + + return NULL; +} + +/** + * n_dhcp4_client_probe_config_dup() - duplicate probe configuration + * @config: configuration to operate on + * @dupp: output argument for duplicate + * + * This duplicates the probe configuration given as @config and returns it in + * @dupp to the caller. + * + * Return: 0 on success, negative error code on failure. + */ +int n_dhcp4_client_probe_config_dup(NDhcp4ClientProbeConfig *config, + NDhcp4ClientProbeConfig **dupp) { + _c_cleanup_(n_dhcp4_client_probe_config_freep) NDhcp4ClientProbeConfig *dup = NULL; + int r; + + r = n_dhcp4_client_probe_config_new(&dup); + if (r) + return r; + + dup->inform_only = config->inform_only; + dup->init_reboot = config->init_reboot; + dup->requested_ip = config->requested_ip; + dup->ms_start_delay = config->ms_start_delay; + + for (unsigned int i = 0; i < config->n_request_parameters; ++i) + dup->request_parameters[dup->n_request_parameters++] = config->request_parameters[i]; + + for (unsigned int i = 0; i <= UINT8_MAX; ++i) { + if (!config->options[i]) + break; + + r = n_dhcp4_client_probe_option_new(&dup->options[i], + config->options[i]->option, + config->options[i]->data, + config->options[i]->n_data); + if (r) + return r; + } + + *dupp = dup; + dup = NULL; + return 0; +} + +/** + * n_dhcp4_client_probe_config_set_inform_only() - set inform-only property + * @config: configuration to operate on + * @inform_only: value to set + * + * This sets the inform-only property of the given configuration object. This + * property controls whether the client probe should request a full lease, or + * whether it should just ask for auxiliary information without requesting an + * address. + * + * The default is to request a full lease and address. If inform-only is set to + * true, only auxiliary information will be requested. + * + * XXX: This is currently not implemented, and setting the property has no effect. + */ +_c_public_ void n_dhcp4_client_probe_config_set_inform_only(NDhcp4ClientProbeConfig *config, bool inform_only) { + config->inform_only = inform_only; +} + +/** + * n_dhcp4_client_probe_config_set_init_reboot() - set init-reboot property + * @config: configuration to operate on + * @init_reboot: value to set + * + * This sets the init-reboot property of the given configuration object. If this + * is enabled, a requested IP address must also be set. + * + * The default is false. If set to true, a probe will make use of the + * INIT-REBOOT path, as described by the DHCP specification. In most cases, you + * do not want this. + * + * XXX: This is currently not implemented, and setting the property has no effect. + * + * Background: The INIT-REBOOT path allows a DHCP client to skip + * server-discovery when rebooting/resuming their machine. The DHCP + * client simply re-requests the lease it had acquired before. This + * saves one roundtrip in the success-case, since the DISCOVER step + * is skipped. However, there are little to no timeouts involved, + * so the roundtrip should be barely noticeable. In contrast, if + * the INIT-REBOOT fails (because the lease is no longer valid, or + * not valid on this network), the client has to wait for a + * possible answer to the request before actually starting the DHCP + * process all over. This significantly increases the time needed + * to switch networks. + * The INIT-REBOOT state might have been a real improvements with + * the old resend-timeouts mandated by the DHCP specification. + * However, on modern networks with improved timeout values we + * recommend against using it. + */ +_c_public_ void n_dhcp4_client_probe_config_set_init_reboot(NDhcp4ClientProbeConfig *config, bool init_reboot) { + config->init_reboot = init_reboot; +} + +/** + * n_dhcp4_client_probe_config_set_requested_ip() - set requested-ip property + * @config: configuration to operate on + * @ip: value to set + * + * This sets the requested-ip property of the given configuration object. + * + * The default is all 0. If set to something else, the DHCP discovery will + * include this IP in its requests to tell DHCP servers which address to pick. + * Servers are not required to honor this, nor does this have any effect on + * servers not serving this address. + * + * This field should always be set if the caller knows of an address that was + * previously acquired on this network. It serves as hint to servers and will + * allow them to provide the same address again. + */ +_c_public_ void n_dhcp4_client_probe_config_set_requested_ip(NDhcp4ClientProbeConfig *config, struct in_addr ip) { + config->requested_ip = ip; +} + +/** + * n_dhcp4_client_probe_config_set_start_delay() - set start delay + * @config: configuration to operate on + * @msecs: value to set + * + * This sets the start delay property of the given configuration object. + * + * The default is 9000 ms, which is based on RFC2131. In the RFC the start + * delay is specified to be a random value in the range 1000 to 10.000 ms. + * However, there does not appear to be any particular reason to + * unconditionally wait at least one second, so we move the range down to + * start at 0 ms. The reaon for the random delay is to avoid network-wide + * events causing too much simultaneous network traffic. However, on modern + * networks, a more reasonable value may be in the 10 ms range. + */ +_c_public_ void n_dhcp4_client_probe_config_set_start_delay(NDhcp4ClientProbeConfig *config, uint64_t msecs) { + config->ms_start_delay = msecs; +} + +/** + * n_dhpc4_client_probe_config_request_option() - append option to request from the server + * @config: configuration to operate on + * @option: option to request + * + * This adds an option to the list of options to request from the server. + * + * A server may send options that we do not requst, and it may omit options + * that we do request. However, to increase the likelyhood of uniform behavior + * between server implementations, we do not expose options that were not + * explicitly requested. + * + * When called multiple times, the order matters. Earlier requests are + * considered higher priority than later requests, in case the server must omit + * some, due to a lack of space. If the same option is requested more than once, + * only the first call has an effect. + */ +_c_public_ void n_dhcp4_client_probe_config_request_option(NDhcp4ClientProbeConfig *config, uint8_t option) { + for (unsigned int i = 0; i < config->n_request_parameters; ++i) { + if (config->request_parameters[i] == option) + return; + } + + c_assert(config->n_request_parameters <= UINT8_MAX); + + config->request_parameters[config->n_request_parameters++] = option; +} + +/** + * n_dhcp4_client_probe_config_append_option() - append option to outgoing messages + * @config: configuration to operate on + * @option: DHCP option number + * @data: payload + * @n_data: number of bytes in payload + * + * This sets extra options on a given configuration object. + * + * These options are appended verbatim to outgoing messages where + * that is supported by the specification. The same options are + * appended to all messages. + * + * No option may be appended more than once. Options considered internal + * to the DHCP protocol may not be appended. + * + * Return: 0 on success, N_DHCP4_E_DUPLICATE_OPTION if an option has already been + * appended, N_DHCP4_E_INTERNAL if the option is not configurable, or + * a negative error code on failure. + */ +_c_public_ int n_dhcp4_client_probe_config_append_option(NDhcp4ClientProbeConfig *config, + uint8_t option, + const void *data, + uint8_t n_data) { + int r; + + /* XXX: filter internal options */ + + for (unsigned int i = 0; i <= UINT8_MAX; ++i) { + if (config->options[i]) { + if (config->options[i]->option == option) + return N_DHCP4_E_DUPLICATE_OPTION; + + continue; + } + + r = n_dhcp4_client_probe_option_new(&config->options[i], + option, + data, + n_data); + if (r) + return r; + + return 0; + } + + c_assert(0); + return -ENOTRECOVERABLE; +} + +static void n_dhcp4_client_probe_config_initialize_random_seed(NDhcp4ClientProbeConfig *config) { + uint8_t hash_seed[] = { + 0x25, 0x3f, 0x02, 0x75, 0x3a, 0xb8, 0x4f, 0x91, + 0x9d, 0x0a, 0xd6, 0x15, 0x9d, 0x72, 0x7b, 0xcb, + }; + CSipHash hash = C_SIPHASH_NULL; + unsigned short int seed16v[3]; + const uint8_t *p; + uint64_t u64; + int r; + + /* + * Initialize seed48_r(3) + * + * We need random jitter for all timeouts and delays, used to reduce + * network traffic during bursts. This is not meant as security measure + * but only meant to improve network utilization during bursts. The + * random source is thus negligible. However, we want, under all + * circumstances, avoid two instances running with the same seed. Thus + * we source the seed from AT_RANDOM, which grants us a per-process + * unique seed. We then add the current time to make sure consequetive + * instances use different seeds (to avoid clashes if processes are + * duplicated, or similar), and lastly we add the config memory address + * to avoid clashes of multiple parallel instances. + * + * Again, none of these are meant as security measure, but only to + * avoid *ACCIDENTAL* seed clashes. That is, in the case that many + * transactions are started in parallel, we delay the individual + * messages (as described in the spec), to reduce the traffic on the + * network and the chance of packets being dropped (and thus triggering + * timeouts and resends). + * + * We hash everything through SipHash, to avoid exposing AT_RANDOM and + * other sources to the network. We use a static salt to distinguish it + * from other implementations using the same random source. + */ + c_siphash_init(&hash, hash_seed); + + p = (const uint8_t *)getauxval(AT_RANDOM); + if (p) + c_siphash_append(&hash, p, 16); + + u64 = n_dhcp4_gettime(CLOCK_MONOTONIC); + c_siphash_append(&hash, (const uint8_t *)&u64, sizeof(u64)); + + c_siphash_append(&hash, (const uint8_t *)&config, sizeof(config)); + + u64 = c_siphash_finalize(&hash); + + seed16v[0] = (u64 >> 0) ^ (u64 >> 48); + seed16v[1] = (u64 >> 16) ^ (u64 >> 0); + seed16v[2] = (u64 >> 32) ^ (u64 >> 16); + + r = seed48_r(seed16v, &config->entropy); + c_assert(!r); +} + +/** + * n_dhcp4_client_probe_config_get_random() - get random data + * @config: config object to operate on + * + * Fetch the next 32bit random number from the entropy pool in @config. + * Note that this is in no way suitable for security purposes. + * + * Return: the random data. + */ +uint32_t n_dhcp4_client_probe_config_get_random(NDhcp4ClientProbeConfig *config) { + long int result; + int r; + + r = mrand48_r(&config->entropy, &result); + c_assert(!r); + + return result; +}; + +/** + * n_dhcp4_client_probe_new() - create new client probe + * @probep: output argument for new client probe + * @config: probe configuration + * @client: client to probe on behalf of + * @ns_now: the current time + * + * This creates a new client probe object. + * + * If one is already running, the new one will be immediately (but asynchronously) + * cancelled. Otherwise, a DISCOVER event is scheduled after a randomized delay. + * + * Return: 0 on success, or a negative error code on failure. + */ +int n_dhcp4_client_probe_new(NDhcp4ClientProbe **probep, + NDhcp4ClientProbeConfig *config, + NDhcp4Client *client, + uint64_t ns_now) { + _c_cleanup_(n_dhcp4_client_probe_freep) NDhcp4ClientProbe *probe = NULL; + bool active; + int r; + + /* + * If there is already a probe attached, we create the new probe in + * detached state. It will not be linked into the epoll context and not + * be useful in any way. We immediately raise the CANCELLED event to + * notify the caller about it. + */ + active = !client->current_probe; + + probe = calloc(1, sizeof(*probe)); + if (!probe) + return -ENOMEM; + + *probe = (NDhcp4ClientProbe)N_DHCP4_CLIENT_PROBE_NULL(*probe); + probe->client = n_dhcp4_client_ref(client); + + r = n_dhcp4_client_probe_config_dup(config, &probe->config); + if (r) + return r; + + /* + * XXX: make seed initialization optional, so the entropy can be reused. + */ + n_dhcp4_client_probe_config_initialize_random_seed(probe->config); + + r = n_dhcp4_c_connection_init(&probe->connection, + client->config, + probe->config, + active ? client->fd_epoll : -1); + if (r) + return r; + + if (active) { + /* + * Defer the sending of DISCOVER by a random amount (by default up to 9 seconds). + */ + probe->ns_deferred = ns_now + (n_dhcp4_client_probe_config_get_random(probe->config) % (probe->config->ms_start_delay * 1000000ULL)); + probe->client->current_probe = probe; + } else { + r = n_dhcp4_client_probe_raise(probe, + NULL, + N_DHCP4_CLIENT_EVENT_CANCELLED); + if (r) + return r; + } + + *probep = probe; + probe = NULL; + return 0; +} + +/** + * n_dhcp4_client_probe_free() - destroy a probe + * @probe: probe to operate on, or NULL + * + * This destroys a probe object and deallocates all its resources. + * + * If @probe is NULL, this is a no-op. + * + * Return: NULL is returned. + */ +_c_public_ NDhcp4ClientProbe *n_dhcp4_client_probe_free(NDhcp4ClientProbe *probe) { + NDhcp4CEventNode *node, *t_node; + NDhcp4ClientLease *lease, *t_lease; + + if (!probe) + return NULL; + + c_list_for_each_entry_safe(lease, t_lease, &probe->lease_list, probe_link) + n_dhcp4_client_lease_unlink(lease); + + c_list_for_each_entry_safe(node, t_node, &probe->event_list, probe_link) + n_dhcp4_c_event_node_free(node); + + if (probe == probe->client->current_probe) + probe->client->current_probe = NULL; + + n_dhcp4_client_lease_unref(probe->current_lease); + n_dhcp4_c_connection_deinit(&probe->connection); + n_dhcp4_client_unref(probe->client); + n_dhcp4_client_probe_config_free(probe->config); + + c_assert(c_list_is_empty(&probe->lease_list)); + c_assert(c_list_is_empty(&probe->event_list)); + free(probe); + + return NULL; +} + +/** + * n_dhcp4_client_probe_set_userdata() - set userdata pointer + * @probe: the probe to operate on + * @userdata: pointer to userdata + * + * Set a userdata pointer. The pointed to data is still owned by the caller, and + * is completely opaque to the probe. + */ +_c_public_ void n_dhcp4_client_probe_set_userdata(NDhcp4ClientProbe *probe, void *userdata) { + probe->userdata = userdata; +} + +/** + * n_dhcp4_client_probe_get_userdata() - get userdata pointer + * @probe: the probe to operate on + * @userdatap: return pointer for userdata pointer + * + * Get the userdata pointer. The lifetime of the userdata and making sure it is + * still valid when accessed via the probe is the responsibility of the caller. + */ +_c_public_ void n_dhcp4_client_probe_get_userdata(NDhcp4ClientProbe *probe, void **userdatap) { + *userdatap = probe->userdata; +} + +/** + * n_dhcp4_client_probe_raise() - XXX + */ +int n_dhcp4_client_probe_raise(NDhcp4ClientProbe *probe, NDhcp4CEventNode **nodep, unsigned int event) { + NDhcp4CEventNode *node; + int r; + + r = n_dhcp4_client_raise(probe->client, &node, event); + if (r) + return r; + + switch (event) { + case N_DHCP4_CLIENT_EVENT_OFFER: + node->event.offer.probe = probe; + break; + case N_DHCP4_CLIENT_EVENT_GRANTED: + node->event.granted.probe = probe; + break; + case N_DHCP4_CLIENT_EVENT_RETRACTED: + node->event.retracted.probe = probe; + break; + case N_DHCP4_CLIENT_EVENT_EXTENDED: + node->event.extended.probe = probe; + break; + case N_DHCP4_CLIENT_EVENT_EXPIRED: + node->event.expired.probe = probe; + break; + case N_DHCP4_CLIENT_EVENT_CANCELLED: + node->event.cancelled.probe = probe; + break; + default: + c_assert(0); + n_dhcp4_c_event_node_free(node); + return -ENOTRECOVERABLE; + } + + if (nodep) + *nodep = node; + return 0; +} + +void n_dhcp4_client_probe_get_timeout(NDhcp4ClientProbe *probe, uint64_t *timeoutp) { + uint64_t t1 = 0; + uint64_t t2 = 0; + uint64_t lifetime = 0; + uint64_t timeout = 0; + + if (probe->current_lease) { + t1 = probe->current_lease->t1; + t2 = probe->current_lease->t2; + lifetime = probe->current_lease->lifetime; + } + + n_dhcp4_c_connection_get_timeout(&probe->connection, &timeout); + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + if (probe->ns_deferred && (!timeout || probe->ns_deferred < timeout)) + timeout = probe->ns_deferred; + + break; + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + if (t1 && (!timeout || t1 < timeout)) + timeout = t1; + + /* fall-through */ + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + if (t2 && (!timeout || t2 < timeout)) + timeout = t2; + + /* fall-through */ + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + if (lifetime && (!timeout || lifetime < timeout)) + timeout = lifetime; + break; + default: + /* ignore */ + break; + } + + *timeoutp = timeout; +} + +static int n_dhcp4_client_probe_outgoing_append_options(NDhcp4ClientProbe *probe, NDhcp4Outgoing *outgoing) { + int r; + + for (unsigned int i = 0; i <= UINT8_MAX; ++i) { + if (!probe->config->options[i]) + break; + + r = n_dhcp4_outgoing_append(outgoing, + probe->config->options[i]->option, + probe->config->options[i]->data, + probe->config->options[i]->n_data); + if (r) { + if (r == N_DHCP4_E_NO_SPACE) + /* XXX */ + break; + + return r; + } + } + + return 0; +} + +static int n_dhcp4_client_probe_transition_deferred(NDhcp4ClientProbe *probe, uint64_t ns_now) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL; + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + r = n_dhcp4_c_connection_listen(&probe->connection); + if (r) + return r; + + r = n_dhcp4_c_connection_discover_new(&probe->connection, &request); + if (r) + return r; + + if (probe->config->requested_ip.s_addr != INADDR_ANY) { + r = n_dhcp4_outgoing_append_requested_ip(request, probe->config->requested_ip); + if (r) + return r; + } + + r = n_dhcp4_client_probe_outgoing_append_options(probe, request); + if (r) + return r; + + r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now); + if (r) + return r; + else + request = NULL; /* consumed */ + + probe->state = N_DHCP4_CLIENT_PROBE_STATE_SELECTING; + probe->ns_deferred = 0; + + break; + + case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: + default: + abort(); + break; + } + + return 0; +} + +static int n_dhcp4_client_probe_transition_t1(NDhcp4ClientProbe *probe, uint64_t ns_now) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL; + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + r = n_dhcp4_c_connection_renew_new(&probe->connection, &request); + if (r) + return r; + + r = n_dhcp4_client_probe_outgoing_append_options(probe, request); + if (r) + return r; + + r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now); + if (r) + return r; + else + request = NULL; /* consumed */ + + probe->state = N_DHCP4_CLIENT_PROBE_STATE_RENEWING; + + break; + + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: + default: + abort(); + break; + } + + return 0; +} + +static int n_dhcp4_client_probe_transition_t2(NDhcp4ClientProbe *probe, uint64_t ns_now) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL; + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + r = n_dhcp4_c_connection_rebind_new(&probe->connection, &request); + if (r) + return r; + + r = n_dhcp4_client_probe_outgoing_append_options(probe, request); + if (r) + return r; + + r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now); + if (r) + return r; + else + request = NULL; /* consumed */ + + probe->state = N_DHCP4_CLIENT_PROBE_STATE_REBINDING; + + break; + + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: + default: + abort(); + break; + } + + return 0; +} + +static int n_dhcp4_client_probe_transition_lifetime(NDhcp4ClientProbe *probe) { + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + + /* XXX */ + + r = n_dhcp4_client_probe_raise(probe, + NULL, + N_DHCP4_CLIENT_EVENT_EXPIRED); + if (r) + return r; + + c_assert(probe->client->current_probe == probe); + probe->client->current_probe = NULL; + + n_dhcp4_c_connection_close(&probe->connection); + + probe->state = N_DHCP4_CLIENT_PROBE_STATE_EXPIRED; + + break; + + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: + default: + abort(); + break; + } + + return 0; +} + +static int n_dhcp4_client_probe_transition_offer(NDhcp4ClientProbe *probe, NDhcp4Incoming *message) { + _c_cleanup_(n_dhcp4_client_lease_unrefp) NDhcp4ClientLease *lease = NULL; + NDhcp4CEventNode *node; + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: + + r = n_dhcp4_client_probe_raise(probe, + &node, + N_DHCP4_CLIENT_EVENT_OFFER); + if (r) + return r; + + r = n_dhcp4_client_lease_new(&lease, message); + if (r) + return r; + + /* message consumed, do not fail */ + + n_dhcp4_client_lease_link(lease, probe); + + node->event.offer.lease = n_dhcp4_client_lease_ref(lease); + + break; + + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: + default: + /* ignore */ + break; + } + + return 0; +} + +static int n_dhcp4_client_probe_transition_ack(NDhcp4ClientProbe *probe, NDhcp4Incoming *message) { + _c_cleanup_(n_dhcp4_client_lease_unrefp) NDhcp4ClientLease *lease = NULL; + NDhcp4CEventNode *node; + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + + r = n_dhcp4_client_probe_raise(probe, + &node, + N_DHCP4_CLIENT_EVENT_EXTENDED); + if (r) + return r; + + r = n_dhcp4_client_lease_new(&lease, message); + if (r) + return r; + + /* message consumed, do not fail */ + + n_dhcp4_client_lease_link(lease, probe); + + node->event.extended.lease = n_dhcp4_client_lease_ref(lease); + n_dhcp4_client_lease_unref(probe->current_lease); + probe->current_lease = n_dhcp4_client_lease_ref(lease); + probe->state = N_DHCP4_CLIENT_PROBE_STATE_BOUND; + + break; + + case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + + r = n_dhcp4_client_probe_raise(probe, + &node, + N_DHCP4_CLIENT_EVENT_GRANTED); + if (r) + return r; + + r = n_dhcp4_client_lease_new(&lease, message); + if (r) + return r; + + /* message consumed, don to fail */ + + n_dhcp4_client_lease_link(lease, probe); + + node->event.granted.lease = n_dhcp4_client_lease_ref(lease); + probe->current_lease = n_dhcp4_client_lease_ref(lease); + probe->state = N_DHCP4_CLIENT_PROBE_STATE_GRANTED; + + break; + + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: + default: + /* ignore */ + break; + } + + return 0; +} + +static int n_dhcp4_client_probe_transition_nak(NDhcp4ClientProbe *probe) { + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + + /* XXX */ + + r = n_dhcp4_client_probe_raise(probe, + NULL, + N_DHCP4_CLIENT_EVENT_RETRACTED); + if (r) + return r; + + probe->state = N_DHCP4_CLIENT_PROBE_STATE_INIT; + + break; + + case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: + default: + /* ignore */ + break; + } + + return 0; +} + +int n_dhcp4_client_probe_transition_select(NDhcp4ClientProbe *probe, NDhcp4Incoming *offer, uint64_t ns_now) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL; + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: + r = n_dhcp4_c_connection_select_new(&probe->connection, &request, offer); + if (r) + return r; + + r = n_dhcp4_client_probe_outgoing_append_options(probe, request); + if (r) + return r; + + r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now); + if (r) + return r; + else + request = NULL; /* consumed */ + + /* XXX: ignore other offers */ + + probe->state = N_DHCP4_CLIENT_PROBE_STATE_REQUESTING; + + break; + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: + default: + /* ignore */ + break; + } + + return 0; +} + +/** + * n_dhcp4_client_probe_transition_accept() - XXX + */ +int n_dhcp4_client_probe_transition_accept(NDhcp4ClientProbe *probe, NDhcp4Incoming *ack) { + struct in_addr client = {}; + struct in_addr server = {}; + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + n_dhcp4_incoming_get_yiaddr(ack, &client); + + r = n_dhcp4_incoming_query_server_identifier(ack, &server); + if (r) + return r; + + r = n_dhcp4_c_connection_connect(&probe->connection, &client, &server); + if (r) + return r; + + probe->state = N_DHCP4_CLIENT_PROBE_STATE_BOUND; + + /* XXX: trigger timers */ + + break; + + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: + case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: + default: + /* ignore */ + break; + } + + return 0; +} + +/** + * n_dhc4_client_probe_transition_decline() - XXX + */ +int n_dhcp4_client_probe_transition_decline(NDhcp4ClientProbe *probe, NDhcp4Incoming *offer, const char *error, uint64_t ns_now) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL; + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + r = n_dhcp4_c_connection_decline_new(&probe->connection, &request, offer, error); + if (r) + return r; + + r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now); + if (r) + return r; + else + request = NULL; /* consumed */ + + /* XXX: what state to transition to? */ + + break; + + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: + case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: + default: + /* ignore */ + break; + } + + return 0; +} + +/** + * n_dhcp4_client_probe_dispatch_timer() - XXX + */ +int n_dhcp4_client_probe_dispatch_timer(NDhcp4ClientProbe *probe, uint64_t ns_now) { + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + if (ns_now >= probe->ns_deferred) { + r = n_dhcp4_client_probe_transition_deferred(probe, ns_now); + if (r) + return r; + } + + break; + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + if (ns_now >= probe->current_lease->lifetime) { + r = n_dhcp4_client_probe_transition_lifetime(probe); + if (r) + return r; + } + + break; + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + if (ns_now >= probe->current_lease->lifetime) { + r = n_dhcp4_client_probe_transition_lifetime(probe); + if (r) + return r; + } else if (ns_now >= probe->current_lease->t2) { + r = n_dhcp4_client_probe_transition_t2(probe, ns_now); + if (r) + return r; + } else if (ns_now >= probe->current_lease->t1) { + r = n_dhcp4_client_probe_transition_t1(probe, ns_now); + if (r) + return r; + } + + break; + default: + /* ignore */ + break; + } + + r = n_dhcp4_c_connection_dispatch_timer(&probe->connection, ns_now); + if (r) + return r; + + return 0; +} + +/** + * n_dhcp4_client_probe_dispatch_connection() - XXX + */ +int n_dhcp4_client_probe_dispatch_io(NDhcp4ClientProbe *probe, uint32_t events) { + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL; + uint8_t type; + int r; + + r = n_dhcp4_c_connection_dispatch_io(&probe->connection, &message); + if (r) { + if (r == N_DHCP4_E_AGAIN) + return 0; + else if (r == N_DHCP4_E_MALFORMED || r == N_DHCP4_E_UNEXPECTED) { + /* + * We fetched something from the sockets, which we + * discarded. We don't know whether there is more data + * to fetch, so we set the preempted flag to notify the + * caller we want to be called again. + */ + probe->client->preempted = true; + return 0; + } + + return r; + } + + /* + * We fetched something from the sockets, which we will handle below. + * We don't know whether there is more data to fetch, so we set the + * preempted flag to notify the caller we want to be called again. + */ + probe->client->preempted = true; + + r = n_dhcp4_incoming_query_message_type(message, &type); + if (r == N_DHCP4_E_UNSET || r == N_DHCP4_E_MALFORMED) + /* + * XXX: this can never happen as we already queried the message + * type. + */ + return 0; + + switch (type) { + case N_DHCP4_MESSAGE_OFFER: + r = n_dhcp4_client_probe_transition_offer(probe, message); + if (r) + return r; + else + message = NULL; /* consumed */ + break; + case N_DHCP4_MESSAGE_ACK: + r = n_dhcp4_client_probe_transition_ack(probe, message); + if (r) + return r; + else + message = NULL; /* consumed */ + break; + case N_DHCP4_MESSAGE_NAK: + r = n_dhcp4_client_probe_transition_nak(probe); + if (r) + return r; + break; + default: + /* + * We receiveda message type we do not support, simply discard + * it. + */ + break; + } + + return 0; +} + +/** + * n_dhcp4_client_probe_update_mtu() - XXX + */ +int n_dhcp4_client_probe_update_mtu(NDhcp4ClientProbe *probe, uint16_t mtu) { + return 0; +} diff --git a/shared/n-dhcp4/src/n-dhcp4-client.c b/shared/n-dhcp4/src/n-dhcp4-client.c new file mode 100644 index 00000000..5f7794fb --- /dev/null +++ b/shared/n-dhcp4/src/n-dhcp4-client.c @@ -0,0 +1,839 @@ +/* + * Client Side of the Dynamic Host Configuration Protocol for IPv4 + * + * This implements the public API around the NDhcp4Client object. The client + * object is simply a context to track running probes. It manages pending + * events of all probes, as well as forwards the dispatching requests whenever + * the dispatcher is run. + */ + +#include <assert.h> +#include <c-list.h> +#include <c-stdaux.h> +#include <errno.h> +#include <linux/if_ether.h> +#include <linux/if_infiniband.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/time.h> +#include <sys/timerfd.h> +#include <time.h> +#include <unistd.h> +#include "n-dhcp4.h" +#include "n-dhcp4-private.h" + +/** + * n_dhcp4_client_config_new() - allocate new client configuration + * @configp: output argument for new client config + * + * This creates a new client configuration object. Client configurations are + * unlinked objects that merely serve as collection of parameters. They do not + * perform validity checks. + * + * The new client configuration is fully owned by the caller. They are + * responsible to free the object if no longer needed. + * + * Return: 0 on success, negative error code on failure. + */ +_c_public_ int n_dhcp4_client_config_new(NDhcp4ClientConfig **configp) { + _c_cleanup_(n_dhcp4_client_config_freep) NDhcp4ClientConfig *config = NULL; + + config = calloc(1, sizeof(*config)); + if (!config) + return -ENOMEM; + + *config = (NDhcp4ClientConfig)N_DHCP4_CLIENT_CONFIG_NULL(*config); + + *configp = config; + config = NULL; + return 0; +} + +/** + * n_dhcp4_client_config_free() - destroy client configuration + * @config: client configuration to operate on, or NULL + * + * This destroys a client configuration and deallocates all its resources. If + * NULL is passed, this is a no-op. + * + * Return: NULL is returned. + */ +_c_public_ NDhcp4ClientConfig *n_dhcp4_client_config_free(NDhcp4ClientConfig *config) { + if (!config) + return NULL; + + free(config->client_id); + free(config); + + return NULL; +} + +/** + * n_dhcp4_client_config_dup() - duplicate client configuration + * @config: client configuration to operate on + * @dupp: output argument for duplicate + * + * This duplicates the client configuration given as @config and returns it in + * @dupp to the caller. + * + * Return: 0 on success, negative error code on failure. + */ +int n_dhcp4_client_config_dup(NDhcp4ClientConfig *config, NDhcp4ClientConfig **dupp) { + _c_cleanup_(n_dhcp4_client_config_freep) NDhcp4ClientConfig *dup = NULL; + int r; + + r = n_dhcp4_client_config_new(&dup); + if (r) + return r; + + dup->ifindex = config->ifindex; + dup->transport = config->transport; + dup->request_broadcast = config->request_broadcast; + memcpy(dup->mac, config->mac, sizeof(dup->mac)); + dup->n_mac = config->n_mac; + memcpy(dup->broadcast_mac, config->broadcast_mac, sizeof(dup->broadcast_mac)); + dup->n_broadcast_mac = config->n_broadcast_mac; + + r = n_dhcp4_client_config_set_client_id(dup, + config->client_id, + config->n_client_id); + if (r) + return r; + + *dupp = dup; + dup = NULL; + return 0; +} + +/** + * n_dhcp4_client_config_set_ifindex() - set ifindex property + * @config: client configuration to operate on + * @ifindex: ifindex to set + * + * This sets the ifindex property of the client configuration. The ifindex + * specifies the network device that a DHCP client will run on. + */ +_c_public_ void n_dhcp4_client_config_set_ifindex(NDhcp4ClientConfig *config, int ifindex) { + config->ifindex = ifindex; +} + +/** + * n_dhcp4_client_config_set_transport() - set transport property + * @config: client configuration to operate on + * @transport: transport to set + * + * This sets the transport property of the client configuration. The transport + * defines the hardware transport of the network device that a DHCP client + * runs on. + * + * This takes one of the N_DHCP4_TRANSPORT_* identifiers as argument. + */ +_c_public_ void n_dhcp4_client_config_set_transport(NDhcp4ClientConfig *config, unsigned int transport) { + config->transport = transport; +} + +/** + * n_dhcp4_client_config_set_request_broadcast() - set request-broadcast property + * @config: configuration to operate on + * @request_broadcast: value to set + * + * This sets the request_broadcast property of the given configuration object. + * + * The default is false. If set to true, a the server will be told to not unicast + * replies to the client's IP address before it has been configured, but broadcast + * to INADDR_ANY instead. In most cases, you do not want this. + * + * Background: OFFER and ACK messages from DHCP servers to clients are unicast + * to the IP address handed out, even before the IP address has + * been configured on the taregt interface. This usually works + * because the correct destination hardware address is explicitly + * set on the outgoing packets, rather than being resolved (which + * would not work). However, some hardware does not accept incoming + * IP packets destined for addresses they do not own, even if the + * hardware address is correct. In this case, the server must + * broadcast the replies in order for the client to receive them. + * In general, unneccesary broadcasting is something one wants to + * avoid, and some networks will not deliver broadcasts to the + * client at all, in which case this flag must not be set. + */ +_c_public_ void n_dhcp4_client_config_set_request_broadcast(NDhcp4ClientConfig *config, bool request_broadcast) { + config->request_broadcast = request_broadcast; +} + +/** + * n_dhcp4_client_config_set_mac() - set mac property + * @config: client configuration to operate on + * @mac: hardware address to set + * @n_mac: length of the hardware address + * + * This sets the mac property of the client configuration. It specifies the + * hardware address of the local interface that the DHCP client runs on. + * + * This function copies the specified hardware address into @config. Any + * hardware address is supported. It is up to the consumer of the client + * configuration to verify the validity of the hardware address. + * + * Note: This function may truncate the hardware address internally, but + * retains the original length. The consumer of this configuration can + * thus tell whether the data was truncated and will refuse it. + * The internal buffer is big enough to hold any hardware address of all + * supported transports. Thus, truncation only happens if you use + * unsupported transports, and those will be rejected, anyway. + */ +_c_public_ void n_dhcp4_client_config_set_mac(NDhcp4ClientConfig *config, const uint8_t *mac, size_t n_mac) { + config->n_mac = n_mac; + + if (n_mac > sizeof(config->mac)) + n_mac = sizeof(config->mac); + + memcpy(config->mac, mac, n_mac); +} + +/** + * n_dhcp4_client_config_set_broadcast_mac() - set broadcast-mac property + * @config: client configuration to operate on + * @mac: hardware address to set + * @n_mac: length of the hardware address + * + * This sets the broadcast-mac property of the client configuration. It + * specifies the destination hardware address to use for broadcasts on the + * local interface that the DHCP client runs on. + * + * This function copies the specified hardware address into @config. Any + * hardware address is supported. It is up to the consumer of the client + * configuration to verify the validity of the hardware address. + * + * Note: This function may truncate the hardware address internally, but + * retains the original length. The consumer of this configuration can + * thus tell whether the data was truncated and will refuse it. + * The internal buffer is big enough to hold any hardware address of all + * supported transports. Thus, truncation only happens if you use + * unsupported transports, and those will be rejected, anyway. + */ +_c_public_ void n_dhcp4_client_config_set_broadcast_mac(NDhcp4ClientConfig *config, const uint8_t *mac, size_t n_mac) { + config->n_broadcast_mac = n_mac; + + if (n_mac > sizeof(config->mac)) + n_mac = sizeof(config->mac); + + memcpy(config->broadcast_mac, mac, n_mac); +} + +/** + * n_dhcp4_client_config_set_client_id() - set client-id property + * @config: client configuration to operate on + * @id: client id + * @n_id: length of the client id in bytes + * + * This sets the client-id property of @config. It copies the entire client-id + * buffer into the configuration. + * + * Return: 0 on success, negative error code on failure. + */ +_c_public_ int n_dhcp4_client_config_set_client_id(NDhcp4ClientConfig *config, const uint8_t *id, size_t n_id) { + uint8_t *t; + + t = malloc(n_id + 1); + if (!t) + return -ENOMEM; + + free(config->client_id); + config->client_id = t; + config->n_client_id = n_id; + + memcpy(config->client_id, id, n_id); + config->client_id[n_id] = 0; /* safety 0 for debugging */ + + return 0; +} + +/** + * n_dhcp4_c_event_node_new() - allocate new event + * @nodep: output argument for new event + * + * This allocates a new event node and returns it to the caller. The caller + * fully owns the event-node and is reposonsible to either link it somewhere, + * or release it. + * + * Event nodes can be linked on a client object, as well as optionally on a + * probe object. As long as an event-node is linked, it will be retrievable by + * the API user through n_dhcp4_client_pop_event(). Furthermore, destruction of + * the client, or probe respectively, will clean-up all pending events. + * + * Return: 0 on success, negative error code on failure. + */ +int n_dhcp4_c_event_node_new(NDhcp4CEventNode **nodep) { + NDhcp4CEventNode *node; + + node = calloc(1, sizeof(*node)); + if (!node) + return -ENOMEM; + + *node = (NDhcp4CEventNode)N_DHCP4_C_EVENT_NODE_NULL(*node); + + *nodep = node; + return 0; +} + +/** + * n_dhcp4_c_event_node_free() - deallocate event + * @node: node to operate on, or NULL + * + * This deallocates the node given as @node. If the node is linked on a client + * or probe, it is unlinked automatically. + * + * If @probe is NULL, this is a no-op. + * + * Return: NULL is returned. + */ +NDhcp4CEventNode *n_dhcp4_c_event_node_free(NDhcp4CEventNode *node) { + if (!node) + return NULL; + + switch (node->event.event) { + case N_DHCP4_CLIENT_EVENT_OFFER: + node->event.offer.lease = n_dhcp4_client_lease_unref(node->event.offer.lease); + break; + case N_DHCP4_CLIENT_EVENT_GRANTED: + node->event.granted.lease = n_dhcp4_client_lease_unref(node->event.granted.lease); + break; + case N_DHCP4_CLIENT_EVENT_EXTENDED: + node->event.extended.lease = n_dhcp4_client_lease_unref(node->event.extended.lease); + break; + default: + break; + } + + c_list_unlink(&node->probe_link); + c_list_unlink(&node->client_link); + free(node); + + return NULL; +} + +/** + * n_dhcp4_client_new() - allocate new client + * @clientp: output argument for new client + * @config: configuration to use + * + * This allocates a new DHCP4 client object and returns it in @clientp to the + * caller. The caller then owns a single ref-count to the object and is + * responsible to drop it, when no longer needed. + * + * The configuration given as @config is used to initialize the client. The + * caller is free to destroy the configuration once this function returns. + * + * Return: 0 on success, negative error code on failure. + */ +_c_public_ int n_dhcp4_client_new(NDhcp4Client **clientp, NDhcp4ClientConfig *config) { + _c_cleanup_(n_dhcp4_client_unrefp) NDhcp4Client *client = NULL; + struct epoll_event ev = { + .events = EPOLLIN, + }; + int r; + + c_assert(clientp); + + /* verify configuration */ + { + if (config->ifindex < 1) + return N_DHCP4_E_INVALID_IFINDEX; + + switch (config->transport) { + case N_DHCP4_TRANSPORT_ETHERNET: + if (config->n_mac != ETH_ALEN || + config->n_broadcast_mac != ETH_ALEN) + return N_DHCP4_E_INVALID_ADDRESS; + + break; + case N_DHCP4_TRANSPORT_INFINIBAND: + if (config->n_mac != INFINIBAND_ALEN || + config->n_broadcast_mac != INFINIBAND_ALEN) + return N_DHCP4_E_INVALID_ADDRESS; + + break; + default: + return N_DHCP4_E_INVALID_TRANSPORT; + } + + if (config->n_client_id < 1) + return N_DHCP4_E_INVALID_CLIENT_ID; + } + + client = malloc(sizeof(*client)); + if (!client) + return -ENOMEM; + + *client = (NDhcp4Client)N_DHCP4_CLIENT_NULL(*client); + + r = n_dhcp4_client_config_dup(config, &client->config); + if (r) + return r; + + client->fd_epoll = epoll_create1(EPOLL_CLOEXEC); + if (client->fd_epoll < 0) + return -errno; + + client->fd_timer = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK); + if (client->fd_timer < 0) + return -errno; + + ev.data.u32 = N_DHCP4_CLIENT_EPOLL_TIMER; + r = epoll_ctl(client->fd_epoll, EPOLL_CTL_ADD, client->fd_timer, &ev); + if (r < 0) + return -errno; + + *clientp = client; + client = NULL; + return 0; +} + +static void n_dhcp4_client_free(NDhcp4Client *client) { + NDhcp4CEventNode *node, *t_node; + + c_assert(!client->current_probe); + + c_list_for_each_entry_safe(node, t_node, &client->event_list, client_link) + n_dhcp4_c_event_node_free(node); + + if (client->fd_timer >= 0) { + epoll_ctl(client->fd_epoll, EPOLL_CTL_DEL, client->fd_timer, NULL); + close(client->fd_timer); + } + + if (client->fd_epoll >= 0) + close(client->fd_epoll); + + n_dhcp4_client_config_free(client->config); + free(client); +} + +/** + * n_dhcp4_client_ref() - acquire client reference + * @client: client to operate on, or NULL + * + * This acquires a reference to the client given as @client. If @client is + * NULL, this function is a no-op. + * + * Return: @client is returned. + */ +_c_public_ NDhcp4Client *n_dhcp4_client_ref(NDhcp4Client *client) { + if (client) + ++client->n_refs; + return client; +} + +/** + * n_dhcp4_client_unref() - release client reference + * @client: client to operate on, or NULL + * + * This releases a reference to the client given as @client. If @client is + * NULL, this is a no-op. + * + * Once the last reference is dropped, the client object will get destroyed and + * deallocated. + * + * Return: NULL is returned. + */ +_c_public_ NDhcp4Client *n_dhcp4_client_unref(NDhcp4Client *client) { + if (client && !--client->n_refs) + n_dhcp4_client_free(client); + return NULL; +} + +/** + * n_dhcp4_client_raise() - raise event + * @client: client to operate on + * @nodep: output argument for new event, or NULL + * @event: event type to use + * + * This creates a new event-node on @client, setting the event-type to @event. + * The newly created event-node is returned to the caller in @nodep (unless + * @nodep is NULL). + * + * The event-node is automatically linked on @client. + * + * Return: 0 on success, negative error code on failure. + */ +int n_dhcp4_client_raise(NDhcp4Client *client, NDhcp4CEventNode **nodep, unsigned int event) { + NDhcp4CEventNode *node; + int r; + + r = n_dhcp4_c_event_node_new(&node); + if (r) + return r; + + node->event.event = event; + c_list_link_tail(&client->event_list, &node->client_link); + + if (nodep) + *nodep = node; + return 0; +} + +/** + * n_dhcp4_client_arm_timer() - update timer + * @client: client to operate on + * + * This updates the timer on @client to fire on the next pending timeout. This + * must be called whenever a timeout on @client might have changed. + */ +void n_dhcp4_client_arm_timer(NDhcp4Client *client) { + uint64_t timeout = 0; + int r; + + if (client->current_probe) + n_dhcp4_client_probe_get_timeout(client->current_probe, &timeout); + + if (timeout != client->scheduled_timeout) { + r = timerfd_settime(client->fd_timer, + TFD_TIMER_ABSTIME, + &(struct itimerspec){ + .it_value = { + .tv_sec = timeout / UINT64_C(1000000000), + .tv_nsec = timeout % UINT64_C(1000000000), + }, + }, + NULL); + c_assert(r >= 0); + + client->scheduled_timeout = timeout; + } +} + +/** + * n_dhcp4_client_get_fd() - retrieve event FD + * @client: client to operate on + * @fdp: output argument to store FD + * + * This retrieves the FD used by the client object given as @client. The FD is + * always valid, and returned in @fdp. + * + * The caller is expected to poll this FD for readable events and call + * n_dhcp4_client_dispatch() whenever the FD is readable. + */ +_c_public_ void n_dhcp4_client_get_fd(NDhcp4Client *client, int *fdp) { + *fdp = client->fd_epoll; +} + +static int n_dhcp4_client_dispatch_timer(NDhcp4Client *client, struct epoll_event *event) { + uint64_t v, ns_now; + 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 -ENOTRECOVERABLE; + } + + if (event->events & EPOLLIN) { + r = read(client->fd_timer, &v, sizeof(v)); + if (r < 0) { + if (errno == EAGAIN) { + /* + * There are no more pending events, so nothing + * to be done. Return to the caller. + */ + return 0; + } + + /* + * Something failed. We use CLOCK_BOOTTIME/MONOTONIC, + * so ECANCELED cannot happen. Hence, there is no error + * that we could gracefully handle. Fail hard and let + * the caller deal with it. + */ + return -errno; + } else if (r != sizeof(v) || v == 0) { + /* + * Kernel guarantees 8-byte reads, and only to return + * data if at least one timer triggered; fail hard if + * it suddenly starts exposing unexpected behavior. + */ + return -ENOTRECOVERABLE; + } + + /* + * Forward the timer-event to the active probe. Timers should + * not fire if there is no probe running, but lets ignore them + * for now, so probe-internals are not leaked to this generic + * client dispatcher. + */ + if (client->current_probe) { + /* + * Read the current time *after* dispatching the timer, + * to make sure we do not miss wakeups. + */ + ns_now = n_dhcp4_gettime(CLOCK_BOOTTIME); + + r = n_dhcp4_client_probe_dispatch_timer(client->current_probe, + ns_now); + if (r) + return r; + } + } + + return 0; +} + +static int n_dhcp4_client_dispatch_io(NDhcp4Client *client, struct epoll_event *event) { + int r; + + if (client->current_probe) + r = n_dhcp4_client_probe_dispatch_io(client->current_probe, + event->events); + else + return -ENOTRECOVERABLE; + + return r; +} + +/** + * n_dhcp4_client_dispatch() - dispatch client + * @client: client to operate on + * + * This dispatches pending operations on @client. It will read incoming + * messages, write pending data, and handle any timeouts. + * + * This function never blocks. + * + * If there are more events to dispatch, than would be reasonable to do in a + * single dispatch, this will return N_DHCP4_E_PREEMPTED. In this case the + * caller is expected to call into this function again when it is ready to + * dispatch more events. + * If your event loop is level-triggered (it very likely is), you can + * optionally ignore this return code and treat it as success. + * + * Return: 0 on success, negative error code on failure, N_DHCP4_E_PREEMPTED if + * there is more data to dispatch. + */ +_c_public_ int n_dhcp4_client_dispatch(NDhcp4Client *client) { + struct epoll_event events[2]; + int n, i, r = 0; + + n = epoll_wait(client->fd_epoll, events, sizeof(events) / sizeof(*events), 0); + if (n < 0) { + /* Linux never returns EINTR if `timeout == 0'. */ + return -errno; + } + + client->preempted = false; + + for (i = 0; i < n; ++i) { + switch (events[i].data.u32) { + case N_DHCP4_CLIENT_EPOLL_TIMER: + r = n_dhcp4_client_dispatch_timer(client, events + i); + break; + case N_DHCP4_CLIENT_EPOLL_IO: + r = n_dhcp4_client_dispatch_io(client, events + i); + break; + default: + c_assert(0); + r = 0; + break; + } + + if (r) { + if (r == N_DHCP4_E_DOWN) { + r = n_dhcp4_client_raise(client, + NULL, + N_DHCP4_CLIENT_EVENT_DOWN); + if (r) + return r; + + /* continue normally */ + } else if (r) { + c_assert(r < _N_DHCP4_E_INTERNAL); + return r; + } + } + } + + n_dhcp4_client_arm_timer(client); + + return client->preempted ? N_DHCP4_E_PREEMPTED : 0; +} + +/** + * n_dhcp4_client_pop_event() - fetch pending event + * @client: client to operate on + * @eventp: output argument to store next event + * + * This fetches the next pending event from the event-queue and returns it to a + * caller. A pointer to the event is stored in @eventp. If there is no more + * event queued, NULL is returned. + * + * If a valid event is returned, it is accessible until the next call to this + * function, or the destruction of the context object (this might be either the + * client object or the probe object, pointed to by the event), whichever + * happens first. + * That is, the caller should not pin the returned event object, but copy + * required information into their own state tracking contexts. + * + * The possible events are: + * * N_DHCP4_CLIENT_EVENT_OFFER: A lease offered from a server in response + * to a probe. Several such offers may be + * received until one of them is selected by + * the caller. Only one lease may be selected. + * The attached lease object may be queried + * for information in order to decide which + * lease to select, though the information is + * not guaranteed to stay the same in the + * final lease. + * * N_DHCP4_CLIENT_EVENT_GRANTED: A selected lease was granted by the server. + * The information in the attached lease + * object should be used to configure the + * client. Once the client has been + * configured, the lease should be accepted. + * * N_DHCP4_CLIENT_EVENT_RETRACTED: A selected lease offer was retracted by the + * server. This can happen in case the server + * offers the same lease to several clients, + * or the server discovers that the IP address + * in the lease is already in use. + * * N_DHCP4_CLIENT_EVENT_EXTENDED: An active lease is extended, if applicable + * the kernel should be updated with the new + * lifetime information for addresses and/or + * routes. + * * N_DHCP4_CLIENT_EVENT_EXPIRED: An active lease failed to be extended by + * the end of its lifetime. The client should + * immediately stop using the information + * contained in the lease. + * * N_DHCP4_CLIENT_EVENT_DOWN: The network interface was put down down. + * The user is recommended to reestablish the + * lease at the first opportunity when the + * network comes back up. Note that this is + * purely informational, the probe will keep + * running, and if the network topology does + * not change any lease we have will still be + * valid. + * * N_DHCP4_CLIENT_EVENT_CANCELLED: The probe was cancelled. This can happen if + * the client attempted several incompatible + * probes in parallel, then the most recent + * ones will be cancelled asynchronously. + * + * Return: 0 on success, negative error code on failure. + */ +_c_public_ int n_dhcp4_client_pop_event(NDhcp4Client *client, NDhcp4ClientEvent **eventp) { + NDhcp4CEventNode *node, *t_node; + + c_list_for_each_entry_safe(node, t_node, &client->event_list, client_link) { + if (node->is_public) { + n_dhcp4_c_event_node_free(node); + continue; + } + + node->is_public = true; + *eventp = &node->event; + return 0; + } + + *eventp = NULL; + return 0; +} + +/** + * n_dhcp4_client_update_mtu() - update link mtu + * @client: client to operate on + * @mtu: new mtu + * + * This updates the link MTU used by the client object. By default, the minimum + * requirement given by the IP specification is assumed, which means 576 + * bytes. The caller is advised to update this to the actual MTU used by the + * link layer. + * + * This value reflects the MTU of the link layer. That is, it is the maximum + * packet size that you can send on that link, excluding the link-header but + * including the IP-header. On ethernet-v2 this would be 1500. + * + * If unsure, it is safe to leave this unset. However, in this case a DHCP + * server will be required to omit information if it does not fit into the + * default MTU. + * + * Unless you keep the default MTU, you should update the MTU whenever the link + * MTU changes. That is, when it is increased *and* when it is decreased. + * However, you must be aware that decreasing the MTU on a link might cause + * temporary data loss. + * + * Background: Knowing the link MTU guarantees that we can possibly transmit + * packets bigger than the IP minimum (i.e., 576 bytes). However, + * it does not guarantee that a possible target supports parsing + * packets bigger than the IP minimum. Hence, the MTU is used by a + * client to send a hint to a server that it can receive replies + * bigger than the minimum. As such, a server can reply with more + * information than otherwise possible. + * Since this DHCP client does not support fragmented packets, we + * simply set the allowed packet-size to the local link MTU. + * Note that DHCP relays might cause DHCP packets to be routed. + * However, such relays are required to always reassemble any + * fragments they receive into full DHCP packets, before they + * forward them either way. This guarantees that incoming packets + * are never fragmented, unless they exceed the local link MTU + * (this would otherwise not neccessarily be true, if some other + * part of the routed network had a lower MTU). + * + * Return: 0 on success, negative error code on failure. + */ +_c_public_ int n_dhcp4_client_update_mtu(NDhcp4Client *client, uint16_t mtu) { + int r; + + if (mtu == client->mtu) + return 0; + + if (client->current_probe) { + r = n_dhcp4_client_probe_update_mtu(client->current_probe, mtu); + if (r) + return r; + } + + client->mtu = mtu; + return 0; +} + +/** + * n_dhcp4_client_probe() - create a new probe + * @client: client to operate on + * @probep: output argument to store new probe + * @config: probe configuration to use + * + * This creates a new probe on @client. Probes represent DHCP requests and + * track the state over the entire lifetime of a lease. Once a probe is created + * it will start looking for DHCP servers, request a lease from them, and renew + * the lease continously whenever it expires. Furthermore, if a lease cannot be + * renewed, a new lease will be requested. + * + * The API allows for many probes to be run at the same time. However, the DHCP + * specification forbids many of those cases (e.g., you must not reuse a client + * id, otherwise it will be impossible to track who to forward received packets + * to). Hence, so far only a single probe can run at a time. If you create a + * new probe, all older probes that conflict with that probe will be canceled + * (their state machine is halted and a N_DHCP4_CLIENT_EVENT_CANCELLED event is + * raised. + * This might change in the future, though. There might be cases where multiple + * probes can be run in parallel (e.g., with different client-ids, or an INFORM + * in parallel to a REQUEST, ...). + * + * Return: 0 on success, negative error code on failure. + */ +_c_public_ int n_dhcp4_client_probe(NDhcp4Client *client, + NDhcp4ClientProbe **probep, + NDhcp4ClientProbeConfig *config) { + _c_cleanup_(n_dhcp4_client_probe_freep) NDhcp4ClientProbe *probe = NULL; + uint64_t ns_now; + int r; + + ns_now = n_dhcp4_gettime(CLOCK_BOOTTIME); + + r = n_dhcp4_client_probe_new(&probe, config, client, ns_now); + if (r) + return r; + + n_dhcp4_client_arm_timer(client); + + *probep = probe; + probe = NULL; + return 0; +} diff --git a/shared/n-dhcp4/src/n-dhcp4-incoming.c b/shared/n-dhcp4/src/n-dhcp4-incoming.c new file mode 100644 index 00000000..255da458 --- /dev/null +++ b/shared/n-dhcp4/src/n-dhcp4-incoming.c @@ -0,0 +1,431 @@ +/* + * DHCPv4 Incoming Messages + * + * This file implements the message parser object for incoming DHCP4 messages. + * It takes a linear data blob as input, and provides accessors for the message + * content. + * + * This wrapper mainly deals with the OPTIONs array. That is, in hides the + * different overload-sections the DHCP4 spec defines, it concatenates + * duplicate option fields (as described by the spec), and provides a + * consistent view to the caller. + * + * Internally, for every incoming message we linearize its OPTIONs. This means, + * we create a copy of the contents, and merge all duplicate options into a + * single option entry. We then provide accessors to the caller to easily get + * O(1) access to individual fields. + */ + +#include <assert.h> +#include <c-stdaux.h> +#include <endian.h> +#include <errno.h> +#include <inttypes.h> +#include <netinet/ip.h> +#include <netinet/udp.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include "n-dhcp4.h" +#include "n-dhcp4-private.h" + +static void n_dhcp4_incoming_prefetch(NDhcp4Incoming *incoming, size_t *offset, uint8_t option, const uint8_t *raw, size_t n_raw) { + uint8_t o, l; + size_t pos; + + for (pos = 0; pos < n_raw; ) { + o = raw[pos++]; + if (o == N_DHCP4_OPTION_PAD) + continue; + if (o == N_DHCP4_OPTION_END) + return; + + /* bail out if no remaining space for length field */ + if (pos >= n_raw) + return; + + /* bail out if length exceeds the available space */ + l = raw[pos++]; + if (l > n_raw || pos > n_raw - l) + return; + + /* prefetch content if it matches @option */ + if (o == option) { + memcpy((uint8_t *)&incoming->message + *offset, raw + pos, l); + *offset += l; + } + + pos += l; + } +} + +static void n_dhcp4_incoming_merge(NDhcp4Incoming *incoming, size_t *offset, uint8_t overload, uint8_t option) { + uint8_t *m = (uint8_t *)&incoming->message; + size_t pos; + + /* + * Prefetch all options matching @option from the 3 sections, + * concatenating their content. Remember the offset and size of the + * option in our message state. + */ + + pos = *offset; + + /* prefetch option from OPTIONS */ + n_dhcp4_incoming_prefetch(incoming, offset, option, + m + offsetof(NDhcp4Message, options), + incoming->n_message - offsetof(NDhcp4Message, options)); + + /* prefetch option from FILE */ + if (overload & N_DHCP4_OVERLOAD_FILE) + n_dhcp4_incoming_prefetch(incoming, offset, option, + m + offsetof(NDhcp4Message, file), + sizeof(incoming->message.file)); + + /* prefetch option from SNAME */ + if (overload & N_DHCP4_OVERLOAD_SNAME) + n_dhcp4_incoming_prefetch(incoming, offset, option, + m + offsetof(NDhcp4Message, sname), + sizeof(incoming->message.sname)); + + incoming->options[option].value = m + pos; + incoming->options[option].size = *offset - pos; +} + +static void n_dhcp4_incoming_linearize(NDhcp4Incoming *incoming) { + uint8_t *m, o, l, overload; + size_t i, pos, end, offset; + + /* + * Linearize all OPTIONs of the incoming message. We know that + * @incoming->message is preallocated to be big enough to hold the + * entire linearized message _trailing_ the original copy. All we have + * to do is walk the raw message in @incoming->message and for each + * option we find, copy it into the trailing space, concatenating all + * instances we find. + * + * Before we can copy the individual options, we must scan for the + * OVERLOAD option. This is required so our prefetcher knows which data + * arrays to scan for prefetching. + * + * So far, we require the OVERLOAD option to be present in the + * options-array (which is obvious and a given). However, if the option + * occurs multiple times outside of the options-array (i.e., SNAME or + * FILE), we silently ignore them. The specification does not allow + * multiple OVERLOAD options, anyway. Hence, this behavior only defines + * what we do when we see broken implementations, and we currently seem + * to support all styles we saw in the wild so far. + */ + + m = (uint8_t *)&incoming->message; + offset = incoming->n_message; + + n_dhcp4_incoming_merge(incoming, &offset, 0, N_DHCP4_OPTION_OVERLOAD); + if (incoming->options[N_DHCP4_OPTION_OVERLOAD].size >= 1) + overload = *incoming->options[N_DHCP4_OPTION_OVERLOAD].value; + else + overload = 0; + + for (i = 0; i < 3; ++i) { + if (i == 0) { /* walk OPTIONS */ + pos = offsetof(NDhcp4Message, options); + end = incoming->n_message; + } else if (i == 1) { /* walk FILE */ + if (!(overload & N_DHCP4_OVERLOAD_FILE)) + continue; + + pos = offsetof(NDhcp4Message, file); + end = pos + sizeof(incoming->message.file); + } else { /* walk SNAME */ + if (!(overload & N_DHCP4_OVERLOAD_SNAME)) + continue; + + pos = offsetof(NDhcp4Message, sname); + end = pos + sizeof(incoming->message.sname); + } + + while (pos < end) { + o = m[pos++]; + if (o == N_DHCP4_OPTION_PAD) + continue; + if (o == N_DHCP4_OPTION_END) + break; + if (pos >= end) + break; + + l = m[pos++]; + if (l > end || pos > end - l) + break; + + if (!incoming->options[o].value) + n_dhcp4_incoming_merge(incoming, &offset, overload, o); + + pos += l; + } + } +} + +/** + * n_dhcp4_incoming_new() - Allocate new incoming message object + * @incomingp: output argument for new object + * @raw: raw message blob + * @n_raw: length of the raw message blob + * + * This function allocates a new incoming-message object to wrap a received + * message blob. It performs basic verification of the message length and + * header, and then linearizes the DHCP4 options. + * + * The incoming-message object mainly provides accessors for the option-array. + * It handles all the different quirks around parsing and concatenating the + * options array, and provides the assembled data to the caller. It does not, + * however, in any way interpret the data of the individual options. This is up + * to the caller to do. + * + * Return: 0 on success, negative error code on failure, N_DHCP4_E_MALFORMED if + * the message is not a valid DHCP4 message. + */ +int n_dhcp4_incoming_new(NDhcp4Incoming **incomingp, const void *raw, size_t n_raw) { + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *incoming = NULL; + size_t size; + + if (n_raw < sizeof(NDhcp4Message) || n_raw > UINT16_MAX) + return N_DHCP4_E_MALFORMED; + + /* + * Allocate enough space for book-keeping, a copy of @raw and trailing + * space for linearized options. The trailing space must be big enough + * to hold the entire options array unmodified (linearizing can only + * make it smaller). Hence, just allocate enough space to hold the raw + * message without the header. + */ + size = sizeof(*incoming) + n_raw - sizeof(NDhcp4Message); + size += n_raw - sizeof(NDhcp4Header); + + incoming = calloc(1, size); + if (!incoming) + return -ENOMEM; + + *incoming = (NDhcp4Incoming)N_DHCP4_INCOMING_NULL(*incoming); + incoming->n_message = n_raw; + memcpy(&incoming->message, raw, n_raw); + + if (incoming->message.magic != htobe32(N_DHCP4_MESSAGE_MAGIC)) + return N_DHCP4_E_MALFORMED; + + /* linearize options */ + n_dhcp4_incoming_linearize(incoming); + + *incomingp = incoming; + incoming = NULL; + return 0; +} + +/** + * n_dhcp4_incoming_free() - Deallocate message object + * @incoming: object to operate on, or NULL + * + * This deallocates and frees the given incoming-message object. If NULL is + * passed, this is a no-op. + * + * Return: NULL is returned. + */ +NDhcp4Incoming *n_dhcp4_incoming_free(NDhcp4Incoming *incoming) { + if (!incoming) + return NULL; + + free(incoming); + + return NULL; +} + +/** + * n_dhcp4_incoming_get_header() - Return message header + * @incoming: message to operate on + * + * This returns a pointer to the message header. Note that modifications to + * this header are permanent and will affect the original message. + * + * Return: A pointer to the message header is returned. + */ +NDhcp4Header *n_dhcp4_incoming_get_header(NDhcp4Incoming *incoming) { + return &incoming->message.header; +} + +/** + * n_dhcp4_incoming_get_raw() - Get access to the raw original message + * @incoming: message to operate on + * @rawp: output argument for the raw blob, or NULL + * + * This hands out a pointer to the raw message blob to the caller. This will + * point to the original message content, rather than the linearized version. + * + * Note that if the caller queried the contents of the message before, any + * modifications done by the caller will not affect the original message. It + * only affects the linearized content (which is a duplicate trailing the + * original message). However, modifications to the message header *DO* also + * appear in the original, since the message header is not duplicated. + * + * In either case, it is better to never modify the message, if you intend to + * forward it further. + * + * Return: The length of the raw message blob is returned. + */ +size_t n_dhcp4_incoming_get_raw(NDhcp4Incoming *incoming, const void **rawp) { + if (rawp) + *rawp = &incoming->message; + return incoming->n_message; +} + +/** + * n_dhcp4_incoming_query() - Query the contents of a specific option + * @incoming: message to query + * @option: option to look for + * @datap: output argument for the option-data, or NULL + * @n_datap: output argument for the length of the option, or NULL + * + * This returns a pointer to the requested option blob in the message. It + * points to a linearized version of all respective option-fields of the same + * type. Hence, the caller is not required to deal with multiple occurrences of + * the same option. + * + * If an option was not present in the incoming message, N_DHCP4_E_UNSET is + * returned. Note that this is different from an empty option! And empty option + * will return a valid pointer and size 0. + * + * Note that the pointer to the option-blob does not point to the original + * message, but a duplicated version. Modifications to the blob will not be + * reflected in the original message, but they will be permanent regarding + * further queries through this function. + * + * Note that the original message alignment might no longer be reflected in the + * returned blob. You must not alias the content of the blob, but always copy + * it out, or consume piecemeal. + * + * This function runs in O(1). + * + * Return: 0 on success, negative error code on failure, N_DHCP4_E_UNSET if the + * option was not found, + */ +int n_dhcp4_incoming_query(NDhcp4Incoming *incoming, uint8_t option, uint8_t **datap, size_t *n_datap) { + if (!incoming->options[option].value) + return N_DHCP4_E_UNSET; + + if (datap) + *datap = incoming->options[option].value; + if (n_datap) + *n_datap = incoming->options[option].size; + return 0; +} + +static int n_dhcp4_incoming_query_u8(NDhcp4Incoming *message, uint8_t option, uint8_t *u8p) { + uint8_t *data; + size_t n_data; + int r; + + r = n_dhcp4_incoming_query(message, option, &data, &n_data); + if (r) + return r; + else if (n_data != sizeof(*data)) + return N_DHCP4_E_MALFORMED; + + *u8p = *data; + return 0; +} + +static int n_dhcp4_incoming_query_u16(NDhcp4Incoming *message, uint8_t option, uint16_t *u16p) { + uint8_t *data; + size_t n_data; + uint16_t be16; + int r; + + r = n_dhcp4_incoming_query(message, option, &data, &n_data); + if (r) + return r; + else if (n_data != sizeof(be16)) + return N_DHCP4_E_MALFORMED; + + memcpy(&be16, data, sizeof(be16)); + + *u16p = ntohs(be16); + return 0; +} + +static int n_dhcp4_incoming_query_u32(NDhcp4Incoming *message, uint8_t option, uint32_t *u32p) { + uint8_t *data; + size_t n_data; + uint32_t be32; + int r; + + r = n_dhcp4_incoming_query(message, option, &data, &n_data); + if (r) + return r; + else if (n_data != sizeof(be32)) + return N_DHCP4_E_MALFORMED; + + memcpy(&be32, data, sizeof(be32)); + + if (be32 == (uint32_t)-1) + *u32p = 0; + else + *u32p = ntohl(be32); + return 0; +} + +static int n_dhcp4_incoming_query_in_addr(NDhcp4Incoming *message, uint8_t option, struct in_addr *addrp) { + uint8_t *data; + size_t n_data; + uint32_t be32; + int r; + + r = n_dhcp4_incoming_query(message, option, &data, &n_data); + if (r) + return r; + else if (n_data != sizeof(be32)) + return N_DHCP4_E_MALFORMED; + + memcpy(&be32, data, sizeof(be32)); + + addrp->s_addr = be32; + return 0; +} + +int n_dhcp4_incoming_query_message_type(NDhcp4Incoming *message, uint8_t *typep) { + return n_dhcp4_incoming_query_u8(message, N_DHCP4_OPTION_MESSAGE_TYPE, typep); +} + +int n_dhcp4_incoming_query_lifetime(NDhcp4Incoming *message, uint32_t *lifetimep) { + return n_dhcp4_incoming_query_u32(message, N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME, lifetimep); +} + +int n_dhcp4_incoming_query_t2(NDhcp4Incoming *message, uint32_t *t2p) { + return n_dhcp4_incoming_query_u32(message, N_DHCP4_OPTION_REBINDING_T2_TIME, t2p); +} + +int n_dhcp4_incoming_query_t1(NDhcp4Incoming *message, uint32_t *t1p) { + return n_dhcp4_incoming_query_u32(message, N_DHCP4_OPTION_RENEWAL_T1_TIME, t1p); +} + +int n_dhcp4_incoming_query_server_identifier(NDhcp4Incoming *message, struct in_addr *idp) { + return n_dhcp4_incoming_query_in_addr(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, idp); +} + +int n_dhcp4_incoming_query_max_message_size(NDhcp4Incoming *message, uint16_t *max_message_sizep) { + return n_dhcp4_incoming_query_u16(message, N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE, max_message_sizep); +} + +int n_dhcp4_incoming_query_requested_ip(NDhcp4Incoming *message, struct in_addr *requested_ipp) { + return n_dhcp4_incoming_query_in_addr(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, requested_ipp); +} + +void n_dhcp4_incoming_get_xid(NDhcp4Incoming *message, uint32_t *xidp) { + NDhcp4Header *header = n_dhcp4_incoming_get_header(message); + + *xidp = header->xid; +} + +void n_dhcp4_incoming_get_yiaddr(NDhcp4Incoming *message, struct in_addr *yiaddr) { + NDhcp4Header *header = n_dhcp4_incoming_get_header(message); + + yiaddr->s_addr = header->yiaddr; +} diff --git a/shared/n-dhcp4/src/n-dhcp4-outgoing.c b/shared/n-dhcp4/src/n-dhcp4-outgoing.c new file mode 100644 index 00000000..c44b5880 --- /dev/null +++ b/shared/n-dhcp4/src/n-dhcp4-outgoing.c @@ -0,0 +1,372 @@ +/* + * DHCPv4 Outgoing Messages + * + * XXX + */ + +#include <assert.h> +#include <c-stdaux.h> +#include <endian.h> +#include <errno.h> +#include <inttypes.h> +#include <netinet/ip.h> +#include <netinet/udp.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include "n-dhcp4.h" +#include "n-dhcp4-private.h" + +/** + * N_DHCP4_OUTGOING_MAX_PHDR - maximum protocol header size + * + * All DHCP4 messages-limits specify the size of the entire packet including + * the protocol layer (i.e., including the IP headers and UDP headers). To + * calculate the size we have remaining for the actual DHCP message, we need to + * substract the maximum possible header-length the linux-kernel might prepend + * to our messages. This turns out to be the maximum IP-header size (including + * optional IP headers, hence 60 bytes) plus the UDP header size (i.e., 8 + * bytes). + */ +#define N_DHCP4_OUTGOING_MAX_PHDR (N_DHCP4_NETWORK_IP_MAXIMUM_HEADER_SIZE + sizeof(struct udphdr)) + +/** + * n_dhcp4_outgoing_new() - Allocate new outgoing message + * @outgoingp: output argument to return allocate object through + * @max_size: maximum transmission size to use + * @overload: select sections to overload + * + * This allocates a new outgoing message and returns it to the caller. The + * caller can then append data to it and send it over the wire. + * + * The @max_size parameter specifies the transport-layer MTU to consider. If 0, + * N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE is used. Note that this argument + * specifies the maximum packet size *INCLUDING* the IP-headers and UDP-header. + * Internally, the allocator makes sure to never create packets bigger than the + * specified MTU. The append functions will return an error, if the packet size + * would exceed the MTU. + * If you use a full UDP stack that supports packet fragmentation, you can + * specify the maximum packet size here (e.g., UINT16_MAX). + * + * Return: 0 on success, error code on failure. + */ +int n_dhcp4_outgoing_new(NDhcp4Outgoing **outgoingp, size_t max_size, uint8_t overload) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *outgoing = NULL; + + c_assert(!(overload & ~(N_DHCP4_OVERLOAD_FILE | N_DHCP4_OVERLOAD_SNAME))); + + /* + * Make sure the minimum limit is bigger than the maximum protocol + * header plus the DHCP-message-header plus a single OPTION_END byte. + */ + static_assert(N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE >= N_DHCP4_OUTGOING_MAX_PHDR + + sizeof(NDhcp4Message) + 1, + "Invalid minimum IP packet limit"); + + outgoing = calloc(1, sizeof(*outgoing)); + if (!outgoing) + return -ENOMEM; + + *outgoing = (NDhcp4Outgoing)N_DHCP4_OUTGOING_NULL(*outgoing); + outgoing->n_message = N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE - N_DHCP4_OUTGOING_MAX_PHDR; + outgoing->i_message = offsetof(NDhcp4Message, options); + outgoing->max_size = outgoing->n_message; + outgoing->overload = overload; + + if (max_size > N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE) + outgoing->max_size = max_size - N_DHCP4_OUTGOING_MAX_PHDR; + + outgoing->message = calloc(1, outgoing->n_message); + if (!outgoing->message) + return -ENOMEM; + + outgoing->message->magic = htonl(N_DHCP4_MESSAGE_MAGIC); + outgoing->message->options[0] = N_DHCP4_OPTION_END; + + *outgoingp = outgoing; + outgoing = NULL; + return 0; +} + +/** + * n_dhcp4_outgoing_free() - Deallocate outgoing message + * @outgoing: message to deallocate, or NULL + * + * This is the opposite to n_dhcp4_outgoing_new(). It deallocates and frees the + * passed object. If @outgoing is NULL, this is a no-op. + * + * Return: NULL is returned. + */ +NDhcp4Outgoing *n_dhcp4_outgoing_free(NDhcp4Outgoing *outgoing) { + if (!outgoing) + return NULL; + + free(outgoing->message); + free(outgoing); + + return NULL; +} + +/** + * n_dhcp4_outgoing_get_header() - Get pointer to the message header + * @outgoing: message to operate on + * + * This returns a pointer to the DHCP4 message header to the caller. The caller + * can use this to fill-in the header-fields. Note that all fields are + * initialized to their default values. Hence, you only need to override the + * fields where the default is not sufficient. + * + * Return: A pointer to the message header is returned. + */ +NDhcp4Header *n_dhcp4_outgoing_get_header(NDhcp4Outgoing *outgoing) { + return &outgoing->message->header; +} + +/** + * n_dhcp4_outgoing_get_raw() - Get the raw message blob + * @outgoing: message to operat on + * @rawp: output argument for the message-blob + * + * This function gives the caller access to the raw message-blob. That is, once + * message-marshaling is complete, use this to get the raw blob for sending. + * Note that this blob is only valid as long as you no longer append any + * further options to the message, nor modify it in any other way. + * + * Return: The size of the raw message blob is returned. + */ +size_t n_dhcp4_outgoing_get_raw(NDhcp4Outgoing *outgoing, const void **rawp) { + if (rawp) + *rawp = outgoing->message; + + /* + * Return the DHCP message until the END option, excluding any + * trailing padding. We overallocate during append, so the + * allocated message might be bigger than what we want to + * send on the wire. + */ + return outgoing->i_message + 1; +} + +static void n_dhcp4_outgoing_append_option(NDhcp4Outgoing *outgoing, + uint8_t option, + const void *data, + uint8_t n_data) { + uint8_t *blob = (void *)outgoing->message; + + blob[outgoing->i_message++] = option; + blob[outgoing->i_message++] = n_data; + memcpy(blob + outgoing->i_message, data, n_data); + outgoing->i_message += n_data; + blob[outgoing->i_message] = N_DHCP4_OPTION_END; +} + +/** + * n_dhcp4_outgoing_append() - Append option to outgoing message + * @outgoing: message to operate on + * @option: option code to append + * @data: data to append in the option + * @n_data: length of the data blob + * + * This appends another option to the given outgoing message. The data is taken + * verbatim and copied into the message. Note that no validation is done. If + * you provide an option multiple times, it will be added multiple times (spec + * then requires them to be interpreted as concatenated option, in case the + * option is marked as such). + * + * The size of a message is limited, based on the restriction passed to the + * outgoing-message constructor. If there is not enough free space to copy in + * the new option, N_DHCP4_E_NO_SPACE is returned. + * + * The order in which you append options might matter to some implementations. + * For example, the message-type is often expected to be the first option. We + * do not place such restrictions, but for compatibility with external + * implementations, you should follow these recommendations. + * Furthermore, we do not implement any kind of smart allocators. That is, all + * options are simply appended when you call this. But due to the overloading + * feature, fragmentation might matter. Hence, if you use overloading, overly + * big options might cause padding, and as such waste space. + * + * Return: 0 on success, negative error code on failure, N_DHCP4_E_NO_SPACE + * when there is not sufficient free space in the message. + */ +int n_dhcp4_outgoing_append(NDhcp4Outgoing *outgoing, + uint8_t option, + const void *data, + uint8_t n_data) { + NDhcp4Message *m; + uint8_t overload; + size_t rem, n; + + c_assert(option != N_DHCP4_OPTION_PAD); + c_assert(option != N_DHCP4_OPTION_END); + c_assert(option != N_DHCP4_OPTION_OVERLOAD); + + /* + * If the iterator is on the OPTIONs field, try appending the new blob. + * We need 2 header-bytes plus @n_data bytes. Additionally, we always + * reserve 3 trailing bytes for a possible OVERLOAD option, and 1 byte + * for the END marker. + */ + if (outgoing->i_message >= offsetof(NDhcp4Message, options)) { + rem = outgoing->n_message - outgoing->i_message; + + /* try fitting into remaining OPTIONs space */ + if (rem >= n_data + 2U + 3U + 1U) { + n_dhcp4_outgoing_append_option(outgoing, option, data, n_data); + return 0; + } + + /* try fitting into allowed OPTIONs space */ + if (outgoing->max_size - outgoing->i_message >= n_data + 2U + 3U + 1U) { + /* try over-allocation to reduce allocation pressure */ + n = outgoing->n_message + n_data + 128; + if (n > outgoing->max_size) + n = outgoing->max_size; + m = realloc(outgoing->message, n); + if (!m) + return -ENOMEM; + + memset((void *)m + outgoing->i_message, 0, n - outgoing->i_message); + outgoing->message = m; + outgoing->n_message = n; + n_dhcp4_outgoing_append_option(outgoing, option, data, n_data); + return 0; + } + + /* not enough remaining space, try OVERLOAD */ + if (!outgoing->overload) + return N_DHCP4_E_NO_SPACE; + + /* + * We ran out of space in the OPTIONs array, but overloading + * was enabled. This means, we can insert an OVERLOAD option + * and then use SNAME/FILE to store more options. + * Note that the three different sections cannot overlap and + * all must have an END marker. So as soon as we add the + * OVERLOAD option, we must make sure the other sections have + * the valid END marker. From then on, our *_append_option() + * helper makes sure to move the END marker with every + * insertion. + */ + overload = outgoing->overload; + n_dhcp4_outgoing_append_option(outgoing, N_DHCP4_OPTION_OVERLOAD, &overload, 1); + + if (overload & N_DHCP4_OVERLOAD_FILE) + outgoing->message->file[0] = N_DHCP4_OPTION_END; + if (overload & N_DHCP4_OVERLOAD_SNAME) + outgoing->message->sname[0] = N_DHCP4_OPTION_END; + + if (overload & N_DHCP4_OVERLOAD_FILE) + outgoing->i_message = offsetof(NDhcp4Message, file); + else if (overload & N_DHCP4_OVERLOAD_SNAME) + outgoing->i_message = offsetof(NDhcp4Message, sname); + } + + /* + * The OPTIONs section is full and OVERLOAD was enabled. Try writing + * into the FILE section. Always reserve 1 byte for the trailing END + * marker. + */ + if (outgoing->i_message >= offsetof(NDhcp4Message, file)) { + rem = sizeof(outgoing->message->file); + rem -= outgoing->i_message - offsetof(NDhcp4Message, file); + + if (rem >= n_data + 2U + 1U) { + n_dhcp4_outgoing_append_option(outgoing, option, data, n_data); + return 0; + } + + if (overload & N_DHCP4_OVERLOAD_SNAME) + outgoing->i_message = offsetof(NDhcp4Message, sname); + else + return N_DHCP4_E_NO_SPACE; + } + + /* + * OPTIONs and FILE are full, try putting data into the SNAME section + * as a last resort. + */ + if (outgoing->i_message >= offsetof(NDhcp4Message, sname)) { + rem = sizeof(outgoing->message->sname); + rem -= outgoing->i_message - offsetof(NDhcp4Message, sname); + + if (rem >= n_data + 2U + 1U) { + n_dhcp4_outgoing_append_option(outgoing, option, data, n_data); + return 0; + } + } + + return N_DHCP4_E_NO_SPACE; +} + +static int n_dhcp4_outgoing_append_u32(NDhcp4Outgoing *message, uint8_t option, uint32_t u32) { + uint32_t be32 = htonl(u32); + int r; + + r = n_dhcp4_outgoing_append(message, option, &be32, sizeof(be32)); + if (r) + return r; + + return 0; +} + +static int n_dhcp4_outgoing_append_in_addr(NDhcp4Outgoing *message, uint8_t option, struct in_addr addr) { + int r; + + r = n_dhcp4_outgoing_append(message, option, &addr.s_addr, sizeof(addr.s_addr)); + if (r) + return r; + + return 0; +} + +int n_dhcp4_outgoing_append_t1(NDhcp4Outgoing *message, uint32_t t1) { + return n_dhcp4_outgoing_append_u32(message, N_DHCP4_OPTION_RENEWAL_T1_TIME, t1); +} + +int n_dhcp4_outgoing_append_t2(NDhcp4Outgoing *message, uint32_t t2) { + return n_dhcp4_outgoing_append_u32(message, N_DHCP4_OPTION_REBINDING_T2_TIME, t2); +} + +int n_dhcp4_outgoing_append_lifetime(NDhcp4Outgoing *message, uint32_t lifetime) { + return n_dhcp4_outgoing_append_u32(message, N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME, lifetime); +} + +int n_dhcp4_outgoing_append_server_identifier(NDhcp4Outgoing *message, struct in_addr addr) { + return n_dhcp4_outgoing_append_in_addr(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, addr); +} + +int n_dhcp4_outgoing_append_requested_ip(NDhcp4Outgoing *message, struct in_addr addr) { + return n_dhcp4_outgoing_append_in_addr(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, addr); +} + +void n_dhcp4_outgoing_set_secs(NDhcp4Outgoing *message, uint32_t secs) { + NDhcp4Header *header = n_dhcp4_outgoing_get_header(message); + + /* + * Some DHCP servers will reject DISCOVER or REQUEST messages if 'secs' + * is not set (i.e., set to 0), even though the spec allows it. + */ + c_assert(secs); + + header->secs = htonl(secs); +} + +void n_dhcp4_outgoing_set_xid(NDhcp4Outgoing *message, uint32_t xid) { + NDhcp4Header *header = n_dhcp4_outgoing_get_header(message); + + header->xid = xid; +} + +void n_dhcp4_outgoing_get_xid(NDhcp4Outgoing *message, uint32_t *xidp) { + NDhcp4Header *header = n_dhcp4_outgoing_get_header(message); + + *xidp = header->xid; +} + +void n_dhcp4_outgoing_set_yiaddr(NDhcp4Outgoing *message, struct in_addr yiaddr) { + NDhcp4Header *header = n_dhcp4_outgoing_get_header(message); + + header->yiaddr = yiaddr.s_addr; +} diff --git a/shared/n-dhcp4/src/n-dhcp4-private.h b/shared/n-dhcp4/src/n-dhcp4-private.h new file mode 100644 index 00000000..c38ddbfc --- /dev/null +++ b/shared/n-dhcp4/src/n-dhcp4-private.h @@ -0,0 +1,688 @@ +#pragma once + +#include <arpa/inet.h> +#include <assert.h> +#include <c-list.h> +#include <c-stdaux.h> +#include <endian.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include "n-dhcp4.h" + +typedef struct NDhcp4CConnection NDhcp4CConnection; +typedef struct NDhcp4CEventNode NDhcp4CEventNode; +typedef struct NDhcp4ClientProbeOption NDhcp4ClientProbeOption; +typedef struct NDhcp4Header NDhcp4Header; +typedef struct NDhcp4Incoming NDhcp4Incoming; +typedef struct NDhcp4Message NDhcp4Message; +typedef struct NDhcp4Outgoing NDhcp4Outgoing; +typedef struct NDhcp4SConnection NDhcp4SConnection; +typedef struct NDhcp4SConnectionIp NDhcp4SConnectionIp; +typedef struct NDhcp4SEventNode NDhcp4SEventNode; + +/* specs */ + +#define N_DHCP4_NETWORK_IP_MAXIMUM_HEADER_SIZE (60) /* See RFC791 */ +#define N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE (576) /* See RFC791 */ +#define N_DHCP4_NETWORK_SERVER_PORT (67) +#define N_DHCP4_NETWORK_CLIENT_PORT (68) +#define N_DHCP4_MESSAGE_MAGIC ((uint32_t)(0x63825363)) +#define N_DHCP4_MESSAGE_FLAG_BROADCAST (htons(0x8000)) + +enum { + N_DHCP4_OP_BOOTREQUEST = 1, + N_DHCP4_OP_BOOTREPLY = 2, +}; + +enum { + N_DHCP4_OPTION_PAD = 0, + N_DHCP4_OPTION_SUBNET_MASK = 1, + N_DHCP4_OPTION_TIME_OFFSET = 2, + N_DHCP4_OPTION_ROUTER = 3, + N_DHCP4_OPTION_DOMAIN_NAME_SERVER = 6, + N_DHCP4_OPTION_HOST_NAME = 12, + N_DHCP4_OPTION_BOOT_FILE_SIZE = 13, + N_DHCP4_OPTION_DOMAIN_NAME = 15, + N_DHCP4_OPTION_ROOT_PATH = 17, + N_DHCP4_OPTION_ENABLE_IP_FORWARDING = 19, + N_DHCP4_OPTION_ENABLE_IP_FORWARDING_NL = 20, + N_DHCP4_OPTION_POLICY_FILTER = 21, + N_DHCP4_OPTION_INTERFACE_MDR = 22, + N_DHCP4_OPTION_INTERFACE_TTL = 23, + N_DHCP4_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, + N_DHCP4_OPTION_INTERFACE_MTU = 26, + N_DHCP4_OPTION_BROADCAST = 28, + N_DHCP4_OPTION_STATIC_ROUTE = 33, + N_DHCP4_OPTION_NTP_SERVER = 42, + N_DHCP4_OPTION_VENDOR_SPECIFIC = 43, + N_DHCP4_OPTION_REQUESTED_IP_ADDRESS = 50, + N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME = 51, + N_DHCP4_OPTION_OVERLOAD = 52, + N_DHCP4_OPTION_MESSAGE_TYPE = 53, + N_DHCP4_OPTION_SERVER_IDENTIFIER = 54, + N_DHCP4_OPTION_PARAMETER_REQUEST_LIST = 55, + N_DHCP4_OPTION_ERROR_MESSAGE = 56, + N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE = 57, + N_DHCP4_OPTION_RENEWAL_T1_TIME = 58, + N_DHCP4_OPTION_REBINDING_T2_TIME = 59, + N_DHCP4_OPTION_VENDOR_CLASS_IDENTIFIER = 60, + N_DHCP4_OPTION_CLIENT_IDENTIFIER = 61, + N_DHCP4_OPTION_FQDN = 81, + N_DHCP4_OPTION_NEW_POSIX_TIMEZONE = 100, + N_DHCP4_OPTION_NEW_TZDB_TIMEZONE = 101, + N_DHCP4_OPTION_CLASSLESS_STATIC_ROUTE = 121, + N_DHCP4_OPTION_PRIVATE_BASE = 224, + N_DHCP4_OPTION_PRIVATE_LAST = 254, + N_DHCP4_OPTION_END = 255, + _N_DHCP4_OPTION_N = 256, +}; + +enum { + N_DHCP4_OVERLOAD_FILE = 1, + N_DHCP4_OVERLOAD_SNAME = 2, +}; + +enum { + N_DHCP4_MESSAGE_DISCOVER = 1, + N_DHCP4_MESSAGE_OFFER = 2, + N_DHCP4_MESSAGE_REQUEST = 3, + N_DHCP4_MESSAGE_DECLINE = 4, + N_DHCP4_MESSAGE_ACK = 5, + N_DHCP4_MESSAGE_NAK = 6, + N_DHCP4_MESSAGE_RELEASE = 7, + N_DHCP4_MESSAGE_INFORM = 8, + N_DHCP4_MESSAGE_FORCERENEW = 9, +}; + +struct NDhcp4Header { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; +} _c_packed_; + +struct NDhcp4Message { + NDhcp4Header header; + uint8_t sname[64]; + uint8_t file[128]; + uint32_t magic; + uint8_t options[]; +} _c_packed_; + +/* objects */ + +enum { + _N_DHCP4_E_INTERNAL = _N_DHCP4_E_N, + + N_DHCP4_E_UNEXPECTED, + + N_DHCP4_E_NO_SPACE, + N_DHCP4_E_MALFORMED, + + N_DHCP4_E_DROPPED, + N_DHCP4_E_DOWN, + N_DHCP4_E_AGAIN, +}; + +enum { + N_DHCP4_C_CONNECTION_STATE_INIT, + N_DHCP4_C_CONNECTION_STATE_PACKET, + N_DHCP4_C_CONNECTION_STATE_DRAINING, + N_DHCP4_C_CONNECTION_STATE_UDP, + N_DHCP4_C_CONNECTION_STATE_CLOSED, +}; + +enum { + N_DHCP4_CLIENT_EPOLL_TIMER, + N_DHCP4_CLIENT_EPOLL_IO, +}; + +enum { + N_DHCP4_CLIENT_PROBE_STATE_INIT, + N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT, + N_DHCP4_CLIENT_PROBE_STATE_SELECTING, + N_DHCP4_CLIENT_PROBE_STATE_REBOOTING, + N_DHCP4_CLIENT_PROBE_STATE_REQUESTING, + N_DHCP4_CLIENT_PROBE_STATE_GRANTED, + N_DHCP4_CLIENT_PROBE_STATE_BOUND, + N_DHCP4_CLIENT_PROBE_STATE_RENEWING, + N_DHCP4_CLIENT_PROBE_STATE_REBINDING, + N_DHCP4_CLIENT_PROBE_STATE_EXPIRED, +}; + +enum { + N_DHCP4_CLIENT_LEASE_STATE_INIT, + N_DHCP4_CLIENT_LEASE_STATE_OFFERED, + N_DHCP4_CLIENT_LEASE_STATE_SELECTED, + N_DHCP4_CLIENT_LEASE_STATE_DECLINED, + N_DHCP4_CLIENT_LEASE_STATE_ACKED, +}; + +enum { + N_DHCP4_SERVER_EPOLL_TIMER, + N_DHCP4_SERVER_EPOLL_IO, +}; + +enum { + _N_DHCP4_C_MESSAGE_INVALID = 0, + N_DHCP4_C_MESSAGE_DISCOVER, + N_DHCP4_C_MESSAGE_INFORM, + N_DHCP4_C_MESSAGE_SELECT, + N_DHCP4_C_MESSAGE_IGNORE, + N_DHCP4_C_MESSAGE_RENEW, + N_DHCP4_C_MESSAGE_REBIND, + N_DHCP4_C_MESSAGE_REBOOT, + N_DHCP4_C_MESSAGE_RELEASE, + N_DHCP4_C_MESSAGE_DECLINE, +}; + +struct NDhcp4Outgoing { + NDhcp4Message *message; + size_t n_message; + size_t i_message; + size_t max_size; + + uint8_t overload : 2; + + struct { + uint8_t type; + uint64_t start_time; + uint64_t base_time; + uint64_t send_time; + uint64_t send_jitter; + size_t n_send; + } userdata; +}; + +#define N_DHCP4_OUTGOING_NULL(_x) { \ + } + +struct NDhcp4Incoming { + struct { + uint8_t *value; + size_t size; + } options[_N_DHCP4_OPTION_N]; + + struct { + uint8_t type; + uint64_t start_time; + uint64_t base_time; + } userdata; + + size_t n_message; + NDhcp4Message message; + /* @message must be the last member */ +}; + +#define N_DHCP4_INCOMING_NULL(_x) { \ + } + +struct NDhcp4ClientConfig { + int ifindex; + unsigned int transport; + bool request_broadcast; + uint8_t mac[32]; /* MAX_ADDR_LEN */ + size_t n_mac; + uint8_t broadcast_mac[32]; /* MAX_ADDR_LEN */ + size_t n_broadcast_mac; + uint8_t *client_id; + size_t n_client_id; +}; + +#define N_DHCP4_CLIENT_CONFIG_NULL(_x) { \ + .transport = _N_DHCP4_TRANSPORT_N, \ + } + +struct NDhcp4ClientProbeOption { + uint8_t option; + uint8_t n_data; + uint8_t data[]; +}; + +#define N_DHCP4_CLIENT_PROBE_OPTION_NULL(_x) { \ + .option = N_DHCP4_OPTION_PAD, \ + } + +struct NDhcp4ClientProbeConfig { + bool inform_only; + bool init_reboot; + struct in_addr requested_ip; + struct drand48_data entropy; /* entropy pool */ + uint64_t ms_start_delay; /* max ms to wait before starting probe */ + NDhcp4ClientProbeOption *options[UINT8_MAX + 1]; + int8_t request_parameters[UINT8_MAX + 1]; + size_t n_request_parameters; +}; + +#define N_DHCP4_CLIENT_PROBE_CONFIG_NULL(_x) { \ + .ms_start_delay = N_DHCP4_CLIENT_START_DELAY_RFC2131, \ + } + +struct NDhcp4CEventNode { + CList client_link; + CList probe_link; + NDhcp4ClientEvent event; + bool is_public : 1; +}; + +#define N_DHCP4_C_EVENT_NODE_NULL(_x) { \ + .client_link = C_LIST_INIT((_x).client_link), \ + .probe_link = C_LIST_INIT((_x).probe_link), \ + } + +struct NDhcp4CConnection { + NDhcp4ClientConfig *client_config; + NDhcp4ClientProbeConfig *probe_config; + int fd_epoll; + + unsigned int state; /* current connection state */ + int fd_packet; /* packet socket */ + int fd_udp; /* udp socket */ + + NDhcp4Outgoing *request; /* current request */ + + uint32_t client_ip; /* client IP address, or 0 */ + uint32_t server_ip; /* server IP address, or 0 */ + uint16_t mtu; /* client mtu, or 0 */ + + /* + * When we get DHCP packets from the kernel, we need a buffer to read + * the data into. Since UDP packets can be up to 2^16 bytes in size, we + * avoid placing it on the stack and instead read into this scratch + * buffer. It is purely meant as stack replacement, no data is returned + * through this buffer. + */ + uint8_t scratch_buffer[UINT16_MAX]; +}; + +#define N_DHCP4_C_CONNECTION_NULL(_x) { \ + .fd_packet = -1, \ + .fd_udp = -1, \ + } + +struct NDhcp4Client { + unsigned long n_refs; + NDhcp4ClientConfig *config; + CList event_list; + int fd_epoll; + int fd_timer; + + uint16_t mtu; + NDhcp4ClientProbe *current_probe; + uint64_t scheduled_timeout; + + bool preempted : 1; +}; + +#define N_DHCP4_CLIENT_NULL(_x) { \ + .n_refs = 1, \ + .event_list = C_LIST_INIT((_x).event_list), \ + .fd_epoll = -1, \ + .fd_timer = -1, \ + } + +struct NDhcp4ClientProbe { + NDhcp4ClientProbeConfig *config; + NDhcp4Client *client; + CList event_list; + CList lease_list; + void *userdata; + + unsigned int state; /* current probe state */ + uint64_t ns_deferred; /* timeout for deferred action */ + NDhcp4ClientLease *current_lease; /* current lease */ + + NDhcp4CConnection connection; /* client connection wrapper */ +}; + +#define N_DHCP4_CLIENT_PROBE_NULL(_x) { \ + .event_list = C_LIST_INIT((_x).event_list), \ + .lease_list = C_LIST_INIT((_x).lease_list), \ + .connection = N_DHCP4_C_CONNECTION_NULL((_x).connection), \ + } + +struct NDhcp4ClientLease { + unsigned long n_refs; + + NDhcp4ClientProbe *probe; + CList probe_link; + + NDhcp4Incoming *message; + + uint64_t t1; + uint64_t t2; + uint64_t lifetime; +}; + +#define N_DHCP4_CLIENT_LEASE_NULL(_x) { \ + .n_refs = 1, \ + .probe_link = C_LIST_INIT((_x).probe_link), \ + } + +struct NDhcp4ServerConfig { + int ifindex; +}; + +#define N_DHCP4_SERVER_CONFIG_NULL(_x) { \ + } + +struct NDhcp4SEventNode { + CList server_link; + NDhcp4ServerEvent event; + bool is_public : 1; +}; + +#define N_DHCP4_S_EVENT_NODE_NULL(_x) { \ + .server_link = C_LIST_INIT((_x).server_link), \ + } + +struct NDhcp4SConnection { + int ifindex; /* interface index */ + int fd_packet; /* packet socket */ + int fd_udp; /* udp socket */ + uint8_t buf[UINT16_MAX]; /* scratch recevie buffer */ + + /* XXX: support a set of server addresses */ + NDhcp4SConnectionIp *ip; /* server IP address, or NULL */ +}; + +#define N_DHCP4_S_CONNECTION_NULL(_x) { \ + .fd_packet = -1, \ + .fd_udp = -1, \ + } + +struct NDhcp4SConnectionIp { + NDhcp4SConnection *connection; + struct in_addr ip; +}; + +#define N_DHCP4_S_CONNECTION_IP_NULL(_x) { \ +} + +struct NDhcp4Server { + unsigned long n_refs; + CList event_list; + CList lease_list; + + bool preempted : 1; + + NDhcp4SConnection connection; +}; + +#define N_DHCP4_SERVER_NULL(_x) { \ + .n_refs = 1, \ + .event_list = C_LIST_INIT((_x).event_list), \ + .lease_list = C_LIST_INIT((_x).lease_list), \ + .connection = N_DHCP4_S_CONNECTION_NULL((_x).connection), \ + } + +struct NDhcp4ServerIp { + NDhcp4SConnectionIp ip; +}; + +#define N_DHCP4_SERVER_IP_NULL(_x) { \ + .ip = N_DHCP4_S_CONNECTION_IP_NULL((_x).ip), \ + } + +struct NDhcp4ServerLease { + unsigned long n_refs; + + NDhcp4Server *server; + CList server_link; + + NDhcp4Incoming *request; + NDhcp4Incoming *reply; +}; + +#define N_DHCP4_SERVER_LEASE_NULL(_x) { \ + .n_refs = 1, \ + .server_link = C_LIST_INIT((_x).server_link), \ + } + +/* outgoing messages */ + +int n_dhcp4_outgoing_new(NDhcp4Outgoing **outgoingp, size_t max_size, uint8_t overload); +NDhcp4Outgoing *n_dhcp4_outgoing_free(NDhcp4Outgoing *outgoing); + +NDhcp4Header *n_dhcp4_outgoing_get_header(NDhcp4Outgoing *outgoing); +size_t n_dhcp4_outgoing_get_raw(NDhcp4Outgoing *outgoing, const void **rawp); +int n_dhcp4_outgoing_append(NDhcp4Outgoing *outgoing, uint8_t option, const void *data, uint8_t n_data); + +int n_dhcp4_outgoing_append_t1(NDhcp4Outgoing *message, uint32_t t1); +int n_dhcp4_outgoing_append_t2(NDhcp4Outgoing *message, uint32_t t2); +int n_dhcp4_outgoing_append_lifetime(NDhcp4Outgoing *message, uint32_t lifetime); +int n_dhcp4_outgoing_append_server_identifier(NDhcp4Outgoing *message, struct in_addr addr); +int n_dhcp4_outgoing_append_requested_ip(NDhcp4Outgoing *message, struct in_addr addr); + +void n_dhcp4_outgoing_set_secs(NDhcp4Outgoing *message, uint32_t secs); +void n_dhcp4_outgoing_set_xid(NDhcp4Outgoing *message, uint32_t xid); +void n_dhcp4_outgoing_set_yiaddr(NDhcp4Outgoing *message, struct in_addr yiaddr); + +void n_dhcp4_outgoing_get_xid(NDhcp4Outgoing *message, uint32_t *xidp); + +/* incoming messages */ + +int n_dhcp4_incoming_new(NDhcp4Incoming **incomingp, const void *raw, size_t n_raw); +NDhcp4Incoming *n_dhcp4_incoming_free(NDhcp4Incoming *incoming); + +NDhcp4Header *n_dhcp4_incoming_get_header(NDhcp4Incoming *incoming); +size_t n_dhcp4_incoming_get_raw(NDhcp4Incoming *incoming, const void **rawp); +int n_dhcp4_incoming_query(NDhcp4Incoming *incoming, uint8_t option, uint8_t **datap, size_t *n_datap); + +int n_dhcp4_incoming_query_message_type(NDhcp4Incoming *message, uint8_t *typep); +int n_dhcp4_incoming_query_lifetime(NDhcp4Incoming *message, uint32_t *lifetimep); +int n_dhcp4_incoming_query_t2(NDhcp4Incoming *message, uint32_t *t2p); +int n_dhcp4_incoming_query_t1(NDhcp4Incoming *message, uint32_t *t1p); +int n_dhcp4_incoming_query_server_identifier(NDhcp4Incoming *message, struct in_addr *idp); +int n_dhcp4_incoming_query_max_message_size(NDhcp4Incoming *message, uint16_t *max_message_sizep); +int n_dhcp4_incoming_query_requested_ip(NDhcp4Incoming *message, struct in_addr *requested_ipp); + +void n_dhcp4_incoming_get_xid(NDhcp4Incoming *message, uint32_t *xidp); +void n_dhcp4_incoming_get_yiaddr(NDhcp4Incoming *message, struct in_addr *yiaddr); + +/* sockets */ + +int n_dhcp4_c_socket_packet_new(int *sockfdp, int ifindex); +int n_dhcp4_c_socket_udp_new(int *sockfdp, + int ifindex, + const struct in_addr *client_addr, + const struct in_addr *server_addr); +int n_dhcp4_s_socket_packet_new(int *sockfdp); +int n_dhcp4_s_socket_udp_new(int *sockfdp, int ifindex); + +int n_dhcp4_c_socket_packet_send(int sockfd, + int ifindex, + const unsigned char *dest_haddr, + unsigned char halen, + NDhcp4Outgoing *message); +int n_dhcp4_c_socket_udp_send(int sockfd, NDhcp4Outgoing *message); +int n_dhcp4_c_socket_udp_broadcast(int sockfd, NDhcp4Outgoing *message); +int n_dhcp4_s_socket_packet_send(int sockfd, + int ifindex, + const struct in_addr *src_inaddr, + const unsigned char *dest_haddr, + unsigned char halen, + const struct in_addr *dest_inaddr, + NDhcp4Outgoing *message); +int n_dhcp4_s_socket_udp_send(int sockfd, + const struct in_addr *inaddr_src, + const struct in_addr *inaddr_dest, + NDhcp4Outgoing *message); +int n_dhcp4_s_socket_udp_broadcast(int sockfd, + const struct in_addr *inaddr_src, + NDhcp4Outgoing *message); + +int n_dhcp4_c_socket_packet_recv(int sockfd, + uint8_t *buf, + size_t n_buf, + NDhcp4Incoming **messagep); +int n_dhcp4_c_socket_udp_recv(int sockfd, + uint8_t *buf, + size_t n_buf, + NDhcp4Incoming **messagep); +int n_dhcp4_s_socket_udp_recv(int sockfd, + uint8_t *buf, + size_t n_buf, + NDhcp4Incoming **messagep, + struct sockaddr_in *dest); + +/* client configs */ + +int n_dhcp4_client_config_dup(NDhcp4ClientConfig *config, + NDhcp4ClientConfig **dupp); + +/* client probe configs */ + +int n_dhcp4_client_probe_config_dup(NDhcp4ClientProbeConfig *config, + NDhcp4ClientProbeConfig **dupp); +uint32_t n_dhcp4_client_probe_config_get_random(NDhcp4ClientProbeConfig *config); + +/* client events */ + +int n_dhcp4_c_event_node_new(NDhcp4CEventNode **nodep); +NDhcp4CEventNode *n_dhcp4_c_event_node_free(NDhcp4CEventNode *node); + +/* client connections */ + +int n_dhcp4_c_connection_init(NDhcp4CConnection *connection, + NDhcp4ClientConfig *client_config, + NDhcp4ClientProbeConfig *probe_config, + int fd_epoll); +void n_dhcp4_c_connection_deinit(NDhcp4CConnection *connection); + +int n_dhcp4_c_connection_listen(NDhcp4CConnection *connection); +int n_dhcp4_c_connection_connect(NDhcp4CConnection *connection, + const struct in_addr *client, + const struct in_addr *server); +void n_dhcp4_c_connection_close(NDhcp4CConnection *connection); + +void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection, + uint64_t *timeoutp); + +int n_dhcp4_c_connection_discover_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **request); +int n_dhcp4_c_connection_select_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **request, + NDhcp4Incoming *offer); +int n_dhcp4_c_connection_reboot_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **request, + const struct in_addr *client); +int n_dhcp4_c_connection_renew_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **request); +int n_dhcp4_c_connection_rebind_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **request); +int n_dhcp4_c_connection_decline_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **request, + NDhcp4Incoming *ack, + const char *error); +int n_dhcp4_c_connection_inform_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **request); +int n_dhcp4_c_connection_release_new(NDhcp4CConnection *connection, + NDhcp4Outgoing **request, + const char *error); + +int n_dhcp4_c_connection_start_request(NDhcp4CConnection *connection, + NDhcp4Outgoing *request, + uint64_t timestamp); +int n_dhcp4_c_connection_dispatch_timer(NDhcp4CConnection *connection, + uint64_t timestamp); +int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection, + NDhcp4Incoming **messagep); + +/* clients */ + +int n_dhcp4_client_raise(NDhcp4Client *client, NDhcp4CEventNode **nodep, unsigned int event); +void n_dhcp4_client_arm_timer(NDhcp4Client *client); + +/* client probes */ + +int n_dhcp4_client_probe_new(NDhcp4ClientProbe **probep, + NDhcp4ClientProbeConfig *config, + NDhcp4Client *client, + uint64_t ns_now); + +int n_dhcp4_client_probe_raise(NDhcp4ClientProbe *probe, NDhcp4CEventNode **nodep, unsigned int event); +void n_dhcp4_client_probe_get_timeout(NDhcp4ClientProbe *probe, uint64_t *timeoutp); +int n_dhcp4_client_probe_dispatch_timer(NDhcp4ClientProbe *probe, uint64_t ns_now); +int n_dhcp4_client_probe_dispatch_io(NDhcp4ClientProbe *probe, uint32_t events); +int n_dhcp4_client_probe_transition_select(NDhcp4ClientProbe *probe, NDhcp4Incoming *offer, uint64_t ns_now); +int n_dhcp4_client_probe_transition_accept(NDhcp4ClientProbe *probe, NDhcp4Incoming *ack); +int n_dhcp4_client_probe_transition_decline(NDhcp4ClientProbe *probe, NDhcp4Incoming *offer, const char *error, uint64_t ns_now); +int n_dhcp4_client_probe_update_mtu(NDhcp4ClientProbe *probe, uint16_t mtu); + +/* client leases */ + +int n_dhcp4_client_lease_new(NDhcp4ClientLease **leasep, NDhcp4Incoming *message); +void n_dhcp4_client_lease_link(NDhcp4ClientLease *lease, NDhcp4ClientProbe *probe); +void n_dhcp4_client_lease_unlink(NDhcp4ClientLease *lease); + +/* server connections */ + +int n_dhcp4_s_connection_init(NDhcp4SConnection *connection, int ifindex); +void n_dhcp4_s_connection_deinit(NDhcp4SConnection *connection); + +void n_dhcp4_s_connection_get_fd(NDhcp4SConnection *connection, int *fdp); +int n_dhcp4_s_connection_dispatch_io(NDhcp4SConnection *connection, NDhcp4Incoming **messagep); + +int n_dhcp4_s_connection_offer_new(NDhcp4SConnection *connection, + NDhcp4Outgoing **replyp, + NDhcp4Incoming *request, + const struct in_addr *server_address, + const struct in_addr *client_address, + uint32_t lifetime); +int n_dhcp4_s_connection_ack_new(NDhcp4SConnection *connection, + NDhcp4Outgoing **replyp, + NDhcp4Incoming *request, + const struct in_addr *server_address, + const struct in_addr *client_address, + uint32_t lifetime); +int n_dhcp4_s_connection_nak_new(NDhcp4SConnection *connection, + NDhcp4Outgoing **replyp, + NDhcp4Incoming *request, + const struct in_addr *server_address); + +int n_dhcp4_s_connection_send_reply(NDhcp4SConnection *connection, + const struct in_addr *server_addr, + NDhcp4Outgoing *reply); + +/* server connection ips */ + +void n_dhcp4_s_connection_ip_init(NDhcp4SConnectionIp *ip, struct in_addr addr); +void n_dhcp4_s_connection_ip_deinit(NDhcp4SConnectionIp *ip); + +void n_dhcp4_s_connection_ip_link(NDhcp4SConnectionIp *ip, NDhcp4SConnection *connection); +void n_dhcp4_s_connection_ip_unlink(NDhcp4SConnectionIp *ip); + +/* inline helpers */ + +static inline void n_dhcp4_outgoing_freep(NDhcp4Outgoing **outgoing) { + if (*outgoing) + n_dhcp4_outgoing_free(*outgoing); +} + +static inline void n_dhcp4_incoming_freep(NDhcp4Incoming **incoming) { + if (*incoming) + n_dhcp4_incoming_free(*incoming); +} + +static inline uint64_t n_dhcp4_gettime(clockid_t clock) { + struct timespec ts; + int r; + + r = clock_gettime(clock, &ts); + c_assert(r >= 0); + + return ts.tv_sec * 1000ULL * 1000ULL * 1000ULL + ts.tv_nsec; +} diff --git a/shared/n-dhcp4/src/n-dhcp4-socket.c b/shared/n-dhcp4/src/n-dhcp4-socket.c new file mode 100644 index 00000000..b9ac176f --- /dev/null +++ b/shared/n-dhcp4/src/n-dhcp4-socket.c @@ -0,0 +1,657 @@ +/* + * DHCP specific low-level socket helpers + */ + +#include <c-stdaux.h> +#include <errno.h> +#include <linux/filter.h> +#include <sys/socket.h> /* needed by linux/if.h */ +#include <linux/if.h> +#include <linux/if_packet.h> +#include <linux/netdevice.h> +#include <linux/udp.h> +#include <netinet/ip.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <sys/types.h> +#include "n-dhcp4-private.h" +#include "util/packet.h" +#include "util/socket.h" + +/** + * n_dhcp4_c_socket_packet_new() - create a new DHCP4 client packet socket + * @sockfdp: return argumnet for the new socket + * @ifindex: interface index to bind to + * + * Create a new AF_PACKET/SOCK_DGRAM socket usable to listen to and send DHCP client + * packets before an IP address has been configured. + * + * Only unfragmented DHCP packets from a server to a client destined for the given + * ifindex is returned. + * + * Return: 0 on success, or a negative error code on failure. + */ +int n_dhcp4_c_socket_packet_new(int *sockfdp, int ifindex) { + _c_cleanup_(c_closep) int sockfd = -1; + struct sock_filter filter[] = { + /* + * IP + * + * Check + * - UDP + * - Unfragmented + * - Large enough to fit the DHCP header + * + * Leave X the size of the IP header, for future indirect reads. + */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, protocol)), /* A <- IP protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, frag_off)), /* A <- Flags */ + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, ntohs(IP_MF | IP_OFFMASK)), /* A <- A & (IP_MF | IP_OFFMASK) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* fragmented packet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length */ + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_STMT(BPF_ALU + BPF_SUB + BPF_X, 0), /* A -= X */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct udphdr) + sizeof(NDhcp4Message), 1, 0), /* packet >= DHCPPacket ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* + * UDP + * + * Check + * - DHCP client port + * + * Leave X the size of IP and UDP headers, for future indirect reads. + */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct udphdr, dest)), /* A <- UDP destination port */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_NETWORK_CLIENT_PORT, 1, 0), /* UDP destination port == DHCP client port ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + BPF_STMT(BPF_LD + BPF_W + BPF_K, sizeof(struct udphdr)), /* A <- size of UDP header */ + BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), /* A += X */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + + /* + * DHCP + * + * Check + * - BOOTREPLY (from server to client) + * - DHCP magic cookie + */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(NDhcp4Header, op)), /* A <- DHCP op */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_OP_BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + BPF_STMT(BPF_LD + BPF_W + BPF_IND, offsetof(NDhcp4Message, magic)), /* A <- DHCP magic cookie */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_MESSAGE_MAGIC, 1, 0), /* cookie == DHCP magic cookie ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + }; + struct sock_fprog fprog = { + .filter = filter, + .len = sizeof(filter) / sizeof(filter[0]), + }; + struct sockaddr_ll addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = ifindex, + }; + int r, on = 1; + + sockfd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sockfd < 0) + return -errno; + + r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + /* We need the flag that tells us if the checksum is correct. */ + r = setsockopt(sockfd, SOL_PACKET, PACKET_AUXDATA, &on, sizeof(on)); + if (r < 0) + return -errno; + + r = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); + if (r < 0) + return -errno; + + *sockfdp = sockfd; + sockfd = -1; + return 0; +} + +/** + * n_dhcp4_c_socket_udp_new() - create a new DHCP4 client UDP socket + * @sockfdp: return argumnet for the new socket + * @ifindex: interface index to bind to + * @client_addr: client address to bind to + * @server_addr: server address to connect to + * + * Create a new AF_INET/SOCK_DGRAM socket usable to listen to and send DHCP client + * packets. + * + * The client address given in @addr must be configured on the interface @ifindex + * before the socket is created. + * + * Return: 0 on success, or a negative error code on failure. + */ +int n_dhcp4_c_socket_udp_new(int *sockfdp, + int ifindex, + const struct in_addr *client_addr, + const struct in_addr *server_addr) { + _c_cleanup_(c_closep) int sockfd = -1; + struct sock_filter filter[] = { + /* + * IP/UDP + * + * Set X to the size of IP and UDP headers, for future indirect reads. + */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length */ + BPF_STMT(BPF_LD + BPF_W + BPF_K, sizeof(struct udphdr)), /* A <- size of UDP header */ + BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), /* A += X */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + + /* + * DHCP + * + * Check + * - BOOTREPLY (from server to client) + * - DHCP magic cookie + */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(NDhcp4Header, op)), /* A <- DHCP op */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_OP_BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + BPF_STMT(BPF_LD + BPF_W + BPF_IND, offsetof(NDhcp4Message, magic)), /* A <- DHCP magic cookie */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_MESSAGE_MAGIC, 1, 0), /* cookie == DHCP magic cookie ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + }; + struct sock_fprog fprog = { + .filter = filter, + .len = sizeof(filter) / sizeof(filter[0]), + }; + struct sockaddr_in saddr = { + .sin_family = AF_INET, + .sin_addr = *client_addr, + .sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT), + }; + struct sockaddr_in daddr = { + .sin_family = AF_INET, + .sin_addr = *server_addr, + .sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT), + }; + int r, tos = IPTOS_CLASS_CS6, on = 1; + + sockfd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sockfd < 0) + return -errno; + + r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + r = socket_bind_if(sockfd, ifindex); + if (r) + return r; + + r = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); + if (r < 0) + return -errno; + + r = setsockopt(sockfd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); + if (r < 0) + return -errno; + + r = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); + if (r < 0) + return -errno; + + r = connect(sockfd, (struct sockaddr*)&daddr, sizeof(daddr)); + if (r < 0) + return -errno; + + *sockfdp = sockfd; + sockfd = -1; + return 0; +} + +/** + * n_dhcp4_s_socket_packet_new() - create a new DHCP4 server packet socket + * @sockfdp: return argumnet for the new socket + * + * Create a new AF_PACKET/SOCK_DGRAM socket usable to send DHCP packets to clients + * before they have an IP address configured, on the given interface. + * + * Return: 0 on success, or a negative error code on failure. + */ +int n_dhcp4_s_socket_packet_new(int *sockfdp) { + _c_cleanup_(c_closep) int sockfd = -1; + + sockfd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sockfd < 0) + return -errno; + + *sockfdp = sockfd; + sockfd = -1; + return 0; +} + +/** + * n_dhcp4_s_socket_udp_new() - create a new DHCP4 server UDP socket + * @sockfdp: return argumnet for the new socket + * @ifindex: intercafe index to bind to + * + * Create a new AF_INET/SOCK_DGRAM socket usable to listen to DHCP server packets, + * on the given interface. + * + * Return: 0 on success, or a negative error code on failure. + */ +int n_dhcp4_s_socket_udp_new(int *sockfdp, int ifindex) { + _c_cleanup_(c_closep) int sockfd = -1; + struct sock_filter filter[] = { + /* + * IP/UDP + * + * Set X to the size of IP and UDP headers, for future indirect reads. + */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length */ + BPF_STMT(BPF_LD + BPF_W + BPF_K, sizeof(struct udphdr)), /* A <- size of UDP header */ + BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), /* A += X */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + + /* + * DHCP + * + * Check + * - BOOTREQUEST (from client to server) + * - DHCP magic cookie + */ + + BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(NDhcp4Header, op)), /* A <- DHCP op */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_OP_BOOTREQUEST, 1, 0), /* op == BOOTREQUEST ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + BPF_STMT(BPF_LD + BPF_W + BPF_IND, offsetof(NDhcp4Message, magic)), /* A <- DHCP magic cookie */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_MESSAGE_MAGIC, 1, 0), /* cookie == DHCP magic cookie ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + }; + struct sock_fprog fprog = { + .filter = filter, + .len = sizeof(filter) / sizeof(filter[0]), + }; + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr = { INADDR_ANY }, + .sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT), + }; + int r, tos = IPTOS_CLASS_CS6, on = 1; + + sockfd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sockfd < 0) + return -errno; + + r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + r = socket_bind_if(sockfd, ifindex); + if (r) + return r; + + r = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); + if (r < 0) + return -errno; + + r = setsockopt(sockfd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); + if (r < 0) + return -errno; + + r = setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); + if (r < 0) + return -errno; + + r = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); + if (r < 0) + return -errno; + + *sockfdp = sockfd; + sockfd = -1; + return 0; +} + +static int n_dhcp4_socket_packet_send(int sockfd, + int ifindex, + const struct sockaddr_in *src_paddr, + const unsigned char *dest_haddr, + unsigned char halen, + const struct sockaddr_in *dest_paddr, + NDhcp4Outgoing *message) { + struct packet_sockaddr_ll haddr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = ifindex, + .sll_halen = halen, + }; + const void *buf; + size_t n_buf, len; + int r; + + c_assert(halen <= sizeof(haddr.sll_addr)); + + memcpy(haddr.sll_addr, dest_haddr, halen); + + n_buf = n_dhcp4_outgoing_get_raw(message, &buf); + + r = packet_sendto_udp(sockfd, buf, n_buf, &len, src_paddr, &haddr, dest_paddr); + if (r < 0) { + if (r == -EAGAIN || r == -ENOBUFS) + return N_DHCP4_E_DROPPED; + else if (r == -ENETDOWN || r == -ENXIO) + return N_DHCP4_E_DOWN; + else + return r; + } else if (len != n_buf) { + return N_DHCP4_E_DROPPED; + } + + return 0; +} + +/** + * n_dhcp4_c_socket_packet_send() - XXX + */ +int n_dhcp4_c_socket_packet_send(int sockfd, + int ifindex, + const unsigned char *dest_haddr, + unsigned char halen, + NDhcp4Outgoing *message) { + struct sockaddr_in src_paddr = { + .sin_family = AF_INET, + .sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT), + .sin_addr = { INADDR_ANY }, + }; + struct sockaddr_in dest_paddr = { + .sin_family = AF_INET, + .sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT), + .sin_addr = { INADDR_BROADCAST } + }; + + return n_dhcp4_socket_packet_send(sockfd, + ifindex, + &src_paddr, + dest_haddr, + halen, + &dest_paddr, + message); +} + +/** + * n_dhcp4_c_socket_udp_send() - XXX + */ +int n_dhcp4_c_socket_udp_send(int sockfd, + NDhcp4Outgoing *message) { + const void *buf; + size_t n_buf; + ssize_t len; + + n_buf = n_dhcp4_outgoing_get_raw(message, &buf); + + len = send(sockfd, buf, n_buf, 0); + if (len < 0) { + if (errno == EAGAIN || errno == ENOBUFS) + return N_DHCP4_E_DROPPED; + else if (errno == ENETDOWN || errno == ENXIO) + return N_DHCP4_E_DOWN; + else + return -errno; + } else if ((size_t)len != n_buf) + return N_DHCP4_E_DROPPED; + + return 0; +} + +/** + * n_dhcp4_c_socket_udp_broadcast() - XXX + */ +int n_dhcp4_c_socket_udp_broadcast(int sockfd, NDhcp4Outgoing *message) { + struct sockaddr_in sockaddr_dest = { + .sin_family = AF_INET, + .sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT), + .sin_addr = { INADDR_BROADCAST }, + }; + const void *buf; + size_t n_buf; + ssize_t len; + + n_buf = n_dhcp4_outgoing_get_raw(message, &buf); + + len = sendto(sockfd, + buf, + n_buf, + 0, + (struct sockaddr*)&sockaddr_dest, + sizeof(sockaddr_dest)); + if (len < 0) { + if (errno == EAGAIN || errno == ENOBUFS) + return N_DHCP4_E_DROPPED; + else if (errno == ENETDOWN || errno == ENXIO) + return N_DHCP4_E_DOWN; + else + return -errno; + } else if ((size_t)len != n_buf) + return N_DHCP4_E_DROPPED; + + return 0; +} + +/** + * n_dhcp4_s_socket_packet_send() - XXX + */ +int n_dhcp4_s_socket_packet_send(int sockfd, + int ifindex, + const struct in_addr *src_inaddr, + const unsigned char *dest_haddr, + unsigned char halen, + const struct in_addr *dest_inaddr, + NDhcp4Outgoing *message) { + struct sockaddr_in src_paddr = { + .sin_family = AF_INET, + .sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT), + .sin_addr = *src_inaddr, + }; + struct sockaddr_in dest_paddr = { + .sin_family = AF_INET, + .sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT), + .sin_addr = *dest_inaddr, + }; + + return n_dhcp4_socket_packet_send(sockfd, + ifindex, + &src_paddr, + dest_haddr, + halen, + &dest_paddr, + message); +} + +/** + * n_dhcp4_s_socket_udp_send() - XXX + */ +int n_dhcp4_s_socket_udp_send(int sockfd, + const struct in_addr *inaddr_src, + const struct in_addr *inaddr_dest, + NDhcp4Outgoing *message) { + struct sockaddr_in sockaddr_dest = { + .sin_family = AF_INET, + .sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT), + .sin_addr = *inaddr_dest, + }; + struct iovec iov = {}; + union { + struct cmsghdr align; /* ensure correct stack alignment */ + char buf[CMSG_SPACE(sizeof(struct in_pktinfo))]; + } control = {}; + struct in_pktinfo pktinfo = { + .ipi_spec_dst = *inaddr_src, + }; + struct msghdr msg = { + .msg_name = (void*)&sockaddr_dest, + .msg_namelen = sizeof(sockaddr_dest), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control.buf, + .msg_controllen = sizeof(control.buf), + }; + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + ssize_t len; + + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + memcpy(CMSG_DATA(cmsg), &pktinfo, sizeof(pktinfo)); + + iov.iov_len = n_dhcp4_outgoing_get_raw(message, (const void **)&iov.iov_base); + + len = sendmsg(sockfd, &msg, 0); + if (len < 0) { + if (errno == EAGAIN || errno == ENOBUFS) + return N_DHCP4_E_DROPPED; + else if (errno == ENETDOWN || errno == ENXIO) + return N_DHCP4_E_DOWN; + else + return -errno; + } else if ((size_t)len != iov.iov_len) + return N_DHCP4_E_DROPPED; + + return 0; +} + +int n_dhcp4_s_socket_udp_broadcast(int sockfd, + const struct in_addr *inaddr_src, + NDhcp4Outgoing *message) { + return n_dhcp4_s_socket_udp_send(sockfd, + inaddr_src, + &(const struct in_addr){INADDR_BROADCAST}, + message); +} + +int n_dhcp4_c_socket_packet_recv(int sockfd, + uint8_t *buf, + size_t n_buf, + NDhcp4Incoming **messagep) { + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL; + size_t len; + int r; + + r = packet_recv_udp(sockfd, buf, n_buf, &len); + if (r < 0) { + if (r == -ENETDOWN) + return N_DHCP4_E_DOWN; + else if (r == -EAGAIN) + return N_DHCP4_E_AGAIN; + else + return -errno; + } else if (len == 0) { + return N_DHCP4_E_MALFORMED; + } + + r = n_dhcp4_incoming_new(&message, buf, len); + if (r) + return r; + + *messagep = message; + message = NULL; + return 0; +} + +static int n_dhcp4_socket_udp_recv(int sockfd, + uint8_t *buf, + size_t n_buf, + NDhcp4Incoming **messagep, + struct in_pktinfo *pktinfo) { + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL; + struct iovec iov = { + .iov_base = buf, + .iov_len = n_buf, + }; + uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))]; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf), + }; + ssize_t len; + int r; + + len = recvmsg(sockfd, &msg, MSG_TRUNC); + if (len < 0) { + if (errno == ENETDOWN) + return N_DHCP4_E_DOWN; + else if (errno == EAGAIN) + return N_DHCP4_E_AGAIN; + else + return -errno; + } else if (len == 0 || (size_t)len > n_buf) { + return N_DHCP4_E_MALFORMED; + } + + r = n_dhcp4_incoming_new(&message, buf, len); + if (r) + return r; + + if (pktinfo) { + struct cmsghdr *cmsg; + + cmsg = CMSG_FIRSTHDR(&msg); + c_assert(cmsg); + c_assert(cmsg->cmsg_level == IPPROTO_IP); + c_assert(cmsg->cmsg_type == IP_PKTINFO); + c_assert(cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))); + + memcpy(pktinfo, (void*)CMSG_DATA(cmsg), sizeof(struct in_pktinfo)); + } + + *messagep = message; + message = NULL; + return 0; +} + +int n_dhcp4_c_socket_udp_recv(int sockfd, + uint8_t *buf, + size_t n_buf, + NDhcp4Incoming **messagep) { + return n_dhcp4_socket_udp_recv(sockfd, buf, n_buf, messagep, NULL); +} + +int n_dhcp4_s_socket_udp_recv(int sockfd, + uint8_t *buf, + size_t n_buf, + NDhcp4Incoming **messagep, + struct sockaddr_in *dest) { + struct in_pktinfo pktinfo = {}; + int r; + + r = n_dhcp4_socket_udp_recv(sockfd, buf, n_buf, messagep, &pktinfo); + if (r) + return r; + + if (dest) { + dest->sin_family = AF_INET; + dest->sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT); + dest->sin_addr.s_addr = pktinfo.ipi_addr.s_addr; + } + + return 0; +} diff --git a/shared/n-dhcp4/src/n-dhcp4.h b/shared/n-dhcp4/src/n-dhcp4.h new file mode 100644 index 00000000..58a3cb80 --- /dev/null +++ b/shared/n-dhcp4/src/n-dhcp4.h @@ -0,0 +1,286 @@ +#pragma once + +/* + * Dynamic Host Configuration Protocol for IPv4 + * + * This is the public header of the n-dhcp4 library, implementing IPv4 Dynamic + * Host Configuration Protocol as described in RFC-2132. This header defines + * the public API and all entry points of n-dhcp4. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <inttypes.h> +#include <netinet/in.h> +#include <stdbool.h> +#include <stdlib.h> + +typedef struct NDhcp4Client NDhcp4Client; +typedef struct NDhcp4ClientConfig NDhcp4ClientConfig; +typedef struct NDhcp4ClientEvent NDhcp4ClientEvent; +typedef struct NDhcp4ClientLease NDhcp4ClientLease; +typedef struct NDhcp4ClientProbe NDhcp4ClientProbe; +typedef struct NDhcp4ClientProbeConfig NDhcp4ClientProbeConfig; +typedef struct NDhcp4Server NDhcp4Server; +typedef struct NDhcp4ServerConfig NDhcp4ServerConfig; +typedef struct NDhcp4ServerEvent NDhcp4ServerEvent; +typedef struct NDhcp4ServerIp NDhcp4ServerIp; +typedef struct NDhcp4ServerLease NDhcp4ServerLease; + +#define N_DHCP4_CLIENT_START_DELAY_RFC2131 (UINT64_C(9000)) + +enum { + _N_DHCP4_E_SUCCESS, + + N_DHCP4_E_PREEMPTED, + N_DHCP4_E_INTERNAL, + + N_DHCP4_E_INVALID_IFINDEX, + N_DHCP4_E_INVALID_TRANSPORT, + N_DHCP4_E_INVALID_ADDRESS, + N_DHCP4_E_INVALID_CLIENT_ID, + N_DHCP4_E_DUPLICATE_OPTION, + N_DHCP4_E_UNSET, + + _N_DHCP4_E_N, +}; + +enum { + N_DHCP4_TRANSPORT_ETHERNET, + N_DHCP4_TRANSPORT_INFINIBAND, + _N_DHCP4_TRANSPORT_N, +}; + +enum { + N_DHCP4_CLIENT_EVENT_DOWN, + N_DHCP4_CLIENT_EVENT_OFFER, + N_DHCP4_CLIENT_EVENT_GRANTED, + N_DHCP4_CLIENT_EVENT_RETRACTED, + N_DHCP4_CLIENT_EVENT_EXTENDED, + N_DHCP4_CLIENT_EVENT_EXPIRED, + N_DHCP4_CLIENT_EVENT_CANCELLED, + _N_DHCP4_CLIENT_EVENT_N, +}; + +enum { + N_DHCP4_SERVER_EVENT_DOWN, + N_DHCP4_SERVER_EVENT_DISCOVER, + N_DHCP4_SERVER_EVENT_REQUEST, + N_DHCP4_SERVER_EVENT_RENEW, + N_DHCP4_SERVER_EVENT_DECLINE, + N_DHCP4_SERVER_EVENT_RELEASE, + _N_DHCP4_SERVER_EVENT_N, +}; + +struct NDhcp4ClientEvent { + unsigned int event; + union { + struct { + } down; + struct { + NDhcp4ClientProbe *probe; + NDhcp4ClientLease *lease; + } offer, granted, extended; + struct { + NDhcp4ClientProbe *probe; + } retracted, expired, cancelled; + }; +}; + +struct NDhcp4ServerEvent { + unsigned int event; + union { + struct { + } down; + struct { + NDhcp4ServerLease *lease; + } discover, request, decline, release; + }; +}; + +/* client configs */ + +int n_dhcp4_client_config_new(NDhcp4ClientConfig **configp); +NDhcp4ClientConfig *n_dhcp4_client_config_free(NDhcp4ClientConfig *config); + +void n_dhcp4_client_config_set_ifindex(NDhcp4ClientConfig *config, int ifindex); +void n_dhcp4_client_config_set_transport(NDhcp4ClientConfig *config, unsigned int transport); +void n_dhcp4_client_config_set_request_broadcast(NDhcp4ClientConfig *config, bool request_broadcast); +void n_dhcp4_client_config_set_mac(NDhcp4ClientConfig *config, const uint8_t *mac, size_t n_mac); +void n_dhcp4_client_config_set_broadcast_mac(NDhcp4ClientConfig *config, const uint8_t *mac, size_t n_mac); +int n_dhcp4_client_config_set_client_id(NDhcp4ClientConfig *config, const uint8_t *id, size_t n_id); + +/* client-probe configs */ + +int n_dhcp4_client_probe_config_new(NDhcp4ClientProbeConfig **configp); +NDhcp4ClientProbeConfig *n_dhcp4_client_probe_config_free(NDhcp4ClientProbeConfig *config); + +void n_dhcp4_client_probe_config_set_inform_only(NDhcp4ClientProbeConfig *config, bool inform_only); +void n_dhcp4_client_probe_config_set_init_reboot(NDhcp4ClientProbeConfig *config, bool init_reboot); +void n_dhcp4_client_probe_config_set_requested_ip(NDhcp4ClientProbeConfig *config, struct in_addr ip); +void n_dhcp4_client_probe_config_set_start_delay(NDhcp4ClientProbeConfig *config, uint64_t msecs); +void n_dhcp4_client_probe_config_request_option(NDhcp4ClientProbeConfig *config, uint8_t option); +int n_dhcp4_client_probe_config_append_option(NDhcp4ClientProbeConfig *config, + uint8_t option, + const void *data, + uint8_t n_data); + +/* clients */ + +int n_dhcp4_client_new(NDhcp4Client **clientp, NDhcp4ClientConfig *config); +NDhcp4Client *n_dhcp4_client_ref(NDhcp4Client *client); +NDhcp4Client *n_dhcp4_client_unref(NDhcp4Client *client); + +void n_dhcp4_client_get_fd(NDhcp4Client *client, int *fdp); +int n_dhcp4_client_dispatch(NDhcp4Client *client); +int n_dhcp4_client_pop_event(NDhcp4Client *client, NDhcp4ClientEvent **eventp); + +int n_dhcp4_client_update_mtu(NDhcp4Client *client, uint16_t mtu); + +int n_dhcp4_client_probe(NDhcp4Client *client, + NDhcp4ClientProbe **probep, + NDhcp4ClientProbeConfig *config); + +/* client probes */ + +NDhcp4ClientProbe *n_dhcp4_client_probe_free(NDhcp4ClientProbe *probe); + +void n_dhcp4_client_probe_set_userdata(NDhcp4ClientProbe *probe, void *userdata); +void n_dhcp4_client_probe_get_userdata(NDhcp4ClientProbe *probe, void **userdatap); + +/* client leases */ + +NDhcp4ClientLease *n_dhcp4_client_lease_ref(NDhcp4ClientLease *lease); +NDhcp4ClientLease *n_dhcp4_client_lease_unref(NDhcp4ClientLease *lease); + +void n_dhcp4_client_lease_get_yiaddr(NDhcp4ClientLease *lease, struct in_addr *yiaddr); +void n_dhcp4_client_lease_get_lifetime(NDhcp4ClientLease *lease, uint64_t *ns_lifetimep); +int n_dhcp4_client_lease_query(NDhcp4ClientLease *lease, uint8_t option, uint8_t **datap, size_t *n_datap); + +int n_dhcp4_client_lease_select(NDhcp4ClientLease *lease); +int n_dhcp4_client_lease_accept(NDhcp4ClientLease *lease); +int n_dhcp4_client_lease_decline(NDhcp4ClientLease *lease, const char *error); + +/* server configs */ + +int n_dhcp4_server_config_new(NDhcp4ServerConfig **configp); +NDhcp4ServerConfig *n_dhcp4_server_config_free(NDhcp4ServerConfig *config); + +void n_dhcp4_server_config_set_ifindex(NDhcp4ServerConfig *config, int ifindex); + +/* servers */ + +int n_dhcp4_server_new(NDhcp4Server **serverp, NDhcp4ServerConfig *config); +NDhcp4Server *n_dhcp4_server_ref(NDhcp4Server *server); +NDhcp4Server *n_dhcp4_server_unref(NDhcp4Server *server); + +void n_dhcp4_server_get_fd(NDhcp4Server *server, int *fdp); +int n_dhcp4_server_dispatch(NDhcp4Server *server); +int n_dhcp4_server_pop_event(NDhcp4Server *server, NDhcp4ServerEvent **eventp); + +int n_dhcp4_server_add_ip(NDhcp4Server *server, NDhcp4ServerIp **ipp, struct in_addr ip); + +/* server ip addresses */ + +NDhcp4ServerIp *n_dhcp4_server_ip_free(NDhcp4ServerIp *ip); + +/* server leases */ + +NDhcp4ServerLease *n_dhcp4_server_lease_ref(NDhcp4ServerLease *lease); +NDhcp4ServerLease *n_dhcp4_server_lease_unref(NDhcp4ServerLease *lease); + +int n_dhcp4_server_lease_query(NDhcp4ServerLease *lease, uint8_t option, uint8_t **datap, size_t *n_datap); +int n_dhcp4_server_lease_append(NDhcp4ServerLease *lease, uint8_t option, uint8_t *data, size_t n_data); + +int n_dhcp4_server_lease_offer(NDhcp4ServerLease *lease); +int n_dhcp4_server_lease_ack(NDhcp4ServerLease *lease); +int n_dhcp4_server_lease_nack(NDhcp4ServerLease *lease); + +/* inline helpers */ + +static inline void n_dhcp4_client_config_freep(NDhcp4ClientConfig **p) { + if (*p) + n_dhcp4_client_config_free(*p); +} + +static inline void n_dhcp4_client_config_freev(NDhcp4ClientConfig *p) { + n_dhcp4_client_config_free(p); +} + +static inline void n_dhcp4_client_probe_config_freep(NDhcp4ClientProbeConfig **p) { + if (*p) + n_dhcp4_client_probe_config_free(*p); +} + +static inline void n_dhcp4_client_probe_config_freev(NDhcp4ClientProbeConfig *p) { + n_dhcp4_client_probe_config_free(p); +} + +static inline void n_dhcp4_client_unrefp(NDhcp4Client **p) { + if (*p) + n_dhcp4_client_unref(*p); +} + +static inline void n_dhcp4_client_unrefv(NDhcp4Client *p) { + n_dhcp4_client_unref(p); +} + +static inline void n_dhcp4_client_probe_freep(NDhcp4ClientProbe **p) { + if (*p) + n_dhcp4_client_probe_free(*p); +} + +static inline void n_dhcp4_client_probe_freev(NDhcp4ClientProbe *p) { + n_dhcp4_client_probe_free(p); +} + +static inline void n_dhcp4_client_lease_unrefp(NDhcp4ClientLease **p) { + if (*p) + n_dhcp4_client_lease_unref(*p); +} + +static inline void n_dhcp4_client_lease_unrefv(NDhcp4ClientLease *p) { + n_dhcp4_client_lease_unref(p); +} + +static inline void n_dhcp4_server_config_freep(NDhcp4ServerConfig **p) { + if (*p) + n_dhcp4_server_config_free(*p); +} + +static inline void n_dhcp4_server_config_freev(NDhcp4ServerConfig *p) { + n_dhcp4_server_config_free(p); +} + +static inline void n_dhcp4_server_unrefp(NDhcp4Server **p) { + if (*p) + n_dhcp4_server_unref(*p); +} + +static inline void n_dhcp4_server_unrefv(NDhcp4Server *p) { + n_dhcp4_server_unref(p); +} + +static inline void n_dhcp4_server_ip_freep(NDhcp4ServerIp **p) { + if (*p) + n_dhcp4_server_ip_free(*p); +} + +static inline void n_dhcp4_server_ip_freev(NDhcp4ServerIp *p) { + n_dhcp4_server_ip_free(p); +} + +static inline void n_dhcp4_server_lease_unrefp(NDhcp4ServerLease **p) { + if (*p) + n_dhcp4_server_lease_unref(*p); +} + +static inline void n_dhcp4_server_lease_unrefv(NDhcp4ServerLease *p) { + n_dhcp4_server_lease_unref(p); +} + +#ifdef __cplusplus +} +#endif diff --git a/shared/n-dhcp4/src/util/packet.c b/shared/n-dhcp4/src/util/packet.c new file mode 100644 index 00000000..38cb399d --- /dev/null +++ b/shared/n-dhcp4/src/util/packet.c @@ -0,0 +1,456 @@ +/* + * Packet Sockets + */ + +#include <assert.h> +#include <c-stdaux.h> +#include <endian.h> +#include <errno.h> +#include <linux/filter.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <linux/udp.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include "packet.h" + +/** + * packet_internet_checksum() - compute the internet checksum + * @data: the data to checksum + * @size: the length of @data in bytes + * + * Computes the internet checksum for a given blob according to RFC1071. + * + * The internet checksum is the one's complement of the one's complement sum of + * the 16-bit words of the data, padded with zero-bytes if the data does not + * end on a 16-bit boundary. + * + * Return: Checksum is returned. + */ +uint16_t packet_internet_checksum(const uint8_t *data, size_t size) { + uint64_t acc = 0; + uint32_t local; + + while (size >= sizeof(local)) { + memcpy(&local, data, sizeof(local)); + acc += local; + + data += sizeof(local); + size -= sizeof(local); + } + + if (size) { + local = 0; + memcpy(&local, data, size); + acc += local; + } + + while (acc >> 16) + acc = (acc & 0xffff) + (acc >> 16); + + return ~acc; +} + +/** + * packet_internet_checksum_udp() - compute the internet checkum for UDP packets + * @src_addr: source IP address + * @dst_addr: destination IP address + * @src_port: source port + * @dst_port: destination port + * @data: payload + * @size: length of payload in bytes + * @checksum: current checksum, or 0 + * + * Computes the internet checksum for a UDP packet, given the relevant IP and + * UDP header fields. + * + * Note that since a UDP packet contains the checksum itself, the resulting + * checksum will always be 0 (this fact is used to verify that a UDP packet is + * valid). + * Inversely, when calculating the checksum for outgoing packets, you have to + * specify 0 as @checksum, and this function will return the checksum for the + * caller to use for the packet. In this case, though, the caller must check + * whether the returned checksum might coincidentally be 0, in which case it + * must be flipped to -1 (0xffff), since 0 is not allowed as checksum in UDP + * packets, and -1 is arithmetically equivalent in the checksum calculation. + * + * Return: Checksum is returned. + */ +uint16_t packet_internet_checksum_udp(const struct in_addr *src_addr, + const struct in_addr *dst_addr, + uint16_t src_port, + uint16_t dst_port, + const uint8_t *data, + size_t size, + uint16_t checksum) { + struct { + uint32_t src; + uint32_t dst; + uint8_t _zeros; + uint8_t protocol; + uint16_t length; + struct udphdr udp; + } _c_packed_ udp_phdr = { + .src = src_addr->s_addr, + .dst = dst_addr->s_addr, + .protocol = IPPROTO_UDP, + .length = htons(sizeof(struct udphdr) + size), + .udp = { + .source = htons(src_port), + .dest = htons(dst_port), + .len = htons(sizeof(struct udphdr) + size), + .check = checksum, + }, + }; + const uint8_t *iter; + uint64_t acc = 0; + uint32_t local; + + _Static_assert(!(sizeof(udp_phdr) % sizeof(local)), + "UDP header structure size is not a multiple of 4"); + + for (iter = (const uint8_t *)&udp_phdr; + iter < (const uint8_t *)(&udp_phdr + 1); + iter += sizeof(local)) { + memcpy(&local, iter, sizeof(local)); + acc += local; + } + + while (size >= sizeof(local)) { + memcpy(&local, data, sizeof(local)); + acc += local; + + data += sizeof(local); + size -= sizeof(local); + } + + if (size) { + local = 0; + memcpy(&local, data, size); + acc += local; + } + + while (acc >> 16) + acc = (acc & 0xffff) + (acc >> 16); + + return ~acc; +} + +/** + * packet_sendto_udp() - send UDP packet on AF_PACKET socket + * @sockfd: AF_PACKET/SOCK_DGRAM socket + * @buf: payload + * @n_buf: length of payload in bytes + * @n_transmittedp: output argument for number of transmitted bytes + * @src_paddr: source protocol address, see ip(7) + * @dest_haddr: destination hardware address, see packet(7) + * @dest_paddr: destination protocol address, see ip(7) + * + * Sends an UDP packet on a AF_PACKET socket directly to a hardware + * address. The difference between this and sendto() on an AF_INET + * socket is that no routing is performed, so the packet is delivered + * even if the destination IP is not yet configured on the destination + * host. + * + * Return: 0 on success, negative error code on failure. + */ +int packet_sendto_udp(int sockfd, + const void *buf, + size_t n_buf, + size_t *n_transmittedp, + const struct sockaddr_in *src_paddr, + const struct packet_sockaddr_ll *dest_haddr, + const struct sockaddr_in *dest_paddr) { + struct iphdr ip_hdr = { + .version = IPVERSION, + .ihl = sizeof(ip_hdr) / 4, /* Length of header in multiples of four bytes */ + .tos = IPTOS_CLASS_CS6, /* Class Selector for network control */ + .tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + n_buf), + .frag_off = htons(IP_DF), /* Do not fragment */ + .ttl = IPDEFTTL, + .protocol = IPPROTO_UDP, + .saddr = src_paddr->sin_addr.s_addr, + .daddr = dest_paddr->sin_addr.s_addr, + }; + struct udphdr udp_hdr = { + .source = src_paddr->sin_port, + .dest = dest_paddr->sin_port, + .len = htons(sizeof(udp_hdr) + n_buf), + }; + struct iovec iov[3] = { + { + .iov_base = &ip_hdr, + .iov_len = sizeof(ip_hdr), + }, + { + .iov_base = &udp_hdr, + .iov_len = sizeof(udp_hdr), + }, + { + .iov_base = (void *)buf, + .iov_len = n_buf, + }, + }; + struct msghdr msg = { + .msg_name = (void*)dest_haddr, + .msg_namelen = sizeof(*dest_haddr), + .msg_iov = iov, + .msg_iovlen = sizeof(iov) / sizeof(iov[0]), + }; + ssize_t pktlen; + + ip_hdr.check = packet_internet_checksum((void*)&ip_hdr, sizeof(ip_hdr)); + udp_hdr.check = packet_internet_checksum_udp(&src_paddr->sin_addr, + &dest_paddr->sin_addr, + ntohs(src_paddr->sin_port), + ntohs(dest_paddr->sin_port), + buf, + n_buf, + 0); + + /* + * 0x0000 and 0xffff are equivalent for computing the UDP checksum, + * but 0x0000 is reserved in UDP headers, to mean that the checksum is + * not set and should be ignored by the receiver. Hence, flip it to + * 0xffff in that case. + */ + udp_hdr.check = udp_hdr.check ?: 0xffff; + + pktlen = sendmsg(sockfd, &msg, 0); + if (pktlen < 0) + return -errno; + + /* + * Kernel never truncates. Worst case, we get -EMSGSIZE. Kernel *might* + * prepend VNET headers, in which case a bigger length than sent is + * returned. + * Lets assert on this, and then return to the caller the proportion of + * its own buffer that we sent (which is always exactly the requested + * size). + */ + c_assert((size_t)pktlen >= sizeof(ip_hdr) + sizeof(udp_hdr) + n_buf); + *n_transmittedp = n_buf; + return 0; +} + +/** + * packet_recvfrom_upd() - receive UDP packet from AF_PACKET socket + * @sockfd: AF_PACKET/SOCK_DGRAM socket + * @buf: buffor for payload + * @n_buf: max length of payload in bytes + * @n_transmittedp: output argument for number transmitted bytes + * @src: return argumnet for source address, or NULL, see ip(7) + * + * Receives an UDP packet on a AF_PACKET socket. The difference between + * this and recvfrom() on an AF_INET socket is that the packet will be + * received even if the destination IP address has not been configured + * on the interface. + * + * Return: 0 on success, negative error code on failure. + */ +int packet_recvfrom_udp(int sockfd, + void *buf, + size_t n_buf, + size_t *n_transmittedp, + struct sockaddr_in *src) { + union { + struct iphdr hdr; + /* + * Maximum IP-header length is 15 * 4, since it is specified in + * the `ihl` field, which is four bits and interpreted as + * factor of 4. So maximum `ihl` value is `(2^4 - 1) * 4`. + */ + uint8_t data[15 * 4]; + } ip_hdr; + struct udphdr udp_hdr; + struct iovec iov[3] = { + { + .iov_base = &ip_hdr, + }, + { + .iov_base = &udp_hdr, + .iov_len = sizeof(udp_hdr), + }, + { + .iov_base = buf, + .iov_len = n_buf, + }, + }; + uint8_t cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))]; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = sizeof(iov) / sizeof(iov[0]), + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf), + }; + struct cmsghdr *cmsg; + bool checksum = true; + ssize_t pktlen; + size_t hdrlen; + + /* Peek packet to obtain the real IP header length */ + pktlen = recv(sockfd, &ip_hdr.hdr, sizeof(ip_hdr.hdr), MSG_PEEK); + if (pktlen < 0) + return -errno; + + if ((size_t)pktlen < sizeof(ip_hdr.hdr)) { + /* + * Received packet is smaller than the minimal IP header length, + * discard it. + */ + recv(sockfd, NULL, 0, 0); + *n_transmittedp = 0; + return 0; + } + + if (ip_hdr.hdr.version != IPVERSION) { + /* + * This is not an IPv4 packet, discard it. + */ + recv(sockfd, NULL, 0, 0); + *n_transmittedp = 0; + return 0; + } + + hdrlen = ip_hdr.hdr.ihl * 4; + if (hdrlen < sizeof(ip_hdr.hdr)) { + /* + * The length given in the header is smaller than the minimum + * header length, discard the packet. + */ + recv(sockfd, NULL, 0, 0); + *n_transmittedp = 0; + return 0; + } + + /* + * Now that we know the ip-header length, we can prepare the iovec to + * read the entire packet into the correct buffers. + */ + iov[0].iov_len = hdrlen; + pktlen = recvmsg(sockfd, &msg, 0); + if (pktlen < 0) + return -errno; + + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg) { + if (cmsg->cmsg_level == SOL_PACKET && + cmsg->cmsg_type == PACKET_AUXDATA && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct tpacket_auxdata))) { + struct tpacket_auxdata *aux = (void *)CMSG_DATA(cmsg); + checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY); + } + } + + if (ntohs(ip_hdr.hdr.tot_len) > pktlen) { + /* + * The IP-packet is bigger than the chunk returned by the + * kernel. So either the packet is corrupt, or our caller + * provided too small a buffer. In both cases, we simply drop + * the packet. + */ + *n_transmittedp = 0; + return 0; + } + + /* Truncate trailing garbage. */ + pktlen = ntohs(ip_hdr.hdr.tot_len); + + if ((size_t)pktlen < hdrlen + sizeof(udp_hdr)) { + /* + * The packet is too small to even contain an entire UDP + * header, so discard it entirely. + */ + *n_transmittedp = 0; + return 0; + } else if ((size_t)pktlen < hdrlen + ntohs(udp_hdr.len)) { + /* + * The UDP header specified a longer length than the returned + * packet, so discard it entirely. + */ + *n_transmittedp = 0; + return 0; + } + + /* + * Make @pktlen the length of the packet payload, without IP/UDP + * headers, since that is what the caller is interested in. + */ + pktlen = ntohs(udp_hdr.len) - sizeof(struct udphdr); + + /* IP */ + + if (ip_hdr.hdr.protocol != IPPROTO_UDP) { + *n_transmittedp = 0; + return 0; /* not a UDP packet, discard it */ + } else if (ip_hdr.hdr.frag_off & htons(IP_MF | IP_OFFMASK)) { + *n_transmittedp = 0; + return 0; /* fragmented packet, discard it */ + } else if (checksum && packet_internet_checksum(ip_hdr.data, hdrlen)) { + *n_transmittedp = 0; + return 0; /* invalid checksum, discard it */ + } + + /* UDP */ + + if (checksum && udp_hdr.check) { + /* + * Computing the checksum of a packet that has the checksum set + * must yield 0. If it does not yield 0, the packet is invalid, + * in which case we discard it. + */ + if (packet_internet_checksum_udp(&(struct in_addr){ ip_hdr.hdr.saddr }, + &(struct in_addr){ ip_hdr.hdr.daddr }, + ntohs(udp_hdr.source), + ntohs(udp_hdr.dest), + buf, + pktlen, + udp_hdr.check)) { + *n_transmittedp = 0; + return 0; + } + } + + if (src) { + src->sin_family = AF_INET; + src->sin_addr.s_addr = ip_hdr.hdr.saddr; + src->sin_port = udp_hdr.source; + } + + /* Return length of UDP payload (i.e., data written to @buf). */ + *n_transmittedp = pktlen; + return 0; +} + +/** + * packet_shutdown() - shutdown socket for future receive operations + * @sockfd: socket + * + * Partially emulates `shutdown(sockfd, SHUT_RD)`, in the sense that no + * further packets may be queued on the socket. All packets that are + * already queued will still be delivered, but once -EAGAIN is returned + * we are guaranteed never to be able to read more packets in the future. + * + * Return: 0 on success, or a negative error code on failure. + */ +int packet_shutdown(int sockfd) { + struct sock_filter filter[] = { + BPF_STMT(BPF_RET + BPF_K, 0), /* discard all packets */ + }; + struct sock_fprog fprog = { + .filter = filter, + .len = sizeof(filter) / sizeof(filter[0]), + }; + int r; + + r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + return 0; +} diff --git a/shared/n-dhcp4/src/util/packet.h b/shared/n-dhcp4/src/util/packet.h new file mode 100644 index 00000000..98dabf7f --- /dev/null +++ b/shared/n-dhcp4/src/util/packet.h @@ -0,0 +1,61 @@ +#pragma once + +/* + * Packet Sockets + */ + +#include <c-stdaux.h> +#include <inttypes.h> +#include <linux/if_packet.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <unistd.h> + +/* + * `struct sockaddr_ll` is too small to fit the Infiniband hardware address. + * Introduce `struct packet_sockaddr_ll` which is the same as the original, + * except the `sl_addr` field is extended to fit all the supported hardware + * addresses. + */ +struct packet_sockaddr_ll { + unsigned short sll_family; + __be16 sll_protocol; + int sll_ifindex; + unsigned short sll_hatype; + unsigned char sll_pkttype; + unsigned char sll_halen; + unsigned char sll_addr[32]; /* MAX_ADDR_LEN */ +}; + +uint16_t packet_internet_checksum(const uint8_t *data, size_t len); +uint16_t packet_internet_checksum_udp(const struct in_addr *src_addr, + const struct in_addr *dst_addr, + uint16_t src_port, + uint16_t dst_port, + const uint8_t *data, + size_t size, + uint16_t checksum); + +int packet_sendto_udp(int sockfd, + const void *buf, + size_t n_buf, + size_t *n_transmittedp, + const struct sockaddr_in *src_paddr, + const struct packet_sockaddr_ll *dest_haddr, + const struct sockaddr_in *dest_paddr); +int packet_recvfrom_udp(int sockfd, + void *buf, + size_t n_buf, + size_t *n_transmittedp, + struct sockaddr_in *src); + +int packet_shutdown(int sockfd); + +/* inline helpers */ + +static inline int packet_recv_udp(int sockfd, + void *buf, + size_t n_buf, + size_t *n_transmittedp) { + return packet_recvfrom_udp(sockfd, buf, n_buf, n_transmittedp, NULL); +} diff --git a/shared/n-dhcp4/src/util/socket.c b/shared/n-dhcp4/src/util/socket.c new file mode 100644 index 00000000..c25f76fd --- /dev/null +++ b/shared/n-dhcp4/src/util/socket.c @@ -0,0 +1,121 @@ +/* + * Socket Utilities + */ + +#include <assert.h> +#include <c-stdaux.h> +#include <errno.h> +#include <net/if.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include "socket.h" + +/** + * socket_SIOCGIFNAME() - resolve an ifindex to an ifname + * @socket: socket to operate on + * @ifindex: index of network interface to resolve + * @ifname: buffer to store resolved name + * + * This uses the SIOCGIFNAME ioctl to resolve an ifindex to an ifname. The + * buffer provided in @ifnamep must be at least IFNAMSIZ bytes in size. The + * maximum ifname length is IFNAMSIZ-1, and this function always + * zero-terminates the result. + * + * This function is similar to if_indextoname(3) provided by glibc, but it + * allows to specify the target socket explicitly. This allows the caller to + * control the target network-namespace, rather than relying on the network + * namespace of the running process. + * + * Return: 0 on success, negative kernel error code on failure. + */ +int socket_SIOCGIFNAME(int socket, int ifindex, char (*ifnamep)[IFNAMSIZ]) { + struct ifreq req = { .ifr_ifindex = ifindex }; + int r; + + r = ioctl(socket, SIOCGIFNAME, &req); + if (r < 0) + return -errno; + + /* + * The linux kernel guarantees that an interface name is always + * zero-terminated, and it always fully fits into IFNAMSIZ bytes, + * including the zero-terminator. + */ + memcpy(ifnamep, req.ifr_name, IFNAMSIZ); + return 0; +} + +/** + * socket_bind_if() - bind socket to a network interface + * @socket: socket to operate on + * @ifindex: index of network interface to bind to, or 0 + * + * This binds the socket given via @socket to the network interface specified + * via @ifindex. It uses the underlying SO_BINDTODEVICE ioctl of the linux + * kernel. However, if available, if prefers the newer SO_BINDTOIFINDEX ioctl, + * which avoids resolving the interface name temporarily, and thus does not + * suffer from a race-condition. + * + * Return: 0 on success, negative error code on failure. + */ +int socket_bind_if(int socket, int ifindex) { + char ifname[IFNAMSIZ] = {}; + int r; + + c_assert(ifindex >= 0); + + /* + * We first try the newer SO_BINDTOIFINDEX. If it is not available on + * the running kernel, we fall back to SO_BINDTODEVICE. This, however, + * requires us to first resolve the ifindex to an ifname. Note that + * this is racy, since the device name might theoretically change + * asynchronously. + * + * Using 0 as ifindex will remove the device-binding. For + * SO_BINDTOIFINDEX we simply pass-through the 0 to the kernel, which + * recognizes this correctly. For SO_BINDTODEVICE we pass the empty + * string, which the kernel recognizes as a request to remove the + * binding. + * + * The commit introducing SO_BINDTOIFINDEX first appeared in linux-5.1: + * + * commit f5dd3d0c9638a9d9a02b5964c4ad636f06cf7e2c + * Author: David Herrmann <dh.herrmann@gmail.com> + * Date: Tue Jan 15 14:42:14 2019 +0100 + * + * net: introduce SO_BINDTOIFINDEX sockopt + * + * In older kernels, setsockopt(2) is guaranteed to return ENOPROTOOPT + * for this ioctl. + */ + +#ifdef SO_BINDTOIFINDEX + r = setsockopt(socket, + SOL_SOCKET, + SO_BINDTOIFINDEX, + &ifindex, + sizeof(ifindex)); + if (r >= 0) + return 0; + else if (errno != ENOPROTOOPT) + return -errno; +#endif /* SO_BINDTOIFINDEX */ + + if (ifindex > 0) { + r = socket_SIOCGIFNAME(socket, ifindex, &ifname); + if (r) + return r; + } + + r = setsockopt(socket, + SOL_SOCKET, + SO_BINDTODEVICE, + ifname, + strlen(ifname)); + if (r < 0) + return -errno; + + return 0; +} diff --git a/shared/n-dhcp4/src/util/socket.h b/shared/n-dhcp4/src/util/socket.h new file mode 100644 index 00000000..b5ecf2c2 --- /dev/null +++ b/shared/n-dhcp4/src/util/socket.h @@ -0,0 +1,11 @@ +#pragma once + +/* + * Socket Utilities + */ + +#include <c-stdaux.h> +#include <stdlib.h> + +int socket_SIOCGIFNAME(int socket, int ifindex, char (*ifnamep)[IFNAMSIZ]); +int socket_bind_if(int socket, int ifindex); diff --git a/shared/nm-default.h b/shared/nm-default.h index 54e99167..16a756c2 100644 --- a/shared/nm-default.h +++ b/shared/nm-default.h @@ -1,4 +1,3 @@ -/* -*- 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 @@ -30,8 +29,6 @@ #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) @@ -52,17 +49,6 @@ | 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 \ @@ -293,16 +279,11 @@ _nm_g_return_if_fail_warning (const char *log_domain, #include "nm-glib-aux/nm-macros-internal.h" #include "nm-glib-aux/nm-shared-utils.h" #include "nm-glib-aux/nm-errno.h" - -#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL -/* no hash-utils in legacy code. */ -#else #include "nm-glib-aux/nm-hash-utils.h" -#endif /*****************************************************************************/ -#if (NETWORKMANAGER_COMPILATION) & (NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE | NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_UTIL) +#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE #include "nm-version.h" #endif diff --git a/shared/nm-glib-aux/nm-c-list.h b/shared/nm-glib-aux/nm-c-list.h index 5c73f574..7512730d 100644 --- a/shared/nm-glib-aux/nm-c-list.h +++ b/shared/nm-glib-aux/nm-c-list.h @@ -1,4 +1,3 @@ -/* -*- 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 @@ -33,6 +32,8 @@ _what && c_list_contains (list, &_what->member); \ }) +/*****************************************************************************/ + typedef struct { CList lst; void *data; @@ -48,21 +49,34 @@ nm_c_list_elem_new_stale (void *data) return elem; } -static inline void * -nm_c_list_elem_get (CList *lst) +static inline gboolean +nm_c_list_elem_free_full (NMCListElem *elem, GDestroyNotify free_fcn) { - if (!lst) - return NULL; - return c_list_entry (lst, NMCListElem, lst)->data; + if (!elem) + return FALSE; + c_list_unlink_stale (&elem->lst); + if (free_fcn) + free_fcn (elem->data); + g_slice_free (NMCListElem, elem); + return TRUE; } -static inline void +static inline gboolean nm_c_list_elem_free (NMCListElem *elem) { - if (elem) { - c_list_unlink_stale (&elem->lst); - g_slice_free (NMCListElem, elem); - } + return nm_c_list_elem_free_full (elem, NULL); +} + +static inline void * +nm_c_list_elem_free_steal (NMCListElem *elem) +{ + gpointer data; + + if (!elem) + return NULL; + data = elem->data; + nm_c_list_elem_free_full (elem, NULL); + return data; } static inline void @@ -70,22 +84,53 @@ 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); + while ((elem = c_list_first_entry (head, NMCListElem, lst))) + nm_c_list_elem_free_full (elem, free_fcn); +} + +/** + * nm_c_list_elem_find_first: + * @head: the @CList head of a list containing #NMCListElem elements. + * Note that the head is not itself part of the list. + * @needle: the needle pointer. + * + * Iterates the list and returns the first #NMCListElem with the matching @needle, + * using pointer equality. + * + * Returns: the found list element or %NULL if not found. + */ +static inline NMCListElem * +nm_c_list_elem_find_first (CList *head, gconstpointer needle) +{ + NMCListElem *elem; + + c_list_for_each_entry (elem, head, lst) { + if (elem->data == needle) + return elem; } + return NULL; } /*****************************************************************************/ +/** + * nm_c_list_move_before: + * @lst: the list element to which @elem will be prepended. + * @elem: the list element to move. + * + * This unlinks @elem from the current list and linkes it before + * @lst. This is like c_list_link_before(), except that @elem must + * be initialized and linked. Note that @elem may be linked in @lst + * or in another list. In both cases it gets moved. + * + * Returns: %TRUE if there were any changes. %FALSE if elem was already + * linked at the right place. + */ static inline gboolean nm_c_list_move_before (CList *lst, CList *elem) { nm_assert (lst); nm_assert (elem); - nm_assert (c_list_contains (lst, elem)); if ( lst != elem && lst->prev != elem) { @@ -97,12 +142,24 @@ nm_c_list_move_before (CList *lst, CList *elem) } #define nm_c_list_move_tail(lst, elem) nm_c_list_move_before (lst, elem) +/** + * nm_c_list_move_after: + * @lst: the list element to which @elem will be prepended. + * @elem: the list element to move. + * + * This unlinks @elem from the current list and linkes it after + * @lst. This is like c_list_link_after(), except that @elem must + * be initialized and linked. Note that @elem may be linked in @lst + * or in another list. In both cases it gets moved. + * + * Returns: %TRUE if there were any changes. %FALSE if elem was already + * linked at the right place. + */ static inline gboolean nm_c_list_move_after (CList *lst, CList *elem) { nm_assert (lst); nm_assert (elem); - nm_assert (c_list_contains (lst, elem)); if ( lst != elem && lst->next != elem) { @@ -114,4 +171,14 @@ nm_c_list_move_after (CList *lst, CList *elem) } #define nm_c_list_move_front(lst, elem) nm_c_list_move_after (lst, elem) +#define nm_c_list_free_all(lst, type, member, destroy_fcn) \ + G_STMT_START { \ + CList *const _lst = (lst); \ + type *_elem; \ + \ + while ((_elem = c_list_first_entry (_lst, type, member))) { \ + destroy_fcn (_elem); \ + } \ + } G_STMT_END + #endif /* __NM_C_LIST_H__ */ diff --git a/shared/nm-glib-aux/nm-dbus-aux.c b/shared/nm-glib-aux/nm-dbus-aux.c new file mode 100644 index 00000000..083c4fee --- /dev/null +++ b/shared/nm-glib-aux/nm-dbus-aux.c @@ -0,0 +1,69 @@ +/* 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 2019 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dbus-aux.h" + +/*****************************************************************************/ + +static void +_nm_dbus_connection_call_get_name_owner_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + gs_unref_variant GVariant *ret = NULL; + gs_free_error GError *error = NULL; + const char *owner = NULL; + gpointer orig_user_data; + NMDBusConnectionCallGetNameOwnerCb callback; + + nm_utils_user_data_unpack (user_data, &orig_user_data, &callback); + + ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error); + if (ret) + g_variant_get (ret, "(&s)", &owner); + + callback (owner, error, orig_user_data); +} + +void +nm_dbus_connection_call_get_name_owner (GDBusConnection *dbus_connection, + const char *service_name, + int timeout_msec, + GCancellable *cancellable, + NMDBusConnectionCallGetNameOwnerCb callback, + gpointer user_data) +{ + nm_assert (callback); + + g_dbus_connection_call (dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetNameOwner", + g_variant_new ("(s)", service_name), + G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, + timeout_msec, + cancellable, + _nm_dbus_connection_call_get_name_owner_cb, + nm_utils_user_data_pack (user_data, callback)); +} diff --git a/shared/nm-glib-aux/nm-dbus-aux.h b/shared/nm-glib-aux/nm-dbus-aux.h new file mode 100644 index 00000000..271a7d9c --- /dev/null +++ b/shared/nm-glib-aux/nm-dbus-aux.h @@ -0,0 +1,102 @@ +/* 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 2019 Red Hat, Inc. + */ + +#ifndef __NM_DBUS_AUX_H__ +#define __NM_DBUS_AUX_H__ + +#include "nm-std-aux/nm-dbus-compat.h" + +/*****************************************************************************/ + +static inline gboolean +nm_clear_g_dbus_connection_signal (GDBusConnection *dbus_connection, + guint *id) +{ + guint v; + + if ( id + && (v = *id)) { + *id = 0; + g_dbus_connection_signal_unsubscribe (dbus_connection, v); + return TRUE; + } + return FALSE; +} + +/*****************************************************************************/ + +static inline void +nm_dbus_connection_call_start_service_by_name (GDBusConnection *dbus_connection, + const char *name, + int timeout_msec, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_dbus_connection_call (dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "StartServiceByName", + g_variant_new ("(su)", name, 0u), + G_VARIANT_TYPE ("(u)"), + G_DBUS_CALL_FLAGS_NONE, + timeout_msec, + cancellable, + callback, + user_data); +} + +/*****************************************************************************/ + +static inline guint +nm_dbus_connection_signal_subscribe_name_owner_changed (GDBusConnection *dbus_connection, + const char *service_name, + GDBusSignalCallback callback, + gpointer user_data, + GDestroyNotify user_data_free_func) + +{ + return g_dbus_connection_signal_subscribe (dbus_connection, + DBUS_SERVICE_DBUS, + DBUS_INTERFACE_DBUS, + "NameOwnerChanged", + DBUS_PATH_DBUS, + service_name, + G_DBUS_SIGNAL_FLAGS_NONE, + callback, + user_data, + user_data_free_func); +} + +typedef void (*NMDBusConnectionCallGetNameOwnerCb) (const char *name_owner, + GError *error, + gpointer user_data); + +void nm_dbus_connection_call_get_name_owner (GDBusConnection *dbus_connection, + const char *service_name, + int timeout_msec, + GCancellable *cancellable, + NMDBusConnectionCallGetNameOwnerCb callback, + gpointer user_data); + +/*****************************************************************************/ + +#endif /* __NM_DBUS_AUX_H__ */ diff --git a/shared/nm-glib-aux/nm-dedup-multi.c b/shared/nm-glib-aux/nm-dedup-multi.c index 5bdc3e3c..345062ca 100644 --- a/shared/nm-glib-aux/nm-dedup-multi.c +++ b/shared/nm-glib-aux/nm-dedup-multi.c @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-glib-aux/nm-dedup-multi.h b/shared/nm-glib-aux/nm-dedup-multi.h index 82c6f1e9..ca15c516 100644 --- a/shared/nm-glib-aux/nm-dedup-multi.h +++ b/shared/nm-glib-aux/nm-dedup-multi.h @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-glib-aux/nm-enum-utils.c b/shared/nm-glib-aux/nm-enum-utils.c index a4f6e809..b16267a5 100644 --- a/shared/nm-glib-aux/nm-enum-utils.c +++ b/shared/nm-glib-aux/nm-enum-utils.c @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-glib-aux/nm-enum-utils.h b/shared/nm-glib-aux/nm-enum-utils.h index 1827fdf4..20db07cc 100644 --- a/shared/nm-glib-aux/nm-enum-utils.h +++ b/shared/nm-glib-aux/nm-enum-utils.h @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-glib-aux/nm-glib.h b/shared/nm-glib-aux/nm-glib.h index e941e067..bdb7ea5b 100644 --- a/shared/nm-glib-aux/nm-glib.h +++ b/shared/nm-glib-aux/nm-glib.h @@ -1,4 +1,3 @@ -/* -*- 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 @@ -523,10 +522,18 @@ _nm_g_variant_new_printf (const char *format_string, ...) /*****************************************************************************/ -#if !GLIB_CHECK_VERSION (2, 56, 0) +/* Recent glib also casts the results to typeof(Obj), but only if + * + * ( defined(g_has_typeof) && GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_56 ) + * + * Since we build NetworkManager with older GLIB_VERSION_MAX_ALLOWED, it's + * not taking effect. + * + * Override this. */ +#undef g_object_ref +#undef g_object_ref_sink #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 /*****************************************************************************/ diff --git a/shared/nm-glib-aux/nm-hash-utils.c b/shared/nm-glib-aux/nm-hash-utils.c index 6e728e6b..a6158269 100644 --- a/shared/nm-glib-aux/nm-hash-utils.c +++ b/shared/nm-glib-aux/nm-hash-utils.c @@ -1,4 +1,3 @@ -/* -*- 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 @@ -45,7 +44,10 @@ _get_hash_key_init (void) * to use it as guint* or guint64* pointer. */ static union { guint8 v8[HASH_KEY_SIZE]; - } g_arr _nm_alignas (guint64); + guint _align_as_uint; + guint32 _align_as_uint32; + guint64 _align_as_uint64; + } g_arr; const guint8 *g; union { guint8 v8[HASH_KEY_SIZE]; @@ -125,14 +127,17 @@ void nm_hash_siphash42_init (CSipHash *h, guint static_seed) { const guint8 *g; - guint seed[HASH_KEY_SIZE_GUINT]; + union { + guint64 _align_as_uint64; + guint arr[HASH_KEY_SIZE_GUINT]; + } seed; nm_assert (h); g = _get_hash_key (); - memcpy (seed, g, HASH_KEY_SIZE); - seed[0] ^= static_seed; - c_siphash_init (h, (const guint8 *) seed); + memcpy (&seed, g, HASH_KEY_SIZE); + seed.arr[0] ^= static_seed; + c_siphash_init (h, (const guint8 *) &seed); } guint @@ -194,3 +199,25 @@ nm_pstr_equal (gconstpointer a, gconstpointer b) && s2 && nm_streq0 (*s1, *s2)); } + +guint +nm_pdirect_hash (gconstpointer p) +{ + const void *const*s = p; + + if (!s) + return nm_hash_static (1852748873u); + return nm_direct_hash (*s); +} + +gboolean +nm_pdirect_equal (gconstpointer a, gconstpointer b) +{ + const void *const*s1 = a; + const void *const*s2 = b; + + return (s1 == s2) + || ( s1 + && s2 + && *s1 == *s2); +} diff --git a/shared/nm-glib-aux/nm-hash-utils.h b/shared/nm-glib-aux/nm-hash-utils.h index 3f622f99..f13e0b6d 100644 --- a/shared/nm-glib-aux/nm-hash-utils.h +++ b/shared/nm-glib-aux/nm-hash-utils.h @@ -1,4 +1,3 @@ -/* -*- 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 @@ -34,7 +33,7 @@ void nm_hash_siphash42_init (CSipHash *h, guint static_seed); * * Note, that this is guaranteed to use siphash42 under the hood (contrary to * all other NMHash API, which leave this undefined). That matters at the point, - * where the caller needs to be sure that a reasonably strong hasing algorithm + * where the caller needs to be sure that a reasonably strong hashing algorithm * is used. (Yes, NMHash is all about siphash24, but otherwise that is not promised * anywhere). * @@ -291,7 +290,16 @@ gboolean nm_pstr_equal (gconstpointer a, gconstpointer b); /*****************************************************************************/ -#define NM_HASH_OBFUSCATE_PTR_FMT "%016llx" +/* this hashes/compares the pointer value that we point to. Basically, + * (((const void *const*) a) == ((const void *const*) b)). */ + +guint nm_pdirect_hash (gconstpointer p); + +gboolean nm_pdirect_equal (gconstpointer a, gconstpointer b); + +/*****************************************************************************/ + +#define NM_HASH_OBFUSCATE_PTR_FMT "%016" G_GINT64_MODIFIER "x" /* sometimes we want to log a pointer directly, for providing context/information about * the message that get logged. Logging pointer values directly defeats ASLR, so we should @@ -307,9 +315,19 @@ gboolean nm_pstr_equal (gconstpointer a, gconstpointer b); \ nm_hash_init (&_h, (static_seed)); \ nm_hash_update_val (&_h, _val_obf_ptr); \ - (unsigned long long) nm_hash_complete_u64 (&_h); \ + nm_hash_complete_u64 (&_h); \ }) +/* if you want to log obfuscated pointer for a certain context (like, NMPRuleManager + * logging user-tags), then you are advised to use nm_hash_obfuscate_ptr() with your + * own, unique static-seed. + * + * However, for example the singleton constructors log the obfuscated pointer values + * for all singletons, so they must all be obfuscated with the same seed. So, this + * macro uses a particular static seed that should be used by when comparing pointer + * values in a global context. */ +#define NM_HASH_OBFUSCATE_PTR(ptr) (nm_hash_obfuscate_ptr (1678382159u, ptr)) + /*****************************************************************************/ #endif /* __NM_HASH_UTILS_H__ */ diff --git a/shared/nm-glib-aux/nm-io-utils.c b/shared/nm-glib-aux/nm-io-utils.c index 51312748..23133ec5 100644 --- a/shared/nm-glib-aux/nm-io-utils.c +++ b/shared/nm-glib-aux/nm-io-utils.c @@ -1,4 +1,3 @@ -/* -*- 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 @@ -437,3 +436,26 @@ nm_utils_file_set_contents (const char *filename, return TRUE; } + +/** + * nm_utils_file_stat: + * @filename: the filename to stat. + * @out_st: (allow-none) (out): if given, this will be passed to stat(). + * + * Just wraps stat() and gives the errno number as function result instead + * of setting the errno (though, errno is also set). It's only for convenience + * with + * + * if (nm_utils_file_stat (filename, NULL) == -ENOENT) { + * } + * + * Returns: 0 on success a negative errno on failure. */ +int +nm_utils_file_stat (const char *filename, struct stat *out_st) +{ + struct stat st; + + if (stat (filename, out_st ?: &st) != 0) + return -NM_ERRNO_NATIVE (errno); + return 0; +} diff --git a/shared/nm-glib-aux/nm-io-utils.h b/shared/nm-glib-aux/nm-io-utils.h index dc72a2a6..121fc481 100644 --- a/shared/nm-glib-aux/nm-io-utils.h +++ b/shared/nm-glib-aux/nm-io-utils.h @@ -1,4 +1,3 @@ -/* -*- 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 @@ -60,4 +59,8 @@ gboolean nm_utils_file_set_contents (const char *filename, mode_t mode, GError **error); +struct stat; + +int nm_utils_file_stat (const char *filename, struct stat *out_st); + #endif /* __NM_IO_UTILS_H__ */ diff --git a/shared/nm-glib-aux/nm-jansson.h b/shared/nm-glib-aux/nm-jansson.h index 5a73231f..d4642319 100644 --- a/shared/nm-glib-aux/nm-jansson.h +++ b/shared/nm-glib-aux/nm-jansson.h @@ -1,4 +1,3 @@ -/* -*- 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 @@ -44,6 +43,106 @@ NM_AUTO_DEFINE_FCN0 (json_t *, _nm_auto_decref_json, json_decref) #define nm_auto_decref_json nm_auto(_nm_auto_decref_json) +/*****************************************************************************/ + +static inline int +nm_jansson_json_as_bool (const json_t *elem, + bool *out_val) +{ + if (!elem) + return 0; + + if (!json_is_boolean (elem)) + return -EINVAL; + + NM_SET_OUT (out_val, json_boolean_value (elem)); + return 1; +} + +static inline int +nm_jansson_json_as_int32 (const json_t *elem, + gint32 *out_val) +{ + json_int_t v; + + if (!elem) + return 0; + + if (!json_is_integer (elem)) + return -EINVAL; + + v = json_integer_value (elem); + if ( v < (gint64) G_MININT32 + || v > (gint64) G_MAXINT32) + return -ERANGE; + + NM_SET_OUT (out_val, v); + return 1; +} + +static inline int +nm_jansson_json_as_int (const json_t *elem, + int *out_val) +{ + json_int_t v; + + if (!elem) + return 0; + + if (!json_is_integer (elem)) + return -EINVAL; + + v = json_integer_value (elem); + if ( v < (gint64) G_MININT + || v > (gint64) G_MAXINT) + return -ERANGE; + + NM_SET_OUT (out_val, v); + return 1; +} + +static inline int +nm_jansson_json_as_string (const json_t *elem, + const char **out_val) +{ + if (!elem) + return 0; + + if (!json_is_string (elem)) + return -EINVAL; + + NM_SET_OUT (out_val, json_string_value (elem)); + return 1; +} + +/*****************************************************************************/ + +#ifdef NM_VALUE_TYPE_DEFINE_FUNCTIONS +#include "nm-value-type.h" +static inline gboolean +nm_value_type_from_json (NMValueType value_type, + const json_t *elem, + gpointer out_val) +{ + switch (value_type) { + case NM_VALUE_TYPE_BOOL: return (nm_jansson_json_as_bool (elem, out_val) > 0); + case NM_VALUE_TYPE_INT32: return (nm_jansson_json_as_int32 (elem, out_val) > 0); + case NM_VALUE_TYPE_INT: return (nm_jansson_json_as_int (elem, out_val) > 0); + + /* warning: this overwrites/leaks the previous value. You better have *out_val + * point to uninitialized memory or NULL. */ + case NM_VALUE_TYPE_STRING: return (nm_jansson_json_as_string (elem, out_val) > 0); + + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached (); + return FALSE; +} +#endif + +/*****************************************************************************/ + #endif /* WITH_JANSON */ #endif /* __NM_JANSSON_H__ */ diff --git a/shared/nm-glib-aux/nm-json-aux.c b/shared/nm-glib-aux/nm-json-aux.c new file mode 100644 index 00000000..6f04ef2b --- /dev/null +++ b/shared/nm-glib-aux/nm-json-aux.c @@ -0,0 +1,149 @@ +/* + * 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. + * + * Copyright 2019 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-json-aux.h" + +/*****************************************************************************/ + +static void +_gstr_append_string_len (GString *gstr, + const char *str, + gsize len) +{ + g_string_append_c (gstr, '\"'); + + while (len > 0) { + gsize n; + const char *end; + gboolean valid; + + nm_assert (len > 0); + + valid = g_utf8_validate (str, len, &end); + + nm_assert ( end + && end >= str + && end <= &str[len]); + + if (end > str) { + const char *s; + + for (s = str; s < end; s++) { + nm_assert (s[0] != '\0'); + + if (s[0] < 0x20) { + const char *text; + + switch (s[0]) { + case '\\': text = "\\\\"; break; + case '\"': text = "\\\""; break; + case '\b': text = "\\b"; break; + case '\f': text = "\\f"; break; + case '\n': text = "\\n"; break; + case '\r': text = "\\r"; break; + case '\t': text = "\\t"; break; + default: + g_string_append_printf (gstr, "\\u%04X", (guint) s[0]); + continue; + } + g_string_append (gstr, text); + continue; + } + + if (NM_IN_SET (s[0], '\\', '\"')) + g_string_append_c (gstr, '\\'); + g_string_append_c (gstr, s[0]); + } + } else + nm_assert (!valid); + + if (valid) { + nm_assert (end == &str[len]); + break; + } + + nm_assert (end < &str[len]); + + if (end[0] == '\0') { + /* there is a NUL byte in the string. Technically this is valid UTF-8, so we + * encode it there. However, this will likely result in a truncated string when + * parsing. */ + g_string_append (gstr, "\\u0000"); + } else { + /* the character is not valid UTF-8. There is nothing we can do about it, because + * JSON can only contain UTF-8 and even the escape sequences can only escape Unicode + * codepoints (but not binary). + * + * The argument is not a a string (in any known encoding), hence we cannot represent + * it as a JSON string (which are unicode strings). + * + * Print an underscore instead of the invalid char :) */ + g_string_append_c (gstr, '_'); + } + + n = str - end; + nm_assert (n < len); + n++; + str += n; + len -= n; + } + + g_string_append_c (gstr, '\"'); +} + +void +nm_json_aux_gstr_append_string_len (GString *gstr, + const char *str, + gsize n) +{ + g_return_if_fail (gstr); + + _gstr_append_string_len (gstr, str, n); +} + +void +nm_json_aux_gstr_append_string (GString *gstr, + const char *str) +{ + g_return_if_fail (gstr); + + if (!str) + g_string_append (gstr, "null"); + else + _gstr_append_string_len (gstr, str, strlen (str)); +} + +void +nm_json_aux_gstr_append_obj_name (GString *gstr, + const char *key, + char start_container) +{ + g_return_if_fail (gstr); + g_return_if_fail (key); + + nm_json_aux_gstr_append_string (gstr, key); + + if (start_container != '\0') { + nm_assert (NM_IN_SET (start_container, '[', '{')); + g_string_append_printf (gstr, ": %c ", start_container); + } else + g_string_append (gstr, ": "); +} diff --git a/shared/nm-glib-aux/nm-json-aux.h b/shared/nm-glib-aux/nm-json-aux.h new file mode 100644 index 00000000..19d43ce4 --- /dev/null +++ b/shared/nm-glib-aux/nm-json-aux.h @@ -0,0 +1,83 @@ +/* + * 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. + * + * Copyright 2019 Red Hat, Inc. + */ + +#ifndef __NM_JSON_AUX_H__ +#define __NM_JSON_AUX_H__ + +/*****************************************************************************/ + +static inline GString * +nm_json_aux_gstr_append_delimiter (GString *gstr) +{ + g_string_append (gstr, ", "); + return gstr; +} + +void nm_json_aux_gstr_append_string_len (GString *gstr, + const char *str, + gsize n); + +void nm_json_aux_gstr_append_string (GString *gstr, + const char *str); + +static inline void +nm_json_aux_gstr_append_bool (GString *gstr, + gboolean v) +{ + g_string_append (gstr, v ? "true" : "false"); +} + +static inline void +nm_json_aux_gstr_append_int64 (GString *gstr, + gint64 v) +{ + g_string_append_printf (gstr, "%"G_GINT64_FORMAT, v); +} + +void nm_json_aux_gstr_append_obj_name (GString *gstr, + const char *key, + char start_container); + +/*****************************************************************************/ + +#ifdef NM_VALUE_TYPE_DEFINE_FUNCTIONS +#include "nm-value-type.h" +static inline void +nm_value_type_to_json (NMValueType value_type, + GString *gstr, + gconstpointer p_field) +{ + nm_assert (p_field); + nm_assert (gstr); + + switch (value_type) { + case NM_VALUE_TYPE_BOOL: nm_json_aux_gstr_append_bool (gstr, *((const bool *) p_field)); return; + case NM_VALUE_TYPE_INT32: nm_json_aux_gstr_append_int64 (gstr, *((const gint32 *) p_field)); return; + case NM_VALUE_TYPE_INT: nm_json_aux_gstr_append_int64 (gstr, *((const int *) p_field)); return; + case NM_VALUE_TYPE_STRING: nm_json_aux_gstr_append_string (gstr, *((const char *const *) p_field)); return; + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached (); +} +#endif + +/*****************************************************************************/ + +#endif /* __NM_JSON_AUX_H__ */ diff --git a/shared/nm-glib-aux/nm-keyfile-aux.c b/shared/nm-glib-aux/nm-keyfile-aux.c new file mode 100644 index 00000000..0257bcca --- /dev/null +++ b/shared/nm-glib-aux/nm-keyfile-aux.c @@ -0,0 +1,413 @@ +/* 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 2019 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-keyfile-aux.h" + +#include <syslog.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "nm-io-utils.h" + +/*****************************************************************************/ + +struct _NMKeyFileDB { + NMKeyFileDBLogFcn log_fcn; + NMKeyFileDBGotDirtyFcn got_dirty_fcn; + gpointer user_data; + const char *group_name; + GKeyFile *kf; + guint ref_count; + + bool is_started:1; + bool dirty:1; + bool destroyed:1; + + char filename[]; +}; + +#define _NMLOG(self, \ + syslog_level, \ + fmt, \ + ...) \ + G_STMT_START { \ + NMKeyFileDB *_self = (self); \ + \ + nm_assert (_self); \ + nm_assert (!_self->destroyed); \ + \ + if (_self->log_fcn) { \ + _self->log_fcn (_self, \ + (syslog_level), \ + _self->user_data, \ + ""fmt"", \ + ##__VA_ARGS__); \ + }; \ + } G_STMT_END + +#define _LOGD(...) _NMLOG (self, LOG_DEBUG, __VA_ARGS__) + +static gboolean +_IS_KEY_FILE_DB (NMKeyFileDB *self, gboolean require_is_started, gboolean allow_destroyed) +{ + if (self == NULL) + return FALSE; + if (self->ref_count <= 0) { + nm_assert_not_reached (); + return FALSE; + } + if ( require_is_started + && !self->is_started) + return FALSE; + if ( !allow_destroyed + && self->destroyed) + return FALSE; + return TRUE; +} + +/*****************************************************************************/ + +NMKeyFileDB * +nm_key_file_db_new (const char *filename, + const char *group_name, + NMKeyFileDBLogFcn log_fcn, + NMKeyFileDBGotDirtyFcn got_dirty_fcn, + gpointer user_data) +{ + NMKeyFileDB *self; + gsize l_filename; + gsize l_group; + + g_return_val_if_fail (filename && filename[0], NULL); + g_return_val_if_fail (group_name && group_name[0], NULL); + + l_filename = strlen (filename); + l_group = strlen (group_name); + + self = g_malloc0 (sizeof (NMKeyFileDB) + l_filename + 1 + l_group + 1); + self->ref_count = 1; + self->log_fcn = log_fcn; + self->got_dirty_fcn = got_dirty_fcn; + self->user_data = user_data; + self->kf = g_key_file_new (); + g_key_file_set_list_separator (self->kf, ','); + memcpy (self->filename, filename, l_filename + 1); + self->group_name = &self->filename[l_filename + 1]; + memcpy ((char *) self->group_name, group_name, l_group + 1); + + return self; +} + +NMKeyFileDB * +nm_key_file_db_ref (NMKeyFileDB *self) +{ + if (!self) + return NULL; + + g_return_val_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE), NULL); + + nm_assert (self->ref_count <= G_MAXUINT); + self->ref_count++; + return self; +} + +void +nm_key_file_db_unref (NMKeyFileDB *self) +{ + if (!self) + return; + + g_return_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE)); + + if (--self->ref_count > 0) + return; + + g_key_file_unref (self->kf); + + g_free (self); +} + +/* destroy() is like unref, but it also makes the instance unusable. + * All changes afterwards fail with an assertion. + * + * The point is that NMKeyFileDB is ref-counted in principle. But there + * is a primary owner who also provides the log_fcn(). + * + * When the primary owner goes out of scope and gives up the reference, it does + * not want to receive any log notifications anymore. + * + * The way NMKeyFileDB is intended to be used is in a very strict context: + * NMSettings owns the NMKeyFileDB instance and receives logging notifications. + * It's also the last one to persist the data to disk. Afterwards, no other user + * is supposed to be around and do anything with NMKeyFileDB. But since NMKeyFileDB + * is ref-counted it's hard to ensure that this is truly honored. So we start + * asserting at that point. + */ +void +nm_key_file_db_destroy (NMKeyFileDB *self) +{ + if (!self) + return; + + g_return_if_fail (_IS_KEY_FILE_DB (self, FALSE, FALSE)); + g_return_if_fail (!self->destroyed); + + self->destroyed = TRUE; + nm_key_file_db_unref (self); +} + +/*****************************************************************************/ + +/* nm_key_file_db_start() is supposed to be called right away, after creating the + * instance. + * + * It's not done as separate step after nm_key_file_db_new(), because we want to log, + * and the log_fcn returns the self pointer (which we should not expose before + * nm_key_file_db_new() returns. */ +void +nm_key_file_db_start (NMKeyFileDB *self) +{ + int r; + gs_free char *contents = NULL; + gsize contents_len; + gs_free_error GError *error = NULL; + + g_return_if_fail (_IS_KEY_FILE_DB (self, FALSE, FALSE)); + g_return_if_fail (!self->is_started); + + self->is_started = TRUE; + + r = nm_utils_file_get_contents (-1, + self->filename, + 20*1024*1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &contents, + &contents_len, + &error); + if (r < 0) { + _LOGD ("failed to read \"%s\": %s", self->filename, error->message); + return; + } + + if (!g_key_file_load_from_data (self->kf, + contents, + contents_len, + G_KEY_FILE_KEEP_COMMENTS, + &error)) { + _LOGD ("failed to load keyfile \"%s\": %s", self->filename, error->message); + return; + } + + _LOGD ("loaded keyfile-db for \"%s\"", self->filename); +} + +/*****************************************************************************/ + +const char * +nm_key_file_db_get_filename (NMKeyFileDB *self) +{ + g_return_val_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE), NULL); + + return self->filename; +} + +gboolean +nm_key_file_db_is_dirty (NMKeyFileDB *self) +{ + g_return_val_if_fail (_IS_KEY_FILE_DB (self, FALSE, TRUE), FALSE); + + return self->dirty; +} + +/*****************************************************************************/ + +char * +nm_key_file_db_get_value (NMKeyFileDB *self, + const char *key) +{ + g_return_val_if_fail (_IS_KEY_FILE_DB (self, TRUE, TRUE), NULL); + + return g_key_file_get_value (self->kf, self->group_name, key, NULL); +} + +char ** +nm_key_file_db_get_string_list (NMKeyFileDB *self, + const char *key, + gsize *out_len) +{ + g_return_val_if_fail (_IS_KEY_FILE_DB (self, TRUE, TRUE), NULL); + + return g_key_file_get_string_list (self->kf, self->group_name, key, out_len, NULL); +} + +/*****************************************************************************/ + +static void +_got_dirty (NMKeyFileDB *self, + const char *key) +{ + nm_assert (_IS_KEY_FILE_DB (self, TRUE, FALSE)); + nm_assert (!self->dirty); + + _LOGD ("updated entry for %s.%s", self->group_name, key); + + self->dirty = TRUE; + if (self->got_dirty_fcn) + self->got_dirty_fcn (self, self->user_data); +} + +/*****************************************************************************/ + +void +nm_key_file_db_remove_key (NMKeyFileDB *self, + const char *key) +{ + gboolean got_dirty = FALSE; + + g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE)); + + if (!key) + return; + + if (!self->dirty) { + gs_free_error GError *error = NULL; + + g_key_file_has_key (self->kf, self->group_name, key, &error); + got_dirty = (error != NULL); + } + g_key_file_remove_key (self->kf, self->group_name, key, NULL); + + if (got_dirty) + _got_dirty (self, key); +} + +void +nm_key_file_db_set_value (NMKeyFileDB *self, + const char *key, + const char *value) +{ + gs_free char *old_value = NULL; + gboolean got_dirty = FALSE; + + g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE)); + g_return_if_fail (key); + + if (!value) { + nm_key_file_db_remove_key (self, key); + return; + } + + if (!self->dirty) { + gs_free_error GError *error = NULL; + + old_value = g_key_file_get_value (self->kf, self->group_name, key, &error); + if (error) + got_dirty = TRUE; + } + + g_key_file_set_value (self->kf, self->group_name, key, value); + + if ( !self->dirty + && !got_dirty) { + gs_free_error GError *error = NULL; + gs_free char *new_value = NULL; + + new_value = g_key_file_get_value (self->kf, self->group_name, key, &error); + if ( error + || !new_value + || !nm_streq0 (old_value, new_value)) + got_dirty = TRUE; + } + + if (got_dirty) + _got_dirty (self, key); +} + +void +nm_key_file_db_set_string_list (NMKeyFileDB *self, + const char *key, + const char *const*value, + gssize len) +{ + gs_free char *old_value = NULL; + gboolean got_dirty = FALSE;; + + g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE)); + g_return_if_fail (key); + + if (!value) { + nm_key_file_db_remove_key (self, key); + return; + } + + if (!self->dirty) { + gs_free_error GError *error = NULL; + + old_value = g_key_file_get_value (self->kf, self->group_name, key, &error); + if (error) + got_dirty = TRUE; + } + + if (len < 0) + len = NM_PTRARRAY_LEN (value); + + g_key_file_set_string_list (self->kf, self->group_name, key, value, len); + + if ( !self->dirty + && !got_dirty) { + gs_free_error GError *error = NULL; + gs_free char *new_value = NULL; + + new_value = g_key_file_get_value (self->kf, self->group_name, key, &error); + if ( error + || !new_value + || !nm_streq0 (old_value, new_value)) + got_dirty = TRUE; + } + + if (got_dirty) + _got_dirty (self, key); +} + +/*****************************************************************************/ + +void +nm_key_file_db_to_file (NMKeyFileDB *self, + gboolean force) +{ + gs_free_error GError *error = NULL; + + g_return_if_fail (_IS_KEY_FILE_DB (self, TRUE, FALSE)); + + if ( !force + && !self->dirty) + return; + + self->dirty = FALSE; + + if (!g_key_file_save_to_file (self->kf, + self->filename, + &error)) { + _LOGD ("failure to write keyfile \"%s\": %s", self->filename, error->message); + } else + _LOGD ("write keyfile: \"%s\"", self->filename); +} diff --git a/shared/nm-glib-aux/nm-keyfile-aux.h b/shared/nm-glib-aux/nm-keyfile-aux.h new file mode 100644 index 00000000..8563f4d1 --- /dev/null +++ b/shared/nm-glib-aux/nm-keyfile-aux.h @@ -0,0 +1,78 @@ +/* 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 2019 Red Hat, Inc. + */ + +#ifndef __NM_KEYFILE_AUX_H__ +#define __NM_KEYFILE_AUX_H__ + +/*****************************************************************************/ + +typedef struct _NMKeyFileDB NMKeyFileDB; + +typedef void (*NMKeyFileDBLogFcn) (NMKeyFileDB *self, + int syslog_level, + gpointer user_data, + const char *fmt, + ...) G_GNUC_PRINTF (4, 5); + +typedef void (*NMKeyFileDBGotDirtyFcn) (NMKeyFileDB *self, + gpointer user_data); + +NMKeyFileDB *nm_key_file_db_new (const char *filename, + const char *group, + NMKeyFileDBLogFcn log_fcn, + NMKeyFileDBGotDirtyFcn got_dirty_fcn, + gpointer user_data); + +void nm_key_file_db_start (NMKeyFileDB *self); + +NMKeyFileDB *nm_key_file_db_ref (NMKeyFileDB *self); +void nm_key_file_db_unref (NMKeyFileDB *self); + +void nm_key_file_db_destroy (NMKeyFileDB *self); + +const char *nm_key_file_db_get_filename (NMKeyFileDB *self); + +gboolean nm_key_file_db_is_dirty (NMKeyFileDB *self); + +char *nm_key_file_db_get_value (NMKeyFileDB *self, + const char *key); + +char **nm_key_file_db_get_string_list (NMKeyFileDB *self, + const char *key, + gsize *out_len); + +void nm_key_file_db_remove_key (NMKeyFileDB *self, + const char *key); + +void nm_key_file_db_set_value (NMKeyFileDB *self, + const char *key, + const char *value); + +void nm_key_file_db_set_string_list (NMKeyFileDB *self, + const char *key, + const char *const*value, + gssize len); + +void nm_key_file_db_to_file (NMKeyFileDB *self, + gboolean force); + +/*****************************************************************************/ + +#endif /* __NM_KEYFILE_AUX_H__ */ diff --git a/shared/nm-glib-aux/nm-logging-fwd.h b/shared/nm-glib-aux/nm-logging-fwd.h index 900dfff8..c60a20b5 100644 --- a/shared/nm-glib-aux/nm-logging-fwd.h +++ b/shared/nm-glib-aux/nm-logging-fwd.h @@ -110,4 +110,31 @@ void _nm_log_impl (const char *file, const char *fmt, ...) _nm_printf (10, 11); +static inline NMLogLevel +nm_log_level_from_syslog (int syslog_level) +{ + switch (syslog_level) { + case 0 /* LOG_EMERG */ : return LOGL_ERR; + case 1 /* LOG_ALERT */ : return LOGL_ERR; + case 2 /* LOG_CRIT */ : return LOGL_ERR; + case 3 /* LOG_ERR */ : return LOGL_ERR; + case 4 /* LOG_WARNING */ : return LOGL_WARN; + case 5 /* LOG_NOTICE */ : return LOGL_INFO; + case 6 /* LOG_INFO */ : return LOGL_DEBUG; + case 7 /* LOG_DEBUG */ : return LOGL_TRACE; + default: + return syslog_level >= 0 ? LOGL_TRACE : LOGL_ERR; + } +} + +/*****************************************************************************/ + +struct timespec; + +/* this function must be implemented to handle the notification when + * the first monotonic-timestamp is fetched. */ +extern void _nm_utils_monotonic_timestamp_initialized (const struct timespec *tp, + gint64 offset_sec, + gboolean is_boottime); + #endif /* __NM_LOGGING_DEFINES_H__ */ diff --git a/shared/nm-glib-aux/nm-macros-internal.h b/shared/nm-glib-aux/nm-macros-internal.h index 2e46cd2d..9502c442 100644 --- a/shared/nm-glib-aux/nm-macros-internal.h +++ b/shared/nm-glib-aux/nm-macros-internal.h @@ -1,4 +1,3 @@ -/* -*- 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 @@ -635,8 +634,14 @@ NM_G_ERROR_MSG (GError *error) * It's useful to check the let the compiler ensure that @value is * of a certain type. */ #define _NM_ENSURE_TYPE(type, value) (_Generic ((value), type: (value))) +#define _NM_ENSURE_TYPE_CONST(type, value) (_Generic ((value), \ + const type : ((const type) (value)), \ + const type const: ((const type) (value)), \ + type : ((const type) (value)), \ + type const: ((const type) (value)))) #else #define _NM_ENSURE_TYPE(type, value) (value) +#define _NM_ENSURE_TYPE_CONST(type, value) ((const type) (value)) #endif #if _NM_CC_SUPPORT_GENERIC @@ -865,6 +870,28 @@ fcn (void) \ /*****************************************************************************/ +static inline int +nm_strcmp0 (const char *s1, const char *s2) +{ + int c; + + /* like g_strcmp0(), but this is inlinable. + * + * Also, it is guaranteed to return either -1, 0, or 1. */ + if (s1 == s2) + return 0; + if (!s1) + return -1; + if (!s2) + return 1; + c = strcmp (s1, s2); + if (c < 0) + return -1; + if (c > 0) + return 1; + return 0; +} + static inline gboolean nm_streq (const char *s1, const char *s2) { @@ -879,14 +906,19 @@ nm_streq0 (const char *s1, const char *s2) } #define NM_STR_HAS_PREFIX(str, prefix) \ - (strncmp ((str), ""prefix"", NM_STRLEN (prefix)) == 0) + ({ \ + const char *const _str = (str); \ + \ + _str && (strncmp ((str), ""prefix"", NM_STRLEN (prefix)) == 0); \ + }) #define NM_STR_HAS_SUFFIX(str, suffix) \ ({ \ - const char *_str = (str); \ - gsize _l = strlen (_str); \ + const char *_str; \ + gsize _l; \ \ - ( (_l >= NM_STRLEN (suffix)) \ + ( (_str = (str)) \ + && ((_l = strlen (_str)) >= NM_STRLEN (suffix)) \ && (memcmp (&_str[_l - NM_STRLEN (suffix)], \ ""suffix"", \ NM_STRLEN (suffix)) == 0)); \ @@ -989,10 +1021,9 @@ typedef enum { \ } _PropertyEnums; \ static GParamSpec *obj_properties[_PROPERTY_ENUMS_LAST] = { NULL, } -#define NM_GOBJECT_PROPERTIES_DEFINE(obj_type, ...) \ -NM_GOBJECT_PROPERTIES_DEFINE_BASE (__VA_ARGS__); \ +#define NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY(obj_type, obj_properties, property_enums_type, prop_0) \ static inline void \ -_nm_gobject_notify_together_impl (obj_type *obj, guint n, const _PropertyEnums *props) \ +_nm_gobject_notify_together_impl (obj_type *obj, guint n, const property_enums_type *props) \ { \ const gboolean freeze_thaw = (n > 1); \ \ @@ -1002,9 +1033,9 @@ _nm_gobject_notify_together_impl (obj_type *obj, guint n, const _PropertyEnums * if (freeze_thaw) \ g_object_freeze_notify ((GObject *) obj); \ while (n-- > 0) { \ - const _PropertyEnums prop = *props++; \ + const property_enums_type prop = *props++; \ \ - if (prop != PROP_0) { \ + if (prop != prop_0) { \ nm_assert ((gsize) prop < G_N_ELEMENTS (obj_properties)); \ nm_assert (obj_properties[prop]); \ g_object_notify_by_pspec ((GObject *) obj, obj_properties[prop]); \ @@ -1015,11 +1046,15 @@ _nm_gobject_notify_together_impl (obj_type *obj, guint n, const _PropertyEnums * } \ \ static inline void \ -_notify (obj_type *obj, _PropertyEnums prop) \ +_notify (obj_type *obj, property_enums_type prop) \ { \ _nm_gobject_notify_together_impl (obj, 1, &prop); \ } \ +#define NM_GOBJECT_PROPERTIES_DEFINE(obj_type, ...) \ +NM_GOBJECT_PROPERTIES_DEFINE_BASE (__VA_ARGS__); \ +NM_GOBJECT_PROPERTIES_DEFINE_NOTIFY (obj_type, obj_properties, _PropertyEnums, PROP_0) + /* invokes _notify() for all arguments (of type _PropertyEnums). Note, that if * there are more than one prop arguments, this will involve a freeze/thaw * of GObject property notifications. */ @@ -1130,6 +1165,30 @@ nm_g_object_unref (gpointer obj) #define nm_clear_g_object(pp) \ nm_clear_pointer (pp, g_object_unref) +/** + * nm_clear_error: + * @err: a pointer to pointer to a #GError. + * + * This is like g_clear_error(). The only difference is + * that this is an inline function. + */ +static inline void +nm_clear_error (GError **err) +{ + if (err && *err) { + g_error_free (*err); + *err = NULL; + } +} + +/* Patch g_clear_error() to use nm_clear_error(), which is inlineable + * and visible to the compiler. For example gs_free_error attribute only + * frees the error after checking that it's not %NULL. So, in many cases + * the compiler knows that gs_free_error has no effect and can optimize + * the call away. By making g_clear_error() inlineable, we give the compiler + * more chance to detect that the function actually has no effect. */ +#define g_clear_error(ptr) nm_clear_error(ptr) + static inline gboolean nm_clear_g_source (guint *id) { @@ -1219,6 +1278,14 @@ nm_g_variant_ref (GVariant *v) return v; } +static inline GVariant * +nm_g_variant_ref_sink (GVariant *v) +{ + if (v) + g_variant_ref_sink (v); + return v; +} + static inline void nm_g_variant_unref (GVariant *v) { @@ -1226,6 +1293,14 @@ nm_g_variant_unref (GVariant *v) g_variant_unref (v); } +static inline GVariant * +nm_g_variant_take_ref (GVariant *v) +{ + if (v) + g_variant_take_ref (v); + return v; +} + /*****************************************************************************/ /* Determine whether @x is a power of two (@x being an integer type). @@ -1494,6 +1569,11 @@ nm_strcmp_p (gconstpointer a, gconstpointer b) /*****************************************************************************/ +#define nm_g_slice_free(ptr) \ + g_slice_free (typeof (*(ptr)), ptr) + +/*****************************************************************************/ + /* like g_memdup(). The difference is that the @size argument is of type * gsize, while g_memdup() has type guint. Since, the size of container types * like GArray is guint as well, this means trying to g_memdup() an @@ -1523,15 +1603,72 @@ nm_memdup (gconstpointer data, gsize size) return p; } +#define nm_malloc_maybe_a(alloca_maxlen, bytes, to_free) \ + ({ \ + const gsize _bytes = (bytes); \ + typeof (to_free) _to_free = (to_free); \ + typeof (*_to_free) _ptr; \ + \ + G_STATIC_ASSERT_EXPR ((alloca_maxlen) <= 500); \ + nm_assert (_to_free && !*_to_free); \ + \ + if (_bytes <= (alloca_maxlen)) { \ + _ptr = g_alloca (_bytes); \ + } else { \ + _ptr = g_malloc (_bytes); \ + *_to_free = _ptr; \ + }; \ + \ + _ptr; \ + }) + +#define nm_malloc0_maybe_a(alloca_maxlen, bytes, to_free) \ + ({ \ + const gsize _bytes = (bytes); \ + typeof (to_free) _to_free = (to_free); \ + typeof (*_to_free) _ptr; \ + \ + G_STATIC_ASSERT_EXPR ((alloca_maxlen) <= 500); \ + nm_assert (_to_free && !*_to_free); \ + \ + if (_bytes <= (alloca_maxlen)) { \ + _ptr = g_alloca (_bytes); \ + memset (_ptr, 0, _bytes); \ + } else { \ + _ptr = g_malloc0 (_bytes); \ + *_to_free = _ptr; \ + }; \ + \ + _ptr; \ + }) + +#define nm_memdup_maybe_a(alloca_maxlen, data, size, to_free) \ + ({ \ + const gsize _size = (size); \ + typeof (to_free) _to_free_md = (to_free); \ + typeof (*_to_free_md) _ptr_md = NULL; \ + \ + nm_assert (_to_free_md && !*_to_free_md); \ + \ + if (_size > 0u) { \ + _ptr_md = nm_malloc_maybe_a ((alloca_maxlen), _size, _to_free_md); \ + memcpy (_ptr_md, (data), _size); \ + } \ + \ + _ptr_md; \ + }) + static inline char * _nm_strndup_a_step (char *s, const char *str, gsize len) { NM_PRAGMA_WARNING_DISABLE ("-Wstringop-truncation"); + NM_PRAGMA_WARNING_DISABLE ("-Wstringop-overflow"); if (len > 0) strncpy (s, str, len); s[len] = '\0'; return s; NM_PRAGMA_WARNING_REENABLE; + NM_PRAGMA_WARNING_REENABLE; } /* Similar to g_strndup(), however, if the string (including the terminating diff --git a/shared/nm-glib-aux/nm-obj.h b/shared/nm-glib-aux/nm-obj.h index 4edd1f3e..06016bdd 100644 --- a/shared/nm-glib-aux/nm-obj.h +++ b/shared/nm-glib-aux/nm-obj.h @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-glib-aux/nm-random-utils.c b/shared/nm-glib-aux/nm-random-utils.c index d7c7da42..f56f8b99 100644 --- a/shared/nm-glib-aux/nm-random-utils.c +++ b/shared/nm-glib-aux/nm-random-utils.c @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-glib-aux/nm-random-utils.h b/shared/nm-glib-aux/nm-random-utils.h index 15a118d3..8e134ee9 100644 --- a/shared/nm-glib-aux/nm-random-utils.h +++ b/shared/nm-glib-aux/nm-random-utils.h @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-glib-aux/nm-secret-utils.c b/shared/nm-glib-aux/nm-secret-utils.c index 81f8b5ae..aeb88877 100644 --- a/shared/nm-glib-aux/nm-secret-utils.c +++ b/shared/nm-glib-aux/nm-secret-utils.c @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-glib-aux/nm-secret-utils.h b/shared/nm-glib-aux/nm-secret-utils.h index 034ef7bd..0fd1ac8b 100644 --- a/shared/nm-glib-aux/nm-secret-utils.h +++ b/shared/nm-glib-aux/nm-secret-utils.h @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c index cf08a77f..c8a253a6 100644 --- a/shared/nm-glib-aux/nm-shared-utils.c +++ b/shared/nm-glib-aux/nm-shared-utils.c @@ -1,4 +1,3 @@ -/* -*- 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 @@ -734,10 +733,7 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma gint64 v; const char *s = NULL; - if (str) { - while (g_ascii_isspace (str[0])) - str++; - } + str = nm_str_skip_leading_spaces (str); if (!str || !str[0]) { errno = EINVAL; return fallback; @@ -748,9 +744,9 @@ _nm_utils_ascii_str_to_int64 (const char *str, guint base, gint64 min, gint64 ma if (errno != 0) return fallback; + if (s[0] != '\0') { - while (g_ascii_isspace (s[0])) - s++; + s = nm_str_skip_leading_spaces (s); if (s[0] != '\0') { errno = EINVAL; return fallback; @@ -810,6 +806,15 @@ _nm_utils_ascii_str_to_uint64 (const char *str, guint base, guint64 min, guint64 /*****************************************************************************/ +int +nm_strcmp_with_data (gconstpointer a, gconstpointer b, gpointer user_data) +{ + const char *s1 = a; + const char *s2 = 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 @@ -827,6 +832,15 @@ nm_strcmp_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data) } int +nm_strcmp0_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data) +{ + const char *s1 = *((const char **) a); + const char *s2 = *((const char **) b); + + return nm_strcmp0 (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); @@ -1350,6 +1364,8 @@ _nm_utils_strv_cleanup (char **strv, return strv; if (strip_whitespace) { + /* we only modify the strings pointed to by @strv if @strip_whitespace is + * requested. Otherwise, the strings themselves are untouched. */ for (i = 0; strv[i]; i++) g_strstrip (strv[i]); } @@ -2130,6 +2146,8 @@ 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) { @@ -2154,15 +2172,106 @@ nm_utils_named_values_from_str_dict (GHashTable *hash, guint *out_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_utils_named_value_list_sort (values, len, NULL, NULL); NM_SET_OUT (out_len, len); return values; } +gssize +nm_utils_named_value_list_find (const NMUtilsNamedValue *arr, + gsize len, + const char *name, + gboolean sorted) +{ + gsize i; + + nm_assert (name); + +#if NM_MORE_ASSERTS > 5 + { + for (i = 0; i < len; i++) { + const NMUtilsNamedValue *v = &arr[i]; + + nm_assert (v->name); + if ( sorted + && i > 0) + nm_assert (strcmp (arr[i - 1].name, v->name) < 0); + } + } + + nm_assert ( !sorted + || nm_utils_named_value_list_is_sorted (arr, len, FALSE, NULL, NULL)); +#endif + + if (sorted) { + return nm_utils_array_find_binary_search (arr, + sizeof (NMUtilsNamedValue), + len, + &name, + nm_strcmp_p_with_data, + NULL); + } + for (i = 0; i < len; i++) { + if (nm_streq (arr[i].name, name)) + return i; + } + return ~((gssize) len); +} + +gboolean +nm_utils_named_value_list_is_sorted (const NMUtilsNamedValue *arr, + gsize len, + gboolean accept_duplicates, + GCompareDataFunc compare_func, + gpointer user_data) +{ + gsize i; + int c_limit; + + if (len == 0) + return TRUE; + + g_return_val_if_fail (arr, FALSE); + + if (!compare_func) + compare_func = nm_strcmp_p_with_data; + + c_limit = accept_duplicates ? 0 : -1; + + for (i = 1; i < len; i++) { + int c; + + c = compare_func (&arr[i - 1], &arr[i], user_data); + if (c > c_limit) + return FALSE; + } + return TRUE; +} + +void +nm_utils_named_value_list_sort (NMUtilsNamedValue *arr, + gsize len, + GCompareDataFunc compare_func, + gpointer user_data) +{ + if (len == 0) + return; + + g_return_if_fail (arr); + + if (len == 1) + return; + + g_qsort_with_data (arr, + len, + sizeof (NMUtilsNamedValue), + compare_func ?: nm_strcmp_p_with_data, + user_data); +} + +/*****************************************************************************/ + gpointer * nm_utils_hash_keys_to_array (GHashTable *hash, GCompareDataFunc compare_func, @@ -2193,12 +2302,41 @@ nm_utils_hash_keys_to_array (GHashTable *hash, return keys; } +gboolean +nm_utils_hashtable_same_keys (const GHashTable *a, + const GHashTable *b) +{ + GHashTableIter h; + const char *k; + + if (a == b) + return TRUE; + if (!a || !b) + return FALSE; + if (g_hash_table_size ((GHashTable *) a) != g_hash_table_size ((GHashTable *) b)) + return FALSE; + + g_hash_table_iter_init (&h, (GHashTable *) a); + while (g_hash_table_iter_next (&h, (gpointer) &k, NULL)) { + if (!g_hash_table_contains ((GHashTable *) b, k)) + return FALSE; + } + +#if NM_MORE_ASSERTS > 5 + g_hash_table_iter_init (&h, (GHashTable *) b); + while (g_hash_table_iter_next (&h, (gpointer) &k, NULL)) + nm_assert (g_hash_table_contains ((GHashTable *) a, k)); +#endif + + return TRUE; +} + char ** nm_utils_strv_make_deep_copied (const char **strv) { gsize i; - /* it takes a strv dictionary, and copies each + /* it takes a strv list, and copies each * strings. Note that this updates @strv *in-place* * and returns it. */ @@ -2210,6 +2348,79 @@ nm_utils_strv_make_deep_copied (const char **strv) return (char **) strv; } +char ** +nm_utils_strv_make_deep_copied_n (const char **strv, gsize len) +{ + gsize i; + + /* it takes a strv array with len elements, and copies each + * strings. Note that this updates @strv *in-place* + * and returns it. */ + + if (!strv) + return NULL; + for (i = 0; i < len; i++) + strv[i] = g_strdup (strv[i]); + + return (char **) strv; +} + +/** + * @strv: the strv array to copy. It may be %NULL if @len + * is negative or zero (in which case %NULL will be returned). + * @len: the length of strings in @str. If negative, strv is assumed + * to be a NULL terminated array. + * + * Like g_strdupv(), with two differences: + * + * - accepts a @len parameter for non-null terminated strv array. + * + * - this never returns an empty strv array, but always %NULL if + * there are no strings. + * + * Note that if @len is non-negative, then it still must not + * contain any %NULL pointers within the first @len elements. + * Otherwise you would leak elements if you try to free the + * array with g_strfreev(). Allowing that would be error prone. + * + * Returns: (transfer full): a clone of the strv array. Always + * %NULL terminated. + */ +char ** +nm_utils_strv_dup (gpointer strv, gssize len) +{ + gsize i, l; + char **v; + const char *const *const src = strv; + + if (len < 0) + l = NM_PTRARRAY_LEN (src); + else + l = len; + if (l == 0) { + /* this function never returns an empty strv array. If you + * need that, handle it yourself. */ + return NULL; + } + + v = g_new (char *, l + 1); + for (i = 0; i < l; i++) { + + if (G_UNLIKELY (!src[i])) { + /* NULL strings are not allowed. Clear the remainder of the array + * and return it (with assertion failure). */ + l++; + for (; i < l; i++) + v[i] = NULL; + g_return_val_if_reached (v); + } + + v[i] = g_strdup (src[i]); + } + v[l] = NULL; + return v; +} + /*****************************************************************************/ gssize @@ -2499,8 +2710,8 @@ fail: * @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. + * number, @strv is allowed to contain %NULL strings + * too. * * Ascending sort of the array @strv inplace, using plain strcmp() string * comparison. @@ -2508,9 +2719,16 @@ fail: void _nm_utils_strv_sort (const char **strv, gssize len) { + GCompareDataFunc cmp; gsize l; - l = len < 0 ? (gsize) NM_PTRARRAY_LEN (strv) : (gsize) len; + if (len < 0) { + l = NM_PTRARRAY_LEN (strv); + cmp = nm_strcmp_p_with_data; + } else { + l = len; + cmp = nm_strcmp0_p_with_data; + } if (l <= 1) return; @@ -2520,7 +2738,7 @@ _nm_utils_strv_sort (const char **strv, gssize len) g_qsort_with_data (strv, l, sizeof (const char *), - nm_strcmp_p_with_data, + cmp, NULL); } @@ -2577,6 +2795,53 @@ _nm_utils_strv_cmp_n (const char *const*strv1, /*****************************************************************************/ +/** + * nm_utils_g_slist_find_str: + * @list: the #GSList with NUL terminated strings to search + * @needle: the needle string to look for. + * + * Search the list for @needle and return the first found match + * (or %NULL if not found). Uses strcmp() for finding the first matching + * element. + * + * Returns: the #GSList element with @needle as string value or + * %NULL if not found. + */ +GSList * +nm_utils_g_slist_find_str (const GSList *list, + const char *needle) +{ + nm_assert (needle); + + for (; list; list = list->next) { + nm_assert (list->data); + if (nm_streq (list->data, needle)) + return (GSList *) list; + } + return NULL; +} + +/** + * nm_utils_g_slist_strlist_cmp: + * @a: the left #GSList of strings + * @b: the right #GSList of strings to compare. + * + * Compares two string lists. The data elements are compared with + * strcmp(), alloing %NULL elements. + * + * Returns: 0, 1, or -1, depending on how the lists compare. + */ +int +nm_utils_g_slist_strlist_cmp (const GSList *a, const GSList *b) +{ + for (; a && b; a = a->next, b = b->next) + NM_CMP_DIRECT_STRCMP0 (a->data, b->data); + NM_CMP_SELF (a, b); + return 0; +} + +/*****************************************************************************/ + gpointer _nm_utils_user_data_pack (int nargs, gconstpointer *args) { @@ -2753,10 +3018,13 @@ nm_utils_memeqzero (gconstpointer data, gsize length) * be returned and must be freed by the caller. * If not %NULL, the buffer must already be preallocated and contain * at least (@length*2+1) or (@length*3) bytes, depending on the delimiter. + * If @length is zero, then of course at least one byte will be allocated + * or @out (if given) must contain at least room for the trailing NUL byte. * * Returns: the binary value converted to a hex string. If @out is given, * this always returns @out. If @out is %NULL, a newly allocated string - * is returned. + * is returned. This never returns %NULL, for buffers of length zero + * an empty string is returend. */ char * nm_utils_bin2hexstr_full (gconstpointer addr, @@ -2772,9 +3040,11 @@ nm_utils_bin2hexstr_full (gconstpointer addr, if (out) out0 = out; else { - out0 = out = g_new (char, delimiter == '\0' - ? length * 2 + 1 - : length * 3); + out0 = out = g_new (char, length == 0 + ? 1u + : ( delimiter == '\0' + ? length * 2u + 1u + : length * 3u)); } /* @out must contain at least @length*3 bytes if @delimiter is set, @@ -2939,3 +3209,64 @@ fail: NM_SET_OUT (out_len, 0); return NULL; } + +/*****************************************************************************/ + +GVariant * +nm_utils_gvariant_vardict_filter (GVariant *src, + gboolean (*filter_fcn) (const char *key, + GVariant *val, + char **out_key, + GVariant **out_val, + gpointer user_data), + gpointer user_data) +{ + GVariantIter iter; + GVariantBuilder builder; + const char *key; + GVariant *val; + + g_return_val_if_fail (src && g_variant_is_of_type (src, G_VARIANT_TYPE_VARDICT), NULL); + g_return_val_if_fail (filter_fcn, NULL); + + g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT); + + g_variant_iter_init (&iter, src); + while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) { + _nm_unused gs_unref_variant GVariant *val_free = val; + gs_free char *key2 = NULL; + gs_unref_variant GVariant *val2 = NULL; + + if (filter_fcn (key, + val, + &key2, + &val2, + user_data)) { + g_variant_builder_add (&builder, + "{sv}", + key2 ?: key, + val2 ?: val); + } + } + + return g_variant_builder_end (&builder); +} + +static gboolean +_gvariant_vardict_filter_drop_one (const char *key, + GVariant *val, + char **out_key, + GVariant **out_val, + gpointer user_data) +{ + return !nm_streq (key, user_data); +} + +GVariant * +nm_utils_gvariant_vardict_filter_drop_one (GVariant *src, + const char *key) +{ + return nm_utils_gvariant_vardict_filter (src, + _gvariant_vardict_filter_drop_one, + (gpointer) key); +} diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h index af3c2f83..d9c430d4 100644 --- a/shared/nm-glib-aux/nm-shared-utils.h +++ b/shared/nm-glib-aux/nm-shared-utils.h @@ -1,4 +1,3 @@ -/* -*- 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 @@ -171,6 +170,13 @@ nm_ip4_addr_is_localhost (in_addr_t addr4) return _cc < 0 ? -1 : 1; \ } G_STMT_END +#define NM_CMP_RETURN_DIRECT(c) \ + G_STMT_START { \ + const int _cc = (c); \ + if (_cc) \ + return _cc; \ + } G_STMT_END + #define NM_CMP_SELF(a, b) \ G_STMT_START { \ typeof (a) _a = (a); \ @@ -196,8 +202,11 @@ nm_ip4_addr_is_localhost (in_addr_t addr4) #define NM_CMP_DIRECT_MEMCMP(a, b, size) \ NM_CMP_RETURN (memcmp ((a), (b), (size))) +#define NM_CMP_DIRECT_STRCMP(a, b) \ + NM_CMP_RETURN_DIRECT (strcmp ((a), (b))) + #define NM_CMP_DIRECT_STRCMP0(a, b) \ - NM_CMP_RETURN (g_strcmp0 ((a), (b))) + NM_CMP_RETURN_DIRECT (nm_strcmp0 ((a), (b))) #define NM_CMP_DIRECT_IN6ADDR(a, b) \ G_STMT_START { \ @@ -229,16 +238,16 @@ nm_ip4_addr_is_localhost (in_addr_t addr4) const char *_b = ((b)->field); \ \ if (_a != _b) { \ - NM_CMP_RETURN (g_strcmp0 (_a, _b)); \ + NM_CMP_RETURN_DIRECT (nm_strcmp0 (_a, _b)); \ } \ } G_STMT_END #define NM_CMP_FIELD_STR0(a, b, field) \ - NM_CMP_RETURN (g_strcmp0 (((a)->field), ((b)->field))) + NM_CMP_RETURN_DIRECT (nm_strcmp0 (((a)->field), ((b)->field))) #define NM_CMP_FIELD_MEMCMP_LEN(a, b, field, len) \ NM_CMP_RETURN (memcmp (&((a)->field), &((b)->field), \ - MIN (len, sizeof ((a)->field)))) + NM_MIN (len, sizeof ((a)->field)))) #define NM_CMP_FIELD_MEMCMP(a, b, field) \ NM_CMP_RETURN (memcmp (&((a)->field), \ @@ -305,6 +314,20 @@ GVariant *nm_utils_gbytes_to_variant_ay (GBytes *bytes); /*****************************************************************************/ +GVariant *nm_utils_gvariant_vardict_filter (GVariant *src, + gboolean (*filter_fcn) (const char *key, + GVariant *val, + char **out_key, + GVariant **out_val, + gpointer user_data), + gpointer user_data); + +GVariant * +nm_utils_gvariant_vardict_filter_drop_one (GVariant *src, + const char *key); + +/*****************************************************************************/ + static inline int nm_utils_hexchar_to_int (char ch) { @@ -693,6 +716,8 @@ _nm_g_slice_free_fcn_define (16) * @NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY: the profile is currently not * available/compatible with the device, but this may be only temporary. * + * @NM_UTILS_ERROR_SETTING_MISSING: the setting is missing + * * @NM_UTILS_ERROR_INVALID_ARGUMENT: invalid argument. */ typedef enum { @@ -715,6 +740,8 @@ typedef enum { NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + NM_UTILS_ERROR_SETTING_MISSING, + } NMUtilsError; #define NM_UTILS_ERROR (nm_utils_error_quark ()) @@ -735,7 +762,13 @@ nm_utils_error_set_literal (GError **error, int error_code, const char *literal) } #define nm_utils_error_set(error, error_code, ...) \ - g_set_error ((error), NM_UTILS_ERROR, error_code, __VA_ARGS__) + G_STMT_START { \ + if (NM_NARG (__VA_ARGS__) == 1) { \ + g_set_error_literal ((error), NM_UTILS_ERROR, (error_code), _NM_UTILS_MACRO_FIRST (__VA_ARGS__)); \ + } else { \ + g_set_error ((error), NM_UTILS_ERROR, (error_code), __VA_ARGS__); \ + } \ + } G_STMT_END #define nm_utils_error_set_errno(error, errsv, fmt, ...) \ G_STMT_START { \ @@ -885,7 +918,9 @@ nm_utf8_collate0 (const char *a, const char *b) return g_utf8_collate (a, b); } +int nm_strcmp_with_data (gconstpointer a, gconstpointer b, gpointer user_data); int nm_strcmp_p_with_data (gconstpointer a, gconstpointer b, gpointer user_data); +int nm_strcmp0_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); @@ -906,11 +941,26 @@ typedef struct { }; } NMUtilsNamedValue; -#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); +gssize nm_utils_named_value_list_find (const NMUtilsNamedValue *arr, + gsize len, + const char *name, + gboolean sorted); + +gboolean nm_utils_named_value_list_is_sorted (const NMUtilsNamedValue *arr, + gsize len, + gboolean accept_duplicates, + GCompareDataFunc compare_func, + gpointer user_data); + +void nm_utils_named_value_list_sort (NMUtilsNamedValue *arr, + gsize len, + GCompareDataFunc compare_func, + gpointer user_data); + +/*****************************************************************************/ + gpointer *nm_utils_hash_keys_to_array (GHashTable *hash, GCompareDataFunc compare_func, gpointer user_data, @@ -927,14 +977,28 @@ nm_utils_strdict_get_keys (const GHashTable *hash, out_length); } +gboolean nm_utils_hashtable_same_keys (const GHashTable *a, + const GHashTable *b); + char **nm_utils_strv_make_deep_copied (const char **strv); +char **nm_utils_strv_make_deep_copied_n (const char **strv, gsize len); + 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); } +char **nm_utils_strv_dup (gpointer strv, gssize len); + +/*****************************************************************************/ + +GSList *nm_utils_g_slist_find_str (const GSList *list, + const char *needle); + +int nm_utils_g_slist_strlist_cmp (const GSList *a, const GSList *b); + /*****************************************************************************/ gssize nm_utils_ptrarray_find_binary_search (gconstpointer *list, diff --git a/shared/nm-glib-aux/nm-time-utils.c b/shared/nm-glib-aux/nm-time-utils.c index ae526c34..7735f29d 100644 --- a/shared/nm-glib-aux/nm-time-utils.c +++ b/shared/nm-glib-aux/nm-time-utils.c @@ -22,6 +22,8 @@ #include "nm-time-utils.h" +#include "nm-logging-fwd.h" + /*****************************************************************************/ typedef struct { @@ -229,15 +231,15 @@ nm_utils_get_monotonic_timestamp_s (void) /** * nm_utils_monotonic_timestamp_as_boottime: * @timestamp: the monotonic-timestamp that should be converted into CLOCK_BOOTTIME. - * @timestamp_ns_per_tick: How many nano seconds make one unit of @timestamp? E.g. if - * @timestamp is in unit seconds, pass %NM_UTILS_NS_PER_SECOND; @timestamp in nano - * seconds, pass 1; @timestamp in milli seconds, pass %NM_UTILS_NS_PER_SECOND/1000; etc. + * @timestamp_ns_per_tick: How many nanoseconds make one unit of @timestamp? E.g. if + * @timestamp is in unit seconds, pass %NM_UTILS_NS_PER_SECOND; if @timestamp is + * in nanoseconds, pass 1; if @timestamp is in milliseconds, pass %NM_UTILS_NS_PER_SECOND/1000. * * Returns: the monotonic-timestamp as CLOCK_BOOTTIME, as returned by clock_gettime(). - * The unit is the same as the passed in @timestamp basd on @timestamp_ns_per_tick. - * E.g. if you passed @timestamp in as seconds, it will return boottime in seconds. - * If @timestamp is a non-positive, it returns -1. Note that a (valid) monotonic-timestamp - * is always positive. + * The unit is the same as the passed in @timestamp based on @timestamp_ns_per_tick. + * E.g. if you passed @timestamp in as seconds, it will return boottime in seconds. + * If @timestamp is non-positive, it returns -1. Note that a (valid) monotonic-timestamp + * is always positive. * * On older kernels that don't support CLOCK_BOOTTIME, the returned time is instead CLOCK_MONOTONIC. **/ @@ -263,6 +265,8 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_ p = _t_get_global_state (); + nm_assert (p->offset_sec <= 0); + /* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */ offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick); @@ -271,3 +275,23 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_ return timestamp - offset; } + +gint64 +nm_utils_clock_gettime_ns (clockid_t clockid) +{ + struct timespec tp; + + if (clock_gettime (clockid, &tp) != 0) + return -NM_ERRNO_NATIVE (errno); + return nm_utils_timespec_to_ns (&tp); +} + +gint64 +nm_utils_clock_gettime_ms (clockid_t clockid) +{ + struct timespec tp; + + if (clock_gettime (clockid, &tp) != 0) + return -NM_ERRNO_NATIVE (errno); + return nm_utils_timespec_to_ms (&tp); +} diff --git a/shared/nm-glib-aux/nm-time-utils.h b/shared/nm-glib-aux/nm-time-utils.h index 7e4f4f25..52d6637d 100644 --- a/shared/nm-glib-aux/nm-time-utils.h +++ b/shared/nm-glib-aux/nm-time-utils.h @@ -21,6 +21,22 @@ #ifndef __NM_TIME_UTILS_H__ #define __NM_TIME_UTILS_H__ +#include <time.h> + +static inline gint64 +nm_utils_timespec_to_ns (const struct timespec *ts) +{ + return (((gint64) ts->tv_sec) * ((gint64) NM_UTILS_NS_PER_SECOND)) + + ((gint64) ts->tv_nsec); +} + +static inline gint64 +nm_utils_timespec_to_ms (const struct timespec *ts) +{ + return (((gint64) ts->tv_sec) * ((gint64) 1000)) + + (((gint64) ts->tv_nsec) / ((gint64) NM_UTILS_NS_PER_SECOND / 1000)); +} + gint64 nm_utils_get_monotonic_timestamp_ns (void); gint64 nm_utils_get_monotonic_timestamp_us (void); gint64 nm_utils_get_monotonic_timestamp_ms (void); @@ -34,12 +50,7 @@ nm_utils_get_monotonic_timestamp_ns_cached (gint64 *cache_now) ?: (*cache_now = nm_utils_get_monotonic_timestamp_ns ()); } -struct timespec; - -/* this function must be implemented to handle the notification when - * the first monotonic-timestamp is fetched. */ -extern void _nm_utils_monotonic_timestamp_initialized (const struct timespec *tp, - gint64 offset_sec, - gboolean is_boottime); +gint64 nm_utils_clock_gettime_ns (clockid_t clockid); +gint64 nm_utils_clock_gettime_ms (clockid_t clockid); #endif /* __NM_TIME_UTILS_H__ */ diff --git a/shared/nm-glib-aux/nm-value-type.h b/shared/nm-glib-aux/nm-value-type.h new file mode 100644 index 00000000..b4d6898f --- /dev/null +++ b/shared/nm-glib-aux/nm-value-type.h @@ -0,0 +1,208 @@ +/* + * 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. + * + * Copyright 2019 Red Hat, Inc. + */ + +#ifndef __NM_VALUE_TYPE_H__ +#define __NM_VALUE_TYPE_H__ + +typedef enum { + NM_VALUE_TYPE_UNSPEC = 1, + NM_VALUE_TYPE_BOOL = 2, + NM_VALUE_TYPE_INT32 = 3, + NM_VALUE_TYPE_INT = 4, + NM_VALUE_TYPE_STRING = 5, +} NMValueType; + +/*****************************************************************************/ + +#ifdef NM_VALUE_TYPE_DEFINE_FUNCTIONS + +typedef union { + bool v_bool; + gint32 v_int32; + int v_int; + const char *v_string; + + /* for convenience, also let the union contain other pointer types. These are + * for NM_VALUE_TYPE_UNSPEC. */ + gconstpointer *v_ptr; + const GPtrArray *v_ptrarray; + +} NMValueTypUnion; + +/* Set the NMValueTypUnion. You can also assign the member directly. + * The only purpose of this is that it also returns a pointer to the + * union. So, you can do + * + * ptr = NM_VALUE_TYP_UNION_SET (&value_typ_union_storage, v_bool, TRUE); + */ +#define NM_VALUE_TYP_UNION_SET(_arg, _type, _val) \ + ({ \ + NMValueTypUnion *const _arg2 = (_arg); \ + \ + *_arg2 = (NMValueTypUnion) { \ + ._type = (_val), \ + }; \ + _arg2; \ + }) + +typedef struct { + bool has; + NMValueTypUnion val; +} NMValueTypUnioMaybe; + +#define NM_VALUE_TYP_UNIO_MAYBE_SET(_arg, _type, _val) \ + ({ \ + NMValueTypUnioMaybe *const _arg2 = (_arg); \ + \ + *_arg2 = (NMValueTypUnioMaybe) { \ + .has = TRUE, \ + .val._type = (_val), \ + }; \ + _arg2; \ + }) + +/*****************************************************************************/ + +static inline int +nm_value_type_cmp (NMValueType value_type, + gconstpointer p_a, + gconstpointer p_b) +{ + switch (value_type) { + case NM_VALUE_TYPE_BOOL: NM_CMP_DIRECT (*((const bool *) p_a), *((const bool *) p_b)); return 0; + case NM_VALUE_TYPE_INT32: NM_CMP_DIRECT (*((const gint32 *) p_a), *((const gint32 *) p_b)); return 0; + case NM_VALUE_TYPE_INT: NM_CMP_DIRECT (*((const int *) p_a), *((const int *) p_b)); return 0; + case NM_VALUE_TYPE_STRING: return nm_strcmp0 (*((const char *const*) p_a), *((const char *const*) p_b)); + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached (); + return 0; +} + +static inline gboolean +nm_value_type_equal (NMValueType value_type, + gconstpointer p_a, + gconstpointer p_b) +{ + return nm_value_type_cmp (value_type, p_a, p_b) == 0; +} + +static inline void +nm_value_type_copy (NMValueType value_type, + gpointer dst, + gconstpointer src) +{ + switch (value_type) { + case NM_VALUE_TYPE_BOOL: (*((bool *) dst) = *((const bool *) src)); return; + case NM_VALUE_TYPE_INT32: (*((gint32 *) dst) = *((const gint32 *) src)); return; + case NM_VALUE_TYPE_INT: (*((int *) dst) = *((const int *) src)); return; + case NM_VALUE_TYPE_STRING: + /* self assignment safe! */ + if (*((char **) dst) != *((const char *const*) src)) { + g_free (*((char **) dst)); + *((char **) dst) = g_strdup (*((const char *const*) src)); + } + return; + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached (); +} + +static inline void +nm_value_type_get_from_variant (NMValueType value_type, + gpointer dst, + GVariant *variant, + gboolean clone) +{ + switch (value_type) { + case NM_VALUE_TYPE_BOOL: *((bool *) dst) = g_variant_get_boolean (variant); return; + case NM_VALUE_TYPE_INT32: *((gint32 *) dst) = g_variant_get_int32 (variant); return; + case NM_VALUE_TYPE_STRING: + if (clone) { + g_free (*((char **) dst)); + *((char **) dst) = g_variant_dup_string (variant, NULL); + } else { + /* we don't clone the string, nor free the previous value. */ + *((const char **) dst) = g_variant_get_string (variant, NULL); + } + return; + + case NM_VALUE_TYPE_INT: + /* "int" also does not have a define variant type, because it's not + * clear how many bits we would need. */ + + /* fall-through */ + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached (); +} + +static inline GVariant * +nm_value_type_to_variant (NMValueType value_type, + gconstpointer src) +{ + const char *v_string; + + switch (value_type) { + case NM_VALUE_TYPE_BOOL: return g_variant_new_boolean (*((const bool *) src)); + case NM_VALUE_TYPE_INT32: return g_variant_new_int32 (*((const gint32 *) src));; + case NM_VALUE_TYPE_STRING: + v_string = *((const char *const*) src); + return v_string ? g_variant_new_string (v_string) : NULL; + + case NM_VALUE_TYPE_INT: + /* "int" also does not have a define variant type, because it's not + * clear how many bits we would need. */ + + /* fall-through */ + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached (); + return NULL; +} + +static inline const GVariantType * +nm_value_type_get_variant_type (NMValueType value_type) +{ + switch (value_type) { + case NM_VALUE_TYPE_BOOL: return G_VARIANT_TYPE_BOOLEAN; + case NM_VALUE_TYPE_INT32: return G_VARIANT_TYPE_INT32; + case NM_VALUE_TYPE_STRING: return G_VARIANT_TYPE_STRING; + + case NM_VALUE_TYPE_INT: + /* "int" also does not have a define variant type, because it's not + * clear how many bits we would need. */ + + /* fall-through */ + case NM_VALUE_TYPE_UNSPEC: + break; + } + nm_assert_not_reached (); + return NULL; +} + +/*****************************************************************************/ + +#endif /* NM_VALUE_TYPE_DEFINE_FUNCTIONS */ + +#endif /* __NM_VALUE_TYPE_H__ */ diff --git a/shared/nm-libnm-core-aux/nm-dispatcher-api.h b/shared/nm-libnm-core-aux/nm-dispatcher-api.h index e6d0d92f..0ee0f0a8 100644 --- a/shared/nm-libnm-core-aux/nm-dispatcher-api.h +++ b/shared/nm-libnm-core-aux/nm-dispatcher-api.h @@ -1,4 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* NetworkManager -- Network link manager * * This program is free software; you can redistribute it and/or modify @@ -21,11 +20,6 @@ #ifndef __NM_DISPACHER_API_H__ #define __NM_DISPACHER_API_H__ -#define NMD_SCRIPT_DIR_DEFAULT NMCONFDIR "/dispatcher.d" -#define NMD_SCRIPT_DIR_PRE_UP NMD_SCRIPT_DIR_DEFAULT "/pre-up.d" -#define NMD_SCRIPT_DIR_PRE_DOWN NMD_SCRIPT_DIR_DEFAULT "/pre-down.d" -#define NMD_SCRIPT_DIR_NO_WAIT NMD_SCRIPT_DIR_DEFAULT "/no-wait.d" - #define NM_DISPATCHER_DBUS_SERVICE "org.freedesktop.nm_dispatcher" #define NM_DISPATCHER_DBUS_INTERFACE "org.freedesktop.nm_dispatcher" #define NM_DISPATCHER_DBUS_PATH "/org/freedesktop/nm_dispatcher" diff --git a/shared/nm-libnm-core-aux/nm-libnm-core-aux.c b/shared/nm-libnm-core-aux/nm-libnm-core-aux.c new file mode 100644 index 00000000..a04256b7 --- /dev/null +++ b/shared/nm-libnm-core-aux/nm-libnm-core-aux.c @@ -0,0 +1,373 @@ +/* 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 2019 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-libnm-core-aux.h" + +#include "nm-libnm-core-intern/nm-libnm-core-utils.h" + +/*****************************************************************************/ + +typedef enum { + KEY_TYPE_STRING, + KEY_TYPE_INT, + KEY_TYPE_BOOL, +} KeyType; + +typedef struct { + const char *str_val; + union { + int vint; + bool vbool; + } typ_val; +} ParseData; + +typedef struct { + const char *name; + NMTeamLinkWatcherType watcher_type; + KeyType key_type; + union { + int (*fint) (const NMTeamLinkWatcher *watcher); + gboolean (*fbool) (const NMTeamLinkWatcher *watcher); + const char *(*fstring) (const NMTeamLinkWatcher *watcher); + } get_fcn; + union { + int vint; + bool vbool; + } def_val; +} TeamLinkWatcherKeyInfo; + +static gboolean +_team_link_watcher_validate_active (const NMTeamLinkWatcher *watcher) +{ + return NM_FLAGS_HAS (nm_team_link_watcher_get_flags (watcher), NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE); +} + +static gboolean +_team_link_watcher_validate_inactive (const NMTeamLinkWatcher *watcher) +{ + return NM_FLAGS_HAS (nm_team_link_watcher_get_flags (watcher), NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE); +} + +static gboolean +_team_link_watcher_send_always (const NMTeamLinkWatcher *watcher) +{ + return NM_FLAGS_HAS (nm_team_link_watcher_get_flags (watcher), NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS); +} + +static const TeamLinkWatcherKeyInfo _team_link_watcher_key_infos[_NM_TEAM_LINK_WATCHER_KEY_NUM] = { + +#define _KEY_INFO(key_id, _name, _watcher_type, _key_type, ...) \ + [key_id] = { .name = ""_name"", .watcher_type = (_watcher_type), .key_type = _key_type, ##__VA_ARGS__ } + + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_NAME, "name", NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL | NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_STRING, .get_fcn.fstring = nm_team_link_watcher_get_name, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_DELAY_UP, "delay-up", NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_delay_up, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_DELAY_DOWN, "delay-down", NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_delay_down, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT, "init-wait", NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_init_wait, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_INTERVAL, "interval", NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_interval, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX, "missed-max", NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_missed_max, .def_val.vint = 3, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST, "target-host", NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_STRING, .get_fcn.fstring = nm_team_link_watcher_get_target_host, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_VLANID, "vlanid", NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_INT, .get_fcn.fint = nm_team_link_watcher_get_vlanid, .def_val.vint = -1, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_SOURCE_HOST, "source-host", NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_STRING, .get_fcn.fstring = nm_team_link_watcher_get_source_host, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_VALIDATE_ACTIVE, "validate-active", NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_BOOL, .get_fcn.fbool = _team_link_watcher_validate_active, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_VALIDATE_INACTIVE, "validate-inactive", NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_BOOL, .get_fcn.fbool = _team_link_watcher_validate_inactive, ), + _KEY_INFO (NM_TEAM_LINK_WATCHER_KEY_SEND_ALWAYS, "send-always", NM_TEAM_LINK_WATCHER_TYPE_ARPING, KEY_TYPE_BOOL, .get_fcn.fbool = _team_link_watcher_send_always, ), + +}; + +static NMTeamLinkWatcherType +_team_link_watcher_get_watcher_type_from_name (const char *name) +{ + if (name) { + if (nm_streq (name, NM_TEAM_LINK_WATCHER_ETHTOOL)) + return NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL; + if (nm_streq (name, NM_TEAM_LINK_WATCHER_NSNA_PING)) + return NM_TEAM_LINK_WATCHER_TYPE_NSNAPING; + if (nm_streq (name, NM_TEAM_LINK_WATCHER_ARP_PING)) + return NM_TEAM_LINK_WATCHER_TYPE_ARPING; + } + return NM_TEAM_LINK_WATCHER_TYPE_NONE; +} + +static const char * +_parse_data_get_str (const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM], + NMTeamLinkWatcherKeyId key_id) +{ + nm_assert (_NM_INT_NOT_NEGATIVE (key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM); + nm_assert (_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_STRING); + + return parse_data[key_id].str_val; +} + +static int +_parse_data_get_int (const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM], + NMTeamLinkWatcherKeyId key_id) +{ + nm_assert (_NM_INT_NOT_NEGATIVE (key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM); + nm_assert (_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_INT); + + if (parse_data[key_id].str_val) + return parse_data[key_id].typ_val.vint; + return _team_link_watcher_key_infos[key_id].def_val.vint; +} + +static int +_parse_data_get_bool (const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM], + NMTeamLinkWatcherKeyId key_id) +{ + nm_assert (_NM_INT_NOT_NEGATIVE (key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM); + nm_assert (_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_BOOL); + + if (parse_data[key_id].str_val) + return parse_data[key_id].typ_val.vbool; + return _team_link_watcher_key_infos[key_id].def_val.vbool; +} + +char * +nm_utils_team_link_watcher_to_string (const NMTeamLinkWatcher *watcher) +{ + nm_auto_free_gstring GString *str = NULL; + const char *name; + NMTeamLinkWatcherType watcher_type; + NMTeamLinkWatcherKeyId key_id; + + if (!watcher) + return NULL; + + str = g_string_new (NULL); + + name = nm_team_link_watcher_get_name (watcher); + g_string_append_printf (str, "name=%s", name ?: ""); + + watcher_type = _team_link_watcher_get_watcher_type_from_name (name); + + for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) { + const TeamLinkWatcherKeyInfo *info = &_team_link_watcher_key_infos[key_id]; + const char *vstr; + int vint; + bool vbool; + + nm_assert (info->name && info->name && NM_STRCHAR_ALL (info->name, ch,((ch >= 'a' && ch <= 'z') || NM_IN_SET (ch, '-')))); + nm_assert (NM_IN_SET (info->key_type, KEY_TYPE_STRING, + KEY_TYPE_INT, + KEY_TYPE_BOOL)); + + if (key_id == NM_TEAM_LINK_WATCHER_KEY_NAME) + continue; + + if (!NM_FLAGS_ALL (info->watcher_type, watcher_type)) + continue; + + switch (info->key_type) { + case KEY_TYPE_STRING: + vstr = info->get_fcn.fstring (watcher); + if (vstr) { + g_string_append_printf (nm_gstring_add_space_delimiter (str), + "%s=%s", info->name, vstr); + } + break; + case KEY_TYPE_INT: + vint = info->get_fcn.fint (watcher); + if (vint != info->def_val.vint) { + g_string_append_printf (nm_gstring_add_space_delimiter (str), + "%s=%d", info->name, vint); + } + break; + case KEY_TYPE_BOOL: + vbool = info->get_fcn.fbool (watcher); + if (vbool != info->def_val.vbool) { + g_string_append_printf (nm_gstring_add_space_delimiter (str), + "%s=%s", info->name, vbool ? "true" : "false"); + } + break; + } + } + + return g_string_free (g_steal_pointer (&str), FALSE); +} + +NMTeamLinkWatcher * +nm_utils_team_link_watcher_from_string (const char *str, + GError **error) +{ + gs_free const char **tokens = NULL; + ParseData parse_data[_NM_TEAM_LINK_WATCHER_KEY_NUM] = { }; + NMTeamLinkWatcherType watcher_type; + NMTeamLinkWatcherKeyId key_id; + gsize i_token; + NMTeamLinkWatcher *watcher; + int errsv; + + g_return_val_if_fail (str, NULL); + g_return_val_if_fail (!error || !*error, NULL); + + tokens = nm_utils_escaped_tokens_split (str, NM_ASCII_SPACES); + if (!tokens) { + g_set_error (error, 1, 0, "'%s' is not valid", str); + return NULL; + } + + for (i_token = 0; tokens[i_token]; i_token++) { + const TeamLinkWatcherKeyInfo *info; + const char *key = tokens[i_token]; + const char *val; + + val = strchr (key, '='); + if (!val) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("'%s' is not valid: properties should be specified as 'key=value'"), + key); + return NULL; + } + ((char *) val)[0] = '\0'; + val++; + + for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) { + info = &_team_link_watcher_key_infos[key_id]; + if (nm_streq (key, info->name)) + break; + } + + if (key_id == _NM_TEAM_LINK_WATCHER_KEY_NUM) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("'%s' is not a valid key"), key); + return NULL; + } + + if (parse_data[key_id].str_val) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("duplicate key '%s'"), key); + return NULL; + } + + parse_data[key_id].str_val = val; + + if (info->key_type == KEY_TYPE_INT) { + gint64 v64; + + v64 = _nm_utils_ascii_str_to_int64 (val, 10, G_MININT, G_MAXINT, G_MAXINT64); + if ( v64 == G_MAXINT64 + && ((errsv = errno) != 0)) { + if (errsv == ERANGE) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("number for '%s' is out of range"), key); + } else { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("value for '%s' must be a number"), key); + } + return NULL; + } + parse_data[key_id].typ_val.vint = v64; + } else if (info->key_type == KEY_TYPE_BOOL) { + int vbool; + + vbool = _nm_utils_ascii_str_to_bool (val, -1); + if (vbool == -1) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("value for '%s' must be a boolean"), key); + return NULL; + } + parse_data[key_id].typ_val.vbool = vbool; + } + } + + if (!parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("missing 'name' attribute")); + return NULL; + } + + watcher_type = _team_link_watcher_get_watcher_type_from_name (parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val); + if (watcher_type == NM_TEAM_LINK_WATCHER_TYPE_NONE) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("invalid 'name' \"%s\""), + parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val); + return NULL; + } + + for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) { + const TeamLinkWatcherKeyInfo *info = &_team_link_watcher_key_infos[key_id]; + + if (!parse_data[key_id].str_val) + continue; + if (!NM_FLAGS_ALL (info->watcher_type, watcher_type)) { + nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, + _("attribute '%s' is invalid for \"%s\""), + info->name, + parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val); + return NULL; + } + } + + switch (watcher_type) { + case NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL: + watcher = nm_team_link_watcher_new_ethtool (_parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_DELAY_UP), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_DELAY_DOWN), + error); + break; + case NM_TEAM_LINK_WATCHER_TYPE_NSNAPING: + watcher = nm_team_link_watcher_new_nsna_ping (_parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INTERVAL), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX), + _parse_data_get_str (parse_data, NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST), + error); + break; + default: + nm_assert (watcher_type == NM_TEAM_LINK_WATCHER_TYPE_ARPING); + watcher = nm_team_link_watcher_new_arp_ping2 (_parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_INTERVAL), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX), + _parse_data_get_int (parse_data, NM_TEAM_LINK_WATCHER_KEY_VLANID), + _parse_data_get_str (parse_data, NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST), + _parse_data_get_str (parse_data, NM_TEAM_LINK_WATCHER_KEY_SOURCE_HOST), + ( NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE + | (_parse_data_get_bool (parse_data, NM_TEAM_LINK_WATCHER_KEY_VALIDATE_ACTIVE) ? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE : NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE) + | (_parse_data_get_bool (parse_data, NM_TEAM_LINK_WATCHER_KEY_VALIDATE_INACTIVE) ? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE : NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE) + | (_parse_data_get_bool (parse_data, NM_TEAM_LINK_WATCHER_KEY_SEND_ALWAYS) ? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS : NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE) + ), + error); + break; + } + +#if NM_MORE_ASSERTS > 5 + if (watcher) { + gs_free char *str2 = NULL; + nm_auto_unref_team_link_watcher NMTeamLinkWatcher *watcher2 = NULL; + static _nm_thread_local int recursive; + + nm_assert (!error || !*error); + if (recursive == 0) { + recursive = 1; + str2 = nm_utils_team_link_watcher_to_string (watcher); + nm_assert (str2); + watcher2 = nm_utils_team_link_watcher_from_string (str2, NULL); + nm_assert (watcher2); + nm_assert (nm_team_link_watcher_equal (watcher, watcher2)); + nm_assert (nm_team_link_watcher_equal (watcher2, watcher)); + nm_assert (recursive == 1); + recursive = 0; + } + } else + nm_assert (!error || *error); +#endif + + return watcher; +} diff --git a/shared/nm-libnm-core-aux/nm-libnm-core-aux.h b/shared/nm-libnm-core-aux/nm-libnm-core-aux.h new file mode 100644 index 00000000..d8960ad5 --- /dev/null +++ b/shared/nm-libnm-core-aux/nm-libnm-core-aux.h @@ -0,0 +1,54 @@ +/* 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 2019 Red Hat, Inc. + */ + +#ifndef __NM_LIBNM_CORE_AUX_H__ +#define __NM_LIBNM_CORE_AUX_H__ + +#include "nm-setting-team.h" + +typedef enum { + NM_TEAM_LINK_WATCHER_TYPE_NONE = 0, + NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL = (1u << 0), + NM_TEAM_LINK_WATCHER_TYPE_NSNAPING = (1u << 1), + NM_TEAM_LINK_WATCHER_TYPE_ARPING = (1u << 2), +} NMTeamLinkWatcherType; + +typedef enum { + NM_TEAM_LINK_WATCHER_KEY_NAME, + NM_TEAM_LINK_WATCHER_KEY_DELAY_UP, + NM_TEAM_LINK_WATCHER_KEY_DELAY_DOWN, + NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT, + NM_TEAM_LINK_WATCHER_KEY_INTERVAL, + NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX, + NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST, + NM_TEAM_LINK_WATCHER_KEY_VLANID, + NM_TEAM_LINK_WATCHER_KEY_SOURCE_HOST, + NM_TEAM_LINK_WATCHER_KEY_VALIDATE_ACTIVE, + NM_TEAM_LINK_WATCHER_KEY_VALIDATE_INACTIVE, + NM_TEAM_LINK_WATCHER_KEY_SEND_ALWAYS, + _NM_TEAM_LINK_WATCHER_KEY_NUM, +} NMTeamLinkWatcherKeyId; + +char *nm_utils_team_link_watcher_to_string (const NMTeamLinkWatcher *watcher); + +NMTeamLinkWatcher *nm_utils_team_link_watcher_from_string (const char *str, + GError **error); + +#endif /* __NM_LIBNM_CORE_AUX_H__ */ diff --git a/shared/nm-libnm-core-intern/nm-common-macros.h b/shared/nm-libnm-core-intern/nm-common-macros.h index f5aa3a1e..8352e405 100644 --- a/shared/nm-libnm-core-intern/nm-common-macros.h +++ b/shared/nm-libnm-core-intern/nm-common-macros.h @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-libnm-core-intern/nm-ethtool-utils.c b/shared/nm-libnm-core-intern/nm-ethtool-utils.c index 3313274a..635d77c5 100644 --- a/shared/nm-libnm-core-intern/nm-ethtool-utils.c +++ b/shared/nm-libnm-core-intern/nm-ethtool-utils.c @@ -1,5 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ - /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/shared/nm-libnm-core-intern/nm-ethtool-utils.h b/shared/nm-libnm-core-intern/nm-ethtool-utils.h index 5f22a9a0..71ad9860 100644 --- a/shared/nm-libnm-core-intern/nm-ethtool-utils.h +++ b/shared/nm-libnm-core-intern/nm-ethtool-utils.h @@ -1,4 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/shared/nm-meta-setting.c b/shared/nm-meta-setting.c index 8d1d4ecd..d50739ba 100644 --- a/shared/nm-meta-setting.c +++ b/shared/nm-meta-setting.c @@ -1,5 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ - /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -48,6 +46,7 @@ #include "nm-setting-olpc-mesh.h" #include "nm-setting-ovs-bridge.h" #include "nm-setting-ovs-interface.h" +#include "nm-setting-ovs-dpdk.h" #include "nm-setting-ovs-patch.h" #include "nm-setting-ovs-port.h" #include "nm-setting-ppp.h" @@ -73,87 +72,96 @@ /*****************************************************************************/ const NMSetting8021xSchemeVtable nm_setting_8021x_scheme_vtable[] = { - [NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT] = { - .setting_key = NM_SETTING_802_1X_CA_CERT, - .scheme_func = nm_setting_802_1x_get_ca_cert_scheme, - .format_func = NULL, - .path_func = nm_setting_802_1x_get_ca_cert_path, - .blob_func = nm_setting_802_1x_get_ca_cert_blob, - .uri_func = nm_setting_802_1x_get_ca_cert_uri, - .passwd_func = nm_setting_802_1x_get_ca_cert_password, - .pwflag_func = nm_setting_802_1x_get_ca_cert_password_flags, - .set_cert_func = nm_setting_802_1x_set_ca_cert, - .file_suffix = "ca-cert", - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT] = { - .setting_key = NM_SETTING_802_1X_PHASE2_CA_CERT, - .scheme_func = nm_setting_802_1x_get_phase2_ca_cert_scheme, - .format_func = NULL, - .path_func = nm_setting_802_1x_get_phase2_ca_cert_path, - .blob_func = nm_setting_802_1x_get_phase2_ca_cert_blob, - .uri_func = nm_setting_802_1x_get_phase2_ca_cert_uri, - .passwd_func = nm_setting_802_1x_get_phase2_ca_cert_password, - .pwflag_func = nm_setting_802_1x_get_phase2_ca_cert_password_flags, - .set_cert_func = nm_setting_802_1x_set_phase2_ca_cert, - .file_suffix = "inner-ca-cert", - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT] = { - .setting_key = NM_SETTING_802_1X_CLIENT_CERT, - .scheme_func = nm_setting_802_1x_get_client_cert_scheme, - .format_func = NULL, - .path_func = nm_setting_802_1x_get_client_cert_path, - .blob_func = nm_setting_802_1x_get_client_cert_blob, - .uri_func = nm_setting_802_1x_get_client_cert_uri, - .passwd_func = nm_setting_802_1x_get_client_cert_password, - .pwflag_func = nm_setting_802_1x_get_client_cert_password_flags, - .set_cert_func = nm_setting_802_1x_set_client_cert, - .file_suffix = "client-cert", - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT] = { - .setting_key = NM_SETTING_802_1X_PHASE2_CLIENT_CERT, - .scheme_func = nm_setting_802_1x_get_phase2_client_cert_scheme, - .format_func = NULL, - .path_func = nm_setting_802_1x_get_phase2_client_cert_path, - .blob_func = nm_setting_802_1x_get_phase2_client_cert_blob, - .uri_func = nm_setting_802_1x_get_phase2_client_cert_uri, - .passwd_func = nm_setting_802_1x_get_phase2_client_cert_password, - .pwflag_func = nm_setting_802_1x_get_phase2_client_cert_password_flags, - .set_cert_func = nm_setting_802_1x_set_phase2_client_cert, - .file_suffix = "inner-client-cert", - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY] = { - .setting_key = NM_SETTING_802_1X_PRIVATE_KEY, - .scheme_func = nm_setting_802_1x_get_private_key_scheme, - .format_func = nm_setting_802_1x_get_private_key_format, - .path_func = nm_setting_802_1x_get_private_key_path, - .blob_func = nm_setting_802_1x_get_private_key_blob, - .uri_func = nm_setting_802_1x_get_private_key_uri, - .passwd_func = nm_setting_802_1x_get_private_key_password, - .pwflag_func = nm_setting_802_1x_get_private_key_password_flags, - .set_private_key_func = nm_setting_802_1x_set_private_key, - .file_suffix = "private-key", - .is_secret = TRUE, - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY] = { - .setting_key = NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, - .scheme_func = nm_setting_802_1x_get_phase2_private_key_scheme, - .format_func = nm_setting_802_1x_get_phase2_private_key_format, - .path_func = nm_setting_802_1x_get_phase2_private_key_path, - .blob_func = nm_setting_802_1x_get_phase2_private_key_blob, - .uri_func = nm_setting_802_1x_get_phase2_private_key_uri, - .passwd_func = nm_setting_802_1x_get_phase2_private_key_password, - .pwflag_func = nm_setting_802_1x_get_phase2_private_key_password_flags, - .set_private_key_func = nm_setting_802_1x_set_phase2_private_key, - .file_suffix = "inner-private-key", - .is_secret = TRUE, - }, - - [NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN] = { NULL }, + +#define _D(_scheme_type, ...) \ + [(_scheme_type)] = { \ + .scheme_type = (_scheme_type), \ + __VA_ARGS__ \ + } + + _D (NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN), + + _D (NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT, + .setting_key = NM_SETTING_802_1X_CA_CERT, + .scheme_func = nm_setting_802_1x_get_ca_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_ca_cert_path, + .blob_func = nm_setting_802_1x_get_ca_cert_blob, + .uri_func = nm_setting_802_1x_get_ca_cert_uri, + .passwd_func = nm_setting_802_1x_get_ca_cert_password, + .pwflag_func = nm_setting_802_1x_get_ca_cert_password_flags, + .set_cert_func = nm_setting_802_1x_set_ca_cert, + .file_suffix = "ca-cert", + ), + + _D (NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT, + .setting_key = NM_SETTING_802_1X_PHASE2_CA_CERT, + .scheme_func = nm_setting_802_1x_get_phase2_ca_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_phase2_ca_cert_path, + .blob_func = nm_setting_802_1x_get_phase2_ca_cert_blob, + .uri_func = nm_setting_802_1x_get_phase2_ca_cert_uri, + .passwd_func = nm_setting_802_1x_get_phase2_ca_cert_password, + .pwflag_func = nm_setting_802_1x_get_phase2_ca_cert_password_flags, + .set_cert_func = nm_setting_802_1x_set_phase2_ca_cert, + .file_suffix = "inner-ca-cert", + ), + + _D (NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT, + .setting_key = NM_SETTING_802_1X_CLIENT_CERT, + .scheme_func = nm_setting_802_1x_get_client_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_client_cert_path, + .blob_func = nm_setting_802_1x_get_client_cert_blob, + .uri_func = nm_setting_802_1x_get_client_cert_uri, + .passwd_func = nm_setting_802_1x_get_client_cert_password, + .pwflag_func = nm_setting_802_1x_get_client_cert_password_flags, + .set_cert_func = nm_setting_802_1x_set_client_cert, + .file_suffix = "client-cert", + ), + + _D (NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT, + .setting_key = NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + .scheme_func = nm_setting_802_1x_get_phase2_client_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_phase2_client_cert_path, + .blob_func = nm_setting_802_1x_get_phase2_client_cert_blob, + .uri_func = nm_setting_802_1x_get_phase2_client_cert_uri, + .passwd_func = nm_setting_802_1x_get_phase2_client_cert_password, + .pwflag_func = nm_setting_802_1x_get_phase2_client_cert_password_flags, + .set_cert_func = nm_setting_802_1x_set_phase2_client_cert, + .file_suffix = "inner-client-cert", + ), + + _D (NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY, + .setting_key = NM_SETTING_802_1X_PRIVATE_KEY, + .scheme_func = nm_setting_802_1x_get_private_key_scheme, + .format_func = nm_setting_802_1x_get_private_key_format, + .path_func = nm_setting_802_1x_get_private_key_path, + .blob_func = nm_setting_802_1x_get_private_key_blob, + .uri_func = nm_setting_802_1x_get_private_key_uri, + .passwd_func = nm_setting_802_1x_get_private_key_password, + .pwflag_func = nm_setting_802_1x_get_private_key_password_flags, + .set_private_key_func = nm_setting_802_1x_set_private_key, + .file_suffix = "private-key", + .is_secret = TRUE, + ), + + _D (NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY, + .setting_key = NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + .scheme_func = nm_setting_802_1x_get_phase2_private_key_scheme, + .format_func = nm_setting_802_1x_get_phase2_private_key_format, + .path_func = nm_setting_802_1x_get_phase2_private_key_path, + .blob_func = nm_setting_802_1x_get_phase2_private_key_blob, + .uri_func = nm_setting_802_1x_get_phase2_private_key_uri, + .passwd_func = nm_setting_802_1x_get_phase2_private_key_password, + .pwflag_func = nm_setting_802_1x_get_phase2_private_key_password_flags, + .set_private_key_func = nm_setting_802_1x_set_phase2_private_key, + .file_suffix = "inner-private-key", + .is_secret = TRUE, + ), + +#undef _D }; /*****************************************************************************/ @@ -297,6 +305,12 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = { .setting_name = NM_SETTING_OVS_BRIDGE_SETTING_NAME, .get_setting_gtype = nm_setting_ovs_bridge_get_type, }, + [NM_META_SETTING_TYPE_OVS_DPDK] = { + .meta_type = NM_META_SETTING_TYPE_OVS_DPDK, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_OVS_DPDK_SETTING_NAME, + .get_setting_gtype = nm_setting_ovs_dpdk_get_type, + }, [NM_META_SETTING_TYPE_OVS_INTERFACE] = { .meta_type = NM_META_SETTING_TYPE_OVS_INTERFACE, .setting_priority = NM_SETTING_PRIORITY_HW_BASE, diff --git a/shared/nm-meta-setting.h b/shared/nm-meta-setting.h index 73ee103e..157a715f 100644 --- a/shared/nm-meta-setting.h +++ b/shared/nm-meta-setting.h @@ -1,5 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ - /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -101,6 +99,7 @@ typedef struct { NMSetting8021xCKFormat *out_format, GError **error); const char *file_suffix; + NMSetting8021xSchemeType scheme_type; bool is_secret:1; } NMSetting8021xSchemeVtable; @@ -141,6 +140,7 @@ typedef enum { NM_META_SETTING_TYPE_MACVLAN, NM_META_SETTING_TYPE_MATCH, NM_META_SETTING_TYPE_OVS_BRIDGE, + NM_META_SETTING_TYPE_OVS_DPDK, NM_META_SETTING_TYPE_OVS_INTERFACE, NM_META_SETTING_TYPE_OVS_PATCH, NM_META_SETTING_TYPE_OVS_PORT, diff --git a/shared/nm-std-aux/c-list-util.c b/shared/nm-std-aux/c-list-util.c index 44ca26a5..eb545b2d 100644 --- a/shared/nm-std-aux/c-list-util.c +++ b/shared/nm-std-aux/c-list-util.c @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-std-aux/c-list-util.h b/shared/nm-std-aux/c-list-util.h index 648bacc7..a1fe7169 100644 --- a/shared/nm-std-aux/c-list-util.h +++ b/shared/nm-std-aux/c-list-util.h @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-std-aux/nm-dbus-compat.h b/shared/nm-std-aux/nm-dbus-compat.h index dd97b5fd..8528bd07 100644 --- a/shared/nm-std-aux/nm-dbus-compat.h +++ b/shared/nm-std-aux/nm-dbus-compat.h @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-test-libnm-utils.h b/shared/nm-test-libnm-utils.h index 2b4fa600..4c921f4c 100644 --- a/shared/nm-test-libnm-utils.h +++ b/shared/nm-test-libnm-utils.h @@ -1,4 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,22 +21,11 @@ #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 { GDBusConnection *bus; GDBusProxy *proxy; GPid pid; int keepalive_fd; -#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB - struct { - DBusGConnection *bus; - } libdbus; -#endif } NMTstcServiceInfo; NMTstcServiceInfo *nmtstc_service_init (void); @@ -62,18 +50,6 @@ static inline void _nmtstc_auto_service_cleanup (NMTstcServiceInfo **info) }); \ NM_PRAGMA_WARNING_REENABLE -/*****************************************************************************/ - -#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, const char *method, @@ -85,10 +61,6 @@ NMDevice * nmtstc_service_add_wired_device (NMTstcServiceInfo *sinfo, const char *hwaddr, const char **subchannels); -#endif - -/*****************************************************************************/ - void nmtstc_service_add_connection (NMTstcServiceInfo *sinfo, NMConnection *connection, gboolean verify_connection, diff --git a/shared/nm-test-utils-impl.c b/shared/nm-test-utils-impl.c index 02d71593..eca037ba 100644 --- a/shared/nm-test-utils-impl.c +++ b/shared/nm-test-utils-impl.c @@ -1,4 +1,3 @@ -/* -*- 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 @@ -56,24 +55,6 @@ name_exists (GDBusConnection *c, const char *name) return exists; } -#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB -static DBusGProxy * -_libdbus_create_proxy_test (DBusGConnection *bus) -{ - DBusGProxy *proxy; - - proxy = dbus_g_proxy_new_for_name (bus, - NM_DBUS_SERVICE, - NM_DBUS_PATH, - "org.freedesktop.NetworkManager.LibnmGlibTest"); - g_assert (proxy); - - dbus_g_proxy_set_default_timeout (proxy, G_MAXINT); - - return proxy; -} -#endif - typedef struct { GMainLoop *mainloop; GDBusConnection *bus; @@ -201,11 +182,6 @@ nmtstc_service_init (void) NULL, &error); g_assert_no_error (error); -#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); -#endif return info; } @@ -223,10 +199,6 @@ nmtstc_service_cleanup (NMTstcServiceInfo *info) g_clear_object (&info->proxy); -#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB - g_clear_pointer (&info->libdbus.bus, dbus_g_connection_unref); -#endif - if (info->pid != NM_PID_T_INVAL) { kill (info->pid, SIGTERM); @@ -255,7 +227,6 @@ again_wait: g_free (info); } -#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB) typedef struct { GMainLoop *loop; const char *ifname; @@ -368,7 +339,6 @@ nmtstc_service_add_wired_device (NMTstcServiceInfo *sinfo, NMClient *client, { return add_device_common (sinfo, client, "AddWiredDevice", ifname, hwaddr, subchannels); } -#endif void nmtstc_service_add_connection (NMTstcServiceInfo *sinfo, @@ -376,41 +346,10 @@ nmtstc_service_add_connection (NMTstcServiceInfo *sinfo, gboolean verify_connection, char **out_path) { -#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB - gs_unref_hashtable GHashTable *new_settings = NULL; - gboolean success; - gs_free_error GError *error = NULL; - gs_free char *path = NULL; - gs_unref_object DBusGProxy *proxy = NULL; - - g_assert (sinfo); - g_assert (NM_IS_CONNECTION (connection)); - - new_settings = nm_connection_to_hash (connection, NM_SETTING_HASH_FLAG_ALL); - - proxy = _libdbus_create_proxy_test (sinfo->libdbus.bus); - - success = dbus_g_proxy_call (proxy, - "AddConnection", - &error, - DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, new_settings, - G_TYPE_BOOLEAN, verify_connection, - G_TYPE_INVALID, - DBUS_TYPE_G_OBJECT_PATH, &path, - G_TYPE_INVALID); - g_assert_no_error (error); - g_assert (success); - - g_assert (path && *path); - - if (out_path) - *out_path = g_strdup (path); -#else nmtstc_service_add_connection_variant (sinfo, nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_ALL), verify_connection, out_path); -#endif } void @@ -450,37 +389,10 @@ nmtstc_service_update_connection (NMTstcServiceInfo *sinfo, path = nm_connection_get_path (connection); g_assert (path); -#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB - { - gs_unref_hashtable GHashTable *new_settings = NULL; - gboolean success; - gs_free_error GError *error = NULL; - gs_unref_object DBusGProxy *proxy = NULL; - - g_assert (sinfo); - g_assert (NM_IS_CONNECTION (connection)); - - new_settings = nm_connection_to_hash (connection, NM_SETTING_HASH_FLAG_ALL); - - proxy = _libdbus_create_proxy_test (sinfo->libdbus.bus); - - success = dbus_g_proxy_call (proxy, - "UpdateConnection", - &error, - DBUS_TYPE_G_OBJECT_PATH, path, - DBUS_TYPE_G_MAP_OF_MAP_OF_VARIANT, new_settings, - G_TYPE_BOOLEAN, verify_connection, - G_TYPE_INVALID, - G_TYPE_INVALID); - g_assert_no_error (error); - g_assert (success); - } -#else nmtstc_service_update_connection_variant (sinfo, path, nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_ALL), verify_connection); -#endif } void @@ -508,55 +420,3 @@ nmtstc_service_update_connection_variant (NMTstcServiceInfo *sinfo, g_assert (g_variant_is_of_type (result, G_VARIANT_TYPE ("()"))); g_variant_unref (result); } - -/*****************************************************************************/ - -#if (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_GLIB -NMClient * -nmtstc_nm_client_new (void) -{ - NMClient *client; - DBusGConnection *bus; - GError *error = NULL; - gboolean success; - - bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error); - g_assert_no_error (error); - g_assert (bus); - - client = g_object_new (NM_TYPE_CLIENT, - NM_OBJECT_DBUS_CONNECTION, bus, - NM_OBJECT_DBUS_PATH, NM_DBUS_PATH, - NULL); - g_assert (client != NULL); - - dbus_g_connection_unref (bus); - - success = g_initable_init (G_INITABLE (client), NULL, &error); - g_assert_no_error (error); - g_assert (success == TRUE); - - return client; -} - -NMRemoteSettings * -nmtstc_nm_remote_settings_new (void) -{ - NMRemoteSettings *settings; - DBusGConnection *bus; - GError *error = NULL; - - bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error); - g_assert_no_error (error); - g_assert (bus); - - settings = nm_remote_settings_new (bus); - g_assert (settings); - - dbus_g_connection_unref (bus); - - return settings; -} -#endif - -/*****************************************************************************/ diff --git a/shared/nm-udev-aux/nm-udev-utils.c b/shared/nm-udev-aux/nm-udev-utils.c index 5d0919b3..e9dfd8d0 100644 --- a/shared/nm-udev-aux/nm-udev-utils.c +++ b/shared/nm-udev-aux/nm-udev-utils.c @@ -1,4 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* nm-udev-utils.c - udev utils functions * * This program is free software; you can redistribute it and/or modify diff --git a/shared/nm-udev-aux/nm-udev-utils.h b/shared/nm-udev-aux/nm-udev-utils.h index 911e8a27..0e5895ef 100644 --- a/shared/nm-udev-aux/nm-udev-utils.h +++ b/shared/nm-udev-aux/nm-udev-utils.h @@ -1,4 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* nm-udev-utils.h - udev utils functions * * This program is free software; you can redistribute it and/or modify diff --git a/shared/nm-utils/nm-compat.c b/shared/nm-utils/nm-compat.c index aa7c42f1..ea3e5392 100644 --- a/shared/nm-utils/nm-compat.c +++ b/shared/nm-utils/nm-compat.c @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-utils/nm-compat.h b/shared/nm-utils/nm-compat.h index 52341690..a8e3ee97 100644 --- a/shared/nm-utils/nm-compat.h +++ b/shared/nm-utils/nm-compat.h @@ -1,4 +1,3 @@ -/* -*- 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 diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h index d0ec7d9f..b2d103f1 100644 --- a/shared/nm-utils/nm-test-utils.h +++ b/shared/nm-utils/nm-test-utils.h @@ -1,4 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -710,18 +709,10 @@ nmtst_test_quick (void) #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_WARNING(msg) NMTST_EXPECT_LIBNM (G_LOG_LEVEL_WARNING, msg) #define NMTST_EXPECT_LIBNM_CRITICAL(msg) NMTST_EXPECT_LIBNM (G_LOG_LEVEL_CRITICAL, msg) -#endif /*****************************************************************************/ @@ -866,15 +857,22 @@ nmtst_get_rand (void) } static inline guint32 -nmtst_get_rand_int (void) +nmtst_get_rand_uint32 (void) { return g_rand_int (nmtst_get_rand ()); } +static inline guint +nmtst_get_rand_uint (void) +{ + G_STATIC_ASSERT_EXPR (sizeof (guint32) == sizeof (guint)); + return nmtst_get_rand_uint32 (); +} + static inline gboolean nmtst_get_rand_bool (void) { - return nmtst_get_rand_int () % 2; + return nmtst_get_rand_uint32 () % 2; } static inline gpointer @@ -909,7 +907,7 @@ nmtst_rand_buf (GRand *rand, gpointer buffer, gsize buffer_length) ({ \ typeof (v0) NM_UNIQ_T (UNIQ, uniq)[1 + NM_NARG (__VA_ARGS__)] = { (v0), __VA_ARGS__ }; \ \ - NM_UNIQ_T (UNIQ, uniq)[nmtst_get_rand_int () % G_N_ELEMENTS (NM_UNIQ_T (UNIQ, uniq))]; \ + NM_UNIQ_T (UNIQ, uniq)[nmtst_get_rand_uint32 () % G_N_ELEMENTS (NM_UNIQ_T (UNIQ, uniq))]; \ }) #define nmtst_rand_select(...) \ @@ -1935,6 +1933,69 @@ nmtst_assert_setting_verify_fails (NMSetting *setting, g_clear_error (&error); } +static inline void +nmtst_assert_setting_is_equal (gconstpointer /* const NMSetting * */ a, + gconstpointer /* const NMSetting * */ b, + NMSettingCompareFlags flags) +{ + gs_unref_hashtable GHashTable *hash = NULL; + guint32 r = nmtst_get_rand_uint32 (); + + g_assert (NM_IS_SETTING (a)); + g_assert (NM_IS_SETTING (b)); + + if (NM_FLAGS_HAS (r, 0x4)) + NMTST_SWAP (a, b); + + g_assert (nm_setting_compare ((NMSetting *) a, + (NMSetting *) b, + flags)); + + if (NM_FLAGS_HAS (r, 0x8)) + NMTST_SWAP (a, b); + + g_assert (nm_setting_diff ((NMSetting *) a, + (NMSetting *) b, + flags, + NM_FLAGS_HAS (r, 0x1), + &hash)); + g_assert (!hash); +} +#endif + +#ifdef __NM_SETTING_PRIVATE_H__ +static inline NMSetting * +nmtst_assert_setting_dbus_new (GType gtype, GVariant *variant) +{ + NMSetting *setting; + gs_free_error GError *error = NULL; + + g_assert (g_type_is_a (gtype, NM_TYPE_SETTING)); + g_assert (gtype != NM_TYPE_SETTING); + g_assert (variant); + g_assert (g_variant_is_of_type (variant, NM_VARIANT_TYPE_SETTING)); + + setting = _nm_setting_new_from_dbus (gtype, + variant, + NULL, + NM_SETTING_PARSE_FLAGS_STRICT, + &error); + nmtst_assert_success (setting, error); + return setting; +} + +static inline void +nmtst_assert_setting_dbus_roundtrip (gconstpointer /* const NMSetting * */ setting) +{ + gs_unref_object NMSetting *setting2 = NULL; + gs_unref_variant GVariant *variant = NULL; + + g_assert (NM_IS_SETTING (setting)); + + variant = _nm_setting_to_dbus ((NMSetting *) setting, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL); + setting2 = nmtst_assert_setting_dbus_new (G_OBJECT_TYPE (setting), variant); + nmtst_assert_setting_is_equal (setting, setting2, NM_SETTING_COMPARE_FLAG_EXACT); +} #endif #ifdef __NM_UTILS_H__ @@ -2148,6 +2209,25 @@ typedef enum { #endif /* __NM_CONNECTION_H__ */ +static inline GVariant * +nmtst_variant_from_string (const GVariantType *variant_type, + const char *variant_str) +{ + GVariant *variant; + GError *error = NULL; + + g_assert (variant_type); + g_assert (variant_str); + + variant = g_variant_parse (variant_type, + variant_str, + NULL, + NULL, + &error); + nmtst_assert_success (variant, error); + return variant; +} + /*****************************************************************************/ static inline void diff --git a/shared/nm-utils/nm-vpn-editor-plugin-call.h b/shared/nm-utils/nm-vpn-editor-plugin-call.h index fd982acf..ea5e27b0 100644 --- a/shared/nm-utils/nm-vpn-editor-plugin-call.h +++ b/shared/nm-utils/nm-vpn-editor-plugin-call.h @@ -1,4 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* NetworkManager -- Network link manager * * This program is free software; you can redistribute it and/or modify diff --git a/shared/nm-utils/nm-vpn-plugin-macros.h b/shared/nm-utils/nm-vpn-plugin-macros.h index acc549f2..97260f5f 100644 --- a/shared/nm-utils/nm-vpn-plugin-macros.h +++ b/shared/nm-utils/nm-vpn-plugin-macros.h @@ -1,5 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ - /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/shared/nm-utils/nm-vpn-plugin-utils.c b/shared/nm-utils/nm-vpn-plugin-utils.c index 353a2817..afc05bdf 100644 --- a/shared/nm-utils/nm-vpn-plugin-utils.c +++ b/shared/nm-utils/nm-vpn-plugin-utils.c @@ -1,5 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ - /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/shared/nm-utils/nm-vpn-plugin-utils.h b/shared/nm-utils/nm-vpn-plugin-utils.h index f3928d1e..961e0187 100644 --- a/shared/nm-utils/nm-vpn-plugin-utils.h +++ b/shared/nm-utils/nm-vpn-plugin-utils.h @@ -1,5 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ - /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/shared/nm-utils/tests/test-shared-general.c b/shared/nm-utils/tests/test-shared-general.c index 83cffd7f..24affba7 100644 --- a/shared/nm-utils/tests/test-shared-general.c +++ b/shared/nm-utils/tests/test-shared-general.c @@ -29,24 +29,10 @@ /*****************************************************************************/ -static int _monotonic_timestamp_initialized; - -void -_nm_utils_monotonic_timestamp_initialized (const struct timespec *tp, - gint64 offset_sec, - gboolean is_boottime) -{ - g_assert (!_monotonic_timestamp_initialized); - _monotonic_timestamp_initialized = 1; -} - -/*****************************************************************************/ - static void test_monotonic_timestamp (void) { g_assert (nm_utils_get_monotonic_timestamp_s () > 0); - g_assert (_monotonic_timestamp_initialized); } /*****************************************************************************/ @@ -163,10 +149,10 @@ test_nm_strndup_a (void) char ch; gsize i, l; - input = g_strnfill (nmtst_get_rand_int () % 20, 'x'); + input = g_strnfill (nmtst_get_rand_uint32 () % 20, 'x'); for (i = 0; input[i]; i++) { - while ((ch = ((char) nmtst_get_rand_int ())) == '\0') { + while ((ch = ((char) nmtst_get_rand_uint32 ())) == '\0') { /* repeat. */ } input[i] = ch; @@ -189,7 +175,7 @@ test_nm_strndup_a (void) gs_free char *dup_free = NULL; const char *dup; - l = nmtst_get_rand_int () % 23; + l = nmtst_get_rand_uint32 () % 23; dup = nm_strndup_a (10, input, l, &dup_free); g_assert (strncmp (dup, input, l) == 0); g_assert (strlen (dup) <= l); @@ -228,7 +214,7 @@ test_unaligned (void) guint8 val = 0; while (val == 0) - val = nmtst_get_rand_int () % 256; + val = nmtst_get_rand_uint32 () % 256; buf[shift] = val; @@ -441,6 +427,87 @@ test_strstrip_avoid_copy (void) _do_strstrip_avoid_copy (" 01234567890 "); _do_strstrip_avoid_copy (" 012345678901 "); } + +/*****************************************************************************/ + +static void +test_nm_utils_bin2hexstr (void) +{ + int n_run; + + for (n_run = 0; n_run < 100; n_run++) { + guint8 buf[100]; + guint8 buf2[G_N_ELEMENTS (buf) + 1]; + gsize len = nmtst_get_rand_uint32 () % (G_N_ELEMENTS (buf) + 1); + char strbuf1[G_N_ELEMENTS (buf) * 3]; + gboolean allocate = nmtst_get_rand_bool (); + char delimiter = nmtst_get_rand_bool () ? ':' : '\0'; + gboolean upper_case = nmtst_get_rand_bool (); + gsize expected_strlen; + char *str_hex; + gsize required_len; + gboolean outlen_set; + gsize outlen; + guint8 *bin2; + + nmtst_rand_buf (NULL, buf, len); + + if (len == 0) + expected_strlen = 0; + else if (delimiter != '\0') + expected_strlen = (len * 3u) - 1; + else + expected_strlen = len * 2u; + + g_assert_cmpint (expected_strlen, <, G_N_ELEMENTS (strbuf1)); + + str_hex = nm_utils_bin2hexstr_full (buf, len, delimiter, upper_case, !allocate ? strbuf1 : NULL); + + g_assert (str_hex); + if (!allocate) + g_assert (str_hex == strbuf1); + g_assert_cmpint (strlen (str_hex), ==, expected_strlen); + + g_assert (NM_STRCHAR_ALL (str_hex, ch, (ch >= '0' && ch <= '9') + || ch == delimiter + || ( upper_case + ? (ch >= 'A' && ch <= 'F') + : (ch >= 'a' && ch <= 'f')))); + + required_len = nmtst_get_rand_bool () ? len : 0u; + + outlen_set = required_len == 0 || nmtst_get_rand_bool (); + + memset (buf2, 0, sizeof (buf2)); + + bin2 = nm_utils_hexstr2bin_full (str_hex, + nmtst_get_rand_bool (), + delimiter != '\0' && nmtst_get_rand_bool (), + delimiter != '\0' + ? nmtst_rand_select ((const char *) ":", ":-") + : nmtst_rand_select ((const char *) ":", ":-", "", NULL), + required_len, + buf2, + len, + outlen_set ? &outlen : NULL); + if (len > 0) { + g_assert (bin2); + g_assert (bin2 == buf2); + } else + g_assert (!bin2); + + if (outlen_set) + g_assert_cmpint (outlen, ==, len); + + g_assert_cmpmem (buf, len, buf2, len); + + g_assert (buf2[len] == '\0'); + + if (allocate) + g_free (str_hex); + } +} + /*****************************************************************************/ NMTST_DEFINE (); @@ -458,7 +525,7 @@ int main (int argc, char **argv) g_test_add_func ("/general/test_unaligned", test_unaligned); g_test_add_func ("/general/test_strv_cmp", test_strv_cmp); g_test_add_func ("/general/test_strstrip_avoid_copy", test_strstrip_avoid_copy); + g_test_add_func ("/general/test_nm_utils_bin2hexstr", test_nm_utils_bin2hexstr); return g_test_run (); } - diff --git a/shared/nm-version-macros.h b/shared/nm-version-macros.h index 6c5e8557..c8cc7de1 100644 --- a/shared/nm-version-macros.h +++ b/shared/nm-version-macros.h @@ -1,4 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -37,7 +36,7 @@ * Evaluates to the minor version number of NetworkManager which this source * is compiled against. */ -#define NM_MINOR_VERSION (18) +#define NM_MINOR_VERSION (19) /** * NM_MICRO_VERSION: @@ -45,7 +44,7 @@ * Evaluates to the micro version number of NetworkManager which this source * compiled against. */ -#define NM_MICRO_VERSION (0) +#define NM_MICRO_VERSION (90) /** * NM_CHECK_VERSION: @@ -76,6 +75,7 @@ #define NM_VERSION_1_14 (NM_ENCODE_VERSION (1, 14, 0)) #define NM_VERSION_1_16 (NM_ENCODE_VERSION (1, 16, 0)) #define NM_VERSION_1_18 (NM_ENCODE_VERSION (1, 18, 0)) +#define NM_VERSION_1_20 (NM_ENCODE_VERSION (1, 20, 0)) /* For releases, NM_API_VERSION is equal to NM_VERSION. * diff --git a/shared/nm-version-macros.h.in b/shared/nm-version-macros.h.in index 4b57529a..a546b100 100644 --- a/shared/nm-version-macros.h.in +++ b/shared/nm-version-macros.h.in @@ -1,4 +1,3 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -76,6 +75,7 @@ #define NM_VERSION_1_14 (NM_ENCODE_VERSION (1, 14, 0)) #define NM_VERSION_1_16 (NM_ENCODE_VERSION (1, 16, 0)) #define NM_VERSION_1_18 (NM_ENCODE_VERSION (1, 18, 0)) +#define NM_VERSION_1_20 (NM_ENCODE_VERSION (1, 20, 0)) /* For releases, NM_API_VERSION is equal to NM_VERSION. * diff --git a/shared/systemd/nm-logging-stub.c b/shared/systemd/nm-logging-stub.c index 59699228..95ff51e5 100644 --- a/shared/systemd/nm-logging-stub.c +++ b/shared/systemd/nm-logging-stub.c @@ -45,3 +45,10 @@ _nm_log_impl (const char *file, ...) { } + +void +_nm_utils_monotonic_timestamp_initialized (const struct timespec *tp, + gint64 offset_sec, + gboolean is_boottime) +{ +} diff --git a/shared/systemd/nm-sd-utils-shared.c b/shared/systemd/nm-sd-utils-shared.c index ecd27492..7206fa52 100644 --- a/shared/systemd/nm-sd-utils-shared.c +++ b/shared/systemd/nm-sd-utils-shared.c @@ -24,6 +24,11 @@ #include "path-util.h" #include "hexdecoct.h" +#include "dns-domain.h" + +/*****************************************************************************/ + +const bool mempool_use_allowed = true; /*****************************************************************************/ @@ -83,3 +88,11 @@ nm_sd_utils_unbase64mem (const char *p, { return unbase64mem_full (p, l, secure, (void **) mem, len); } + +int nm_sd_dns_name_to_wire_format (const char *domain, + guint8 *buffer, + size_t len, + gboolean canonical) +{ + return dns_name_to_wire_format (domain, buffer, len, canonical); +} diff --git a/shared/systemd/nm-sd-utils-shared.h b/shared/systemd/nm-sd-utils-shared.h index b3b77c88..48a8cf37 100644 --- a/shared/systemd/nm-sd-utils-shared.h +++ b/shared/systemd/nm-sd-utils-shared.h @@ -39,4 +39,9 @@ int nm_sd_utils_unbase64mem (const char *p, /*****************************************************************************/ +int nm_sd_dns_name_to_wire_format (const char *domain, + guint8 *buffer, + size_t len, + gboolean canonical); + #endif /* __NM_SD_UTILS_SHARED_H__ */ diff --git a/shared/systemd/sd-adapt-shared/format-util.h b/shared/systemd/sd-adapt-shared/format-util.h deleted file mode 100644 index 637892c2..00000000 --- a/shared/systemd/sd-adapt-shared/format-util.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -/* dummy header */ diff --git a/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h b/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h index a285c3cd..8dd93341 100644 --- a/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h +++ b/shared/systemd/sd-adapt-shared/nm-sd-adapt-shared.h @@ -1,4 +1,3 @@ -/* -*- 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, or (at your option) @@ -21,8 +20,6 @@ #include "nm-default.h" -#include <syslog.h> - #include "nm-glib-aux/nm-logging-fwd.h" /*****************************************************************************/ @@ -32,32 +29,29 @@ /*****************************************************************************/ -static inline NMLogLevel -_slog_level_to_nm (int slevel) -{ - switch (LOG_PRI (slevel)) { - case LOG_DEBUG: return LOGL_DEBUG; - case LOG_WARNING: return LOGL_WARN; - case LOG_CRIT: - case LOG_ERR: return LOGL_ERR; - case LOG_INFO: - case LOG_NOTICE: - default: return LOGL_INFO; - } -} +/* systemd detects whether compiler supports "-Wstringop-truncation" to disable + * the warning at particular places. Since we anyway build with -Wno-pragma, + * we don't do that and just let systemd call + * + * _Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"") + * + * regadless whether that would result in a -Wpragma warning. */ +#define HAVE_WSTRINGOP_TRUNCATION 1 + +/*****************************************************************************/ static inline int _nm_log_get_max_level_realm (void) { /* inline function, to avoid coverity warning about constant expression. */ - return LOG_DEBUG; + return 7 /* LOG_DEBUG */; } #define log_get_max_level_realm(realm) _nm_log_get_max_level_realm () #define log_internal_realm(level, error, file, line, func, format, ...) \ ({ \ const int _nm_e = (error); \ - const NMLogLevel _nm_l = _slog_level_to_nm ((level)); \ + const NMLogLevel _nm_l = nm_log_level_from_syslog (LOG_PRI (level)); \ \ if (_nm_log_enabled_impl (!(NM_THREAD_SAFE_ON_MAIN_THREAD), _nm_l, LOGD_SYSTEMD)) { \ const char *_nm_location = strrchr ((""file), '/'); \ @@ -102,6 +96,14 @@ G_STMT_START { \ #include <sys/syscall.h> #include <sys/ioctl.h> +/*****************************************************************************/ + +/* systemd cannot be compiled with "-Wdeclaration-after-statement". In particular + * in combintation with assert_cc(). */ +NM_PRAGMA_WARNING_DISABLE ("-Wdeclaration-after-statement") + +/*****************************************************************************/ + static inline pid_t raw_getpid (void) { #if defined(__alpha__) diff --git a/shared/systemd/sd-adapt-shared/strxcpyx.h b/shared/systemd/sd-adapt-shared/strxcpyx.h deleted file mode 100644 index 637892c2..00000000 --- a/shared/systemd/sd-adapt-shared/strxcpyx.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -/* dummy header */ diff --git a/shared/systemd/src/basic/alloc-util.c b/shared/systemd/src/basic/alloc-util.c index 92b350bc..c97d8700 100644 --- a/shared/systemd/src/basic/alloc-util.c +++ b/shared/systemd/src/basic/alloc-util.c @@ -66,8 +66,31 @@ void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) { if (!q) return NULL; + if (size > 0) { + size_t bn; + + /* Adjust for the 64 byte minimum */ + newalloc = a / size; + + bn = malloc_usable_size(q) / size; + if (bn > newalloc) { + void *qq; + + /* The actual size allocated is larger than what we asked for. Let's call realloc() again to + * take possession of the extra space. This should be cheap, since libc doesn't have to move + * the memory for this. */ + + qq = realloc(q, bn * size); + if (_likely_(qq)) { + *p = qq; + *allocated = bn; + return qq; + } + } + } + *p = q; - *allocated = _unlikely_(size == 0) ? newalloc : malloc_usable_size(q) / size; + *allocated = newalloc; return q; } diff --git a/shared/systemd/src/basic/alloc-util.h b/shared/systemd/src/basic/alloc-util.h index 9b20be47..64d9e003 100644 --- a/shared/systemd/src/basic/alloc-util.h +++ b/shared/systemd/src/basic/alloc-util.h @@ -58,7 +58,7 @@ static inline void *mfree(void *memory) { }) void* memdup(const void *p, size_t l) _alloc_(2); -void* memdup_suffix0(const void *p, size_t l) _alloc_(2); +void* memdup_suffix0(const void *p, size_t l); /* We can't use _alloc_() here, since we return a buffer one byte larger than the specified size */ #define memdupa(p, l) \ ({ \ @@ -112,7 +112,9 @@ _alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, si return memdup(p, size * need); } -_alloc_(2, 3) static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) { +/* Note that we can't decorate this function with _alloc_() since the returned memory area is one byte larger + * than the product of its parameters. */ +static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) { if (size_multiply_overflow(size, need)) return NULL; diff --git a/shared/systemd/src/basic/env-file.c b/shared/systemd/src/basic/env-file.c index 4a0f9c39..5860b050 100644 --- a/shared/systemd/src/basic/env-file.c +++ b/shared/systemd/src/basic/env-file.c @@ -2,8 +2,6 @@ #include "nm-sd-adapt-shared.h" -#include <stdio_ext.h> - #include "alloc-util.h" #include "env-file.h" #include "env-util.h" @@ -548,7 +546,6 @@ int write_env_file(const char *fname, char **l) { if (r < 0) return r; - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); (void) fchmod_umask(fileno(f), 0644); STRV_FOREACH(i, l) diff --git a/shared/systemd/src/basic/env-util.c b/shared/systemd/src/basic/env-util.c index dc10362d..cd9d3176 100644 --- a/shared/systemd/src/basic/env-util.c +++ b/shared/systemd/src/basic/env-util.c @@ -75,7 +75,7 @@ bool env_value_is_valid(const char *e) { * either. Discounting the shortest possible variable name of * length 1, the equal sign and trailing NUL this hence leaves * ARG_MAX-3 as longest possible variable value. */ - if (strlen(e) > (size_t) sysconf(_SC_ARG_MAX) - 3) + if (strlen(e) > sc_arg_max() - 3) return false; return true; @@ -98,7 +98,7 @@ bool env_assignment_is_valid(const char *e) { * be > ARG_MAX, hence the individual variable assignments * cannot be either, but let's leave room for one trailing NUL * byte. */ - if (strlen(e) > (size_t) sysconf(_SC_ARG_MAX) - 1) + if (strlen(e) > sc_arg_max() - 1) return false; return true; @@ -570,7 +570,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { t = strv_env_get_n(env, word+2, e-word-2, flags); - k = strappend(r, t); + k = strjoin(r, t); if (!k) return NULL; @@ -626,7 +626,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { else if (!t && state == DEFAULT_VALUE) t = v = replace_env_n(test_value, e-test_value, env, flags); - k = strappend(r, t); + k = strjoin(r, t); if (!k) return NULL; @@ -645,7 +645,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { t = strv_env_get_n(env, word+1, e-word-1, flags); - k = strappend(r, t); + k = strjoin(r, t); if (!k) return NULL; @@ -664,7 +664,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) { assert(flags & REPLACE_ENV_ALLOW_BRACELESS); t = strv_env_get_n(env, word+1, e-word-1, flags); - return strappend(r, t); + return strjoin(r, t); } else return strnappend(r, word, e-word); } @@ -691,7 +691,7 @@ char **replace_env_argv(char **argv, char **env) { if (e) { int r; - r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES); + r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE); if (r < 0) { ret[k] = NULL; strv_free(ret); diff --git a/shared/systemd/src/basic/env-util.h b/shared/systemd/src/basic/env-util.h index d54f9965..92802ed7 100644 --- a/shared/systemd/src/basic/env-util.h +++ b/shared/systemd/src/basic/env-util.h @@ -4,10 +4,17 @@ #include <stdbool.h> #include <stddef.h> #include <stdio.h> +#include <unistd.h> #include "macro.h" #include "string.h" +static inline size_t sc_arg_max(void) { + long l = sysconf(_SC_ARG_MAX); + assert(l > 0); + return (size_t) l; +} + bool env_name_is_valid(const char *e); bool env_value_is_valid(const char *e); bool env_assignment_is_valid(const char *e); diff --git a/shared/systemd/src/basic/errno-util.h b/shared/systemd/src/basic/errno-util.h index d7a5ea77..6053cde6 100644 --- a/shared/systemd/src/basic/errno-util.h +++ b/shared/systemd/src/basic/errno-util.h @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #pragma once +#include <stdlib.h> +#include <string.h> + #include "macro.h" static inline void _reset_errno_(int *saved_errno) { @@ -28,6 +31,22 @@ static inline int negative_errno(void) { return -errno; } +static inline const char *strerror_safe(int error) { + /* 'safe' here does NOT mean thread safety. */ + return strerror(abs(error)); +} + +static inline int errno_or_else(int fallback) { + /* To be used when invoking library calls where errno handling is not defined clearly: we return + * errno if it is set, and the specified error otherwise. The idea is that the caller initializes + * errno to zero before doing an API call, and then uses this helper to retrieve a somewhat useful + * error code */ + if (errno > 0) + return -errno; + + return -abs(fallback); +} + /* Hint #1: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5. * * Hint #2: The kernel sends e.g., EHOSTUNREACH or ENONET to userspace in some ICMP error cases. See the diff --git a/shared/systemd/src/basic/escape.c b/shared/systemd/src/basic/escape.c index 8f7a1b33..06d823c7 100644 --- a/shared/systemd/src/basic/escape.c +++ b/shared/systemd/src/basic/escape.c @@ -370,34 +370,81 @@ int cunescape(const char *s, UnescapeFlags flags, char **ret) { return cunescape_length(s, strlen(s), flags, ret); } -char *xescape(const char *s, const char *bad) { - char *r, *t; +#if 0 /* NM_IGNORED */ +char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits) { + char *ans, *t, *prev, *prev2; const char *f; - /* Escapes all chars in bad, in addition to \ and all special - * chars, in \xFF style escaping. May be reversed with - * cunescape(). */ + /* Escapes all chars in bad, in addition to \ and all special chars, in \xFF style escaping. May be + * reversed with cunescape(). If eight_bits is true, characters >= 127 are let through unchanged. + * This corresponds to non-ASCII printable characters in pre-unicode encodings. + * + * If console_width is reached, output is truncated and "..." is appended. */ - r = new(char, strlen(s) * 4 + 1); - if (!r) + if (console_width == 0) + return strdup(""); + + ans = new(char, MIN(strlen(s), console_width) * 4 + 1); + if (!ans) return NULL; - for (f = s, t = r; *f; f++) { + memset(ans, '_', MIN(strlen(s), console_width) * 4); + ans[MIN(strlen(s), console_width) * 4] = 0; + + for (f = s, t = prev = prev2 = ans; ; f++) { + char *tmp_t = t; + + if (!*f) { + *t = 0; + return ans; + } + + if ((unsigned char) *f < ' ' || (!eight_bits && (unsigned char) *f >= 127) || + *f == '\\' || strchr(bad, *f)) { + if ((size_t) (t - ans) + 4 > console_width) + break; - if ((*f < ' ') || (*f >= 127) || - (*f == '\\') || strchr(bad, *f)) { *(t++) = '\\'; *(t++) = 'x'; *(t++) = hexchar(*f >> 4); *(t++) = hexchar(*f); - } else + } else { + if ((size_t) (t - ans) + 1 > console_width) + break; + *(t++) = *f; + } + + /* We might need to go back two cycles to fit three dots, so remember two positions */ + prev2 = prev; + prev = tmp_t; } - *t = 0; + /* We can just write where we want, since chars are one-byte */ + size_t c = MIN(console_width, 3u); /* If the console is too narrow, write fewer dots */ + size_t off; + if (console_width - c >= (size_t) (t - ans)) + off = (size_t) (t - ans); + else if (console_width - c >= (size_t) (prev - ans)) + off = (size_t) (prev - ans); + else if (console_width - c >= (size_t) (prev2 - ans)) + off = (size_t) (prev2 - ans); + else + off = console_width - c; + assert(off <= (size_t) (t - ans)); - return r; + memcpy(ans + off, "...", c); + ans[off + c] = '\0'; + return ans; +} + +char *escape_non_printable_full(const char *str, size_t console_width, bool eight_bit) { + if (eight_bit) + return xescape_full(str, "", console_width, true); + else + return utf8_escape_non_printable_full(str, console_width); } +#endif /* NM_IGNORED */ char *octescape(const char *s, size_t len) { char *r, *t; diff --git a/shared/systemd/src/basic/escape.h b/shared/systemd/src/basic/escape.h index 51562099..b26054c5 100644 --- a/shared/systemd/src/basic/escape.h +++ b/shared/systemd/src/basic/escape.h @@ -46,8 +46,12 @@ int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **r int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit); -char *xescape(const char *s, const char *bad); +char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits); +static inline char *xescape(const char *s, const char *bad) { + return xescape_full(s, bad, SIZE_MAX, false); +} char *octescape(const char *s, size_t len); +char *escape_non_printable_full(const char *str, size_t console_width, bool eight_bit); char *shell_escape(const char *s, const char *bad); char* shell_maybe_quote(const char *s, EscapeStyle style); diff --git a/shared/systemd/src/basic/extract-word.c b/shared/systemd/src/basic/extract-word.c index 782c868b..15cbaafb 100644 --- a/shared/systemd/src/basic/extract-word.c +++ b/shared/systemd/src/basic/extract-word.c @@ -30,6 +30,8 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra assert(p); assert(ret); + /* Those two don't make sense together. */ + assert(!FLAGS_SET(flags, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE)); /* Bail early if called after last value or with no input */ if (!*p) @@ -137,7 +139,7 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra for (;; (*p)++, c = **p) { if (c == 0) goto finish_force_terminate; - else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_QUOTES)) { + else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_UNQUOTE)) { quote = c; break; } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { diff --git a/shared/systemd/src/basic/extract-word.h b/shared/systemd/src/basic/extract-word.h index 705ebbe9..e2d43389 100644 --- a/shared/systemd/src/basic/extract-word.h +++ b/shared/systemd/src/basic/extract-word.h @@ -7,7 +7,7 @@ typedef enum ExtractFlags { EXTRACT_RELAX = 1 << 0, EXTRACT_CUNESCAPE = 1 << 1, EXTRACT_CUNESCAPE_RELAX = 1 << 2, - EXTRACT_QUOTES = 1 << 3, + EXTRACT_UNQUOTE = 1 << 3, EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 4, EXTRACT_RETAIN_ESCAPE = 1 << 5, } ExtractFlags; diff --git a/shared/systemd/src/basic/fileio.c b/shared/systemd/src/basic/fileio.c index 0dfb4574..bd2afbbe 100644 --- a/shared/systemd/src/basic/fileio.c +++ b/shared/systemd/src/basic/fileio.c @@ -23,6 +23,7 @@ #include "log.h" #include "macro.h" #include "missing.h" +#include "mkdir.h" #include "parse-util.h" #include "path-util.h" #include "stdio-util.h" @@ -31,6 +32,52 @@ #define READ_FULL_BYTES_MAX (4U*1024U*1024U) +int fopen_unlocked(const char *path, const char *options, FILE **ret) { + assert(ret); + + FILE *f = fopen(path, options); + if (!f) + return -errno; + + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + + *ret = f; + return 0; +} + +int fdopen_unlocked(int fd, const char *options, FILE **ret) { + assert(ret); + + FILE *f = fdopen(fd, options); + if (!f) + return -errno; + + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + + *ret = f; + return 0; +} + +FILE* open_memstream_unlocked(char **ptr, size_t *sizeloc) { + FILE *f = open_memstream(ptr, sizeloc); + if (!f) + return NULL; + + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + + return f; +} + +FILE* fmemopen_unlocked(void *buf, size_t size, const char *mode) { + FILE *f = fmemopen(buf, size, mode); + if (!f) + return NULL; + + (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + + return f; +} + #if 0 /* NM_IGNORED */ int write_string_stream_ts( FILE *f, @@ -98,7 +145,6 @@ static int write_string_file_atomic( if (r < 0) return r; - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); (void) fchmod_umask(fileno(f), 0644); r = write_string_stream_ts(f, line, flags, ts); @@ -132,6 +178,12 @@ int write_string_file_ts( /* We don't know how to verify whether the file contents was already on-disk. */ assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC))); + if (flags & WRITE_STRING_FILE_MKDIR_0755) { + r = mkdir_parents(fn, 0755); + if (r < 0) + return r; + } + if (flags & WRITE_STRING_FILE_ATOMIC) { assert(flags & WRITE_STRING_FILE_CREATE); @@ -144,11 +196,9 @@ int write_string_file_ts( assert(!ts); if (flags & WRITE_STRING_FILE_CREATE) { - f = fopen(fn, "we"); - if (!f) { - r = -errno; + r = fopen_unlocked(fn, "we", &f); + if (r < 0) goto fail; - } } else { int fd; @@ -160,16 +210,13 @@ int write_string_file_ts( goto fail; } - f = fdopen(fd, "w"); - if (!f) { - r = -errno; + r = fdopen_unlocked(fd, "w", &f); + if (r < 0) { safe_close(fd); goto fail; } } - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); - if (flags & WRITE_STRING_FILE_DISABLE_BUFFER) setvbuf(f, NULL, _IONBF, 0); @@ -216,15 +263,14 @@ int write_string_filef( int read_one_line_file(const char *fn, char **line) { _cleanup_fclose_ FILE *f = NULL; + int r; assert(fn); assert(line); - f = fopen(fn, "re"); - if (!f) - return -errno; - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + r = fopen_unlocked(fn, "re", &f); + if (r < 0) + return r; return read_line(f, LONG_LINE_MAX, line); } @@ -233,6 +279,7 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *buf = NULL; size_t l, k; + int r; assert(fn); assert(blob); @@ -246,17 +293,15 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { if (!buf) return -ENOMEM; - f = fopen(fn, "re"); - if (!f) - return -errno; - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + r = fopen_unlocked(fn, "re", &f); + if (r < 0) + return r; /* We try to read one byte more than we need, so that we know whether we hit eof */ errno = 0; k = fread(buf, 1, l + accept_extra_nl + 1, f); if (ferror(f)) - return errno > 0 ? -errno : -EIO; + return errno_or_else(EIO); if (k != l && k != l + accept_extra_nl) return 0; @@ -283,7 +328,8 @@ int read_full_stream_full( assert(f); assert(ret_contents); - assert(!(flags & READ_FULL_FILE_UNBASE64) || ret_size); + assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)); + assert(!(flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) || ret_size); n_next = LINE_MAX; /* Start size */ @@ -324,6 +370,7 @@ int read_full_stream_full( } memcpy_safe(t, buf, n); explicit_bzero_safe(buf, n); + buf = mfree(buf); } else { t = realloc(buf, n_next + 1); if (!t) @@ -339,7 +386,7 @@ int read_full_stream_full( l += k; if (ferror(f)) { - r = errno > 0 ? -errno : -EIO; + r = errno_or_else(EIO); goto finalize; } @@ -360,9 +407,12 @@ int read_full_stream_full( n_next = MIN(n * 2, READ_FULL_BYTES_MAX); } - if (flags & READ_FULL_FILE_UNBASE64) { + if (flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) { buf[l++] = 0; - r = unbase64mem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size); + if (flags & READ_FULL_FILE_UNBASE64) + r = unbase64mem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size); + else + r = unhexmem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size); goto finalize; } @@ -394,15 +444,14 @@ finalize: int read_full_file_full(const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) { _cleanup_fclose_ FILE *f = NULL; + int r; assert(filename); assert(contents); - f = fopen(filename, "re"); - if (!f) - return -errno; - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + r = fopen_unlocked(filename, "re", &f); + if (r < 0) + return r; return read_full_stream_full(f, filename, flags, contents, size); } @@ -547,10 +596,7 @@ static int search_and_fopen_internal(const char *path, const char *mode, const c _cleanup_free_ char *p = NULL; FILE *f; - if (root) - p = strjoin(root, *i, "/", path); - else - p = strjoin(*i, "/", path); + p = path_join(root, *i, path); if (!p) return -ENOMEM; @@ -623,7 +669,7 @@ int fflush_and_check(FILE *f) { fflush(f); if (ferror(f)) - return errno > 0 ? -errno : -EIO; + return errno_or_else(EIO); return 0; } @@ -741,7 +787,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile); int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) { size_t n = 0, allocated = 0, count = 0; _cleanup_free_ char *buffer = NULL; - int r; + int r, tty = -1; assert(f); @@ -816,6 +862,17 @@ int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) { count++; if (eol != EOL_NONE) { + /* If we are on a tty, we can't wait for more input. But we expect only + * \n as the single EOL marker, so there is no need to wait. We check + * this condition last to avoid isatty() check if not necessary. */ + + if (tty < 0) + tty = isatty(fileno(f)); + if (tty > 0) + break; + } + + if (eol != EOL_NONE) { previous_eol |= eol; continue; } @@ -853,7 +910,7 @@ int safe_fgetc(FILE *f, char *ret) { k = fgetc(f); if (k == EOF) { if (ferror(f)) - return errno > 0 ? -errno : -EIO; + return errno_or_else(EIO); if (ret) *ret = 0; diff --git a/shared/systemd/src/basic/fileio.h b/shared/systemd/src/basic/fileio.h index 760e7386..05f6c89d 100644 --- a/shared/systemd/src/basic/fileio.h +++ b/shared/systemd/src/basic/fileio.h @@ -21,6 +21,7 @@ typedef enum { WRITE_STRING_FILE_SYNC = 1 << 4, WRITE_STRING_FILE_DISABLE_BUFFER = 1 << 5, WRITE_STRING_FILE_NOFOLLOW = 1 << 6, + WRITE_STRING_FILE_MKDIR_0755 = 1 << 7, /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file() @@ -31,8 +32,14 @@ typedef enum { typedef enum { READ_FULL_FILE_SECURE = 1 << 0, READ_FULL_FILE_UNBASE64 = 1 << 1, + READ_FULL_FILE_UNHEX = 1 << 2, } ReadFullFileFlags; +int fopen_unlocked(const char *path, const char *options, FILE **ret); +int fdopen_unlocked(int fd, const char *options, FILE **ret); +FILE* open_memstream_unlocked(char **ptr, size_t *sizeloc); +FILE* fmemopen_unlocked(void *buf, size_t size, const char *mode); + int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags, struct timespec *ts); static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) { return write_string_stream_ts(f, line, flags, NULL); diff --git a/shared/systemd/src/basic/format-util.c b/shared/systemd/src/basic/format-util.c new file mode 100644 index 00000000..7a3e735b --- /dev/null +++ b/shared/systemd/src/basic/format-util.c @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "nm-sd-adapt-shared.h" + +#include <stdio.h> + +#include "format-util.h" +#include "memory-util.h" + +char *format_ifname(int ifindex, char buf[static IF_NAMESIZE + 1]) { + /* Buffer is always cleared */ + memzero(buf, IF_NAMESIZE + 1); + return if_indextoname(ifindex, buf); +} + +char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) { + typedef struct { + const char *suffix; + uint64_t factor; + } suffix_table; + static const suffix_table table_iec[] = { + { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "M", UINT64_C(1024)*UINT64_C(1024) }, + { "K", UINT64_C(1024) }, + }, table_si[] = { + { "E", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) }, + { "P", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) }, + { "T", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) }, + { "G", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) }, + { "M", UINT64_C(1000)*UINT64_C(1000) }, + { "K", UINT64_C(1000) }, + }; + const suffix_table *table; + size_t n, i; + + assert_cc(ELEMENTSOF(table_iec) == ELEMENTSOF(table_si)); + + if (t == (uint64_t) -1) + return NULL; + + table = flag & FORMAT_BYTES_USE_IEC ? table_iec : table_si; + n = ELEMENTSOF(table_iec); + + for (i = 0; i < n; i++) + if (t >= table[i].factor) { + if (flag & FORMAT_BYTES_BELOW_POINT) { + snprintf(buf, l, + "%" PRIu64 ".%" PRIu64 "%s", + t / table[i].factor, + i != n - 1 ? + (t / table[i + 1].factor * UINT64_C(10) / table[n - 1].factor) % UINT64_C(10): + (t * UINT64_C(10) / table[i].factor) % UINT64_C(10), + table[i].suffix); + } else + snprintf(buf, l, + "%" PRIu64 "%s", + t / table[i].factor, + table[i].suffix); + + goto finish; + } + + snprintf(buf, l, "%" PRIu64 "%s", t, flag & FORMAT_BYTES_TRAILING_B ? "B" : ""); + +finish: + buf[l-1] = 0; + return buf; + +} diff --git a/shared/systemd/src/basic/format-util.h b/shared/systemd/src/basic/format-util.h new file mode 100644 index 00000000..e0d184a5 --- /dev/null +++ b/shared/systemd/src/basic/format-util.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <inttypes.h> +#include <net/if.h> +#include <stdbool.h> + +#if SIZEOF_PID_T == 4 +# define PID_PRI PRIi32 +#elif SIZEOF_PID_T == 2 +# define PID_PRI PRIi16 +#else +# error Unknown pid_t size +#endif +#define PID_FMT "%" PID_PRI + +#if SIZEOF_UID_T == 4 +# define UID_FMT "%" PRIu32 +#elif SIZEOF_UID_T == 2 +# define UID_FMT "%" PRIu16 +#else +# error Unknown uid_t size +#endif + +#if SIZEOF_GID_T == 4 +# define GID_FMT "%" PRIu32 +#elif SIZEOF_GID_T == 2 +# define GID_FMT "%" PRIu16 +#else +# error Unknown gid_t size +#endif + +#if SIZEOF_TIME_T == 8 +# define PRI_TIME PRIi64 +#elif SIZEOF_TIME_T == 4 +# define PRI_TIME "li" +#else +# error Unknown time_t size +#endif + +#if defined __x86_64__ && defined __ILP32__ +# define PRI_TIMEX PRIi64 +#else +# define PRI_TIMEX "li" +#endif + +#if SIZEOF_RLIM_T == 8 +# define RLIM_FMT "%" PRIu64 +#elif SIZEOF_RLIM_T == 4 +# define RLIM_FMT "%" PRIu32 +#else +# error Unknown rlim_t size +#endif + +#if SIZEOF_DEV_T == 8 +# define DEV_FMT "%" PRIu64 +#elif SIZEOF_DEV_T == 4 +# define DEV_FMT "%" PRIu32 +#else +# error Unknown dev_t size +#endif + +#if SIZEOF_INO_T == 8 +# define INO_FMT "%" PRIu64 +#elif SIZEOF_INO_T == 4 +# define INO_FMT "%" PRIu32 +#else +# error Unknown ino_t size +#endif + +char *format_ifname(int ifindex, char buf[static IF_NAMESIZE + 1]); + +typedef enum { + FORMAT_BYTES_USE_IEC = 1 << 0, + FORMAT_BYTES_BELOW_POINT = 1 << 1, + FORMAT_BYTES_TRAILING_B = 1 << 2, +} FormatBytesFlag; + +#define FORMAT_BYTES_MAX 8 +char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag); +static inline char *format_bytes(char *buf, size_t l, uint64_t t) { + return format_bytes_full(buf, l, t, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | FORMAT_BYTES_TRAILING_B); +} diff --git a/shared/systemd/src/basic/fs-util.c b/shared/systemd/src/basic/fs-util.c index be85eef1..56385fa2 100644 --- a/shared/systemd/src/basic/fs-util.c +++ b/shared/systemd/src/basic/fs-util.c @@ -218,113 +218,65 @@ int readlink_and_make_absolute(const char *p, char **r) { } int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { - char fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; _cleanup_close_ int fd = -1; - bool st_valid = false; - struct stat st; - int r; assert(path); - /* Under the assumption that we are running privileged we first change the access mode and only then - * hand out ownership to avoid a window where access is too open. */ - fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW); /* Let's acquire an O_PATH fd, as precaution to change * mode/owner on the same file */ if (fd < 0) return -errno; - xsprintf(fd_path, "/proc/self/fd/%i", fd); - - if (mode != MODE_INVALID) { - if ((mode & S_IFMT) != 0) { - - if (stat(fd_path, &st) < 0) - return -errno; - - if ((mode & S_IFMT) != (st.st_mode & S_IFMT)) - return -EINVAL; - - st_valid = true; - } - - if (chmod(fd_path, mode & 07777) < 0) { - r = -errno; - - if (!st_valid && stat(fd_path, &st) < 0) - return -errno; - - if ((mode & 07777) != (st.st_mode & 07777)) - return r; - - st_valid = true; - } - } - - if (uid != UID_INVALID || gid != GID_INVALID) { - if (chown(fd_path, uid, gid) < 0) { - r = -errno; - - if (!st_valid && stat(fd_path, &st) < 0) - return -errno; - - if (uid != UID_INVALID && st.st_uid != uid) - return r; - if (gid != GID_INVALID && st.st_gid != gid) - return r; - } - } - - return 0; + return fchmod_and_chown(fd, mode, uid, gid); } int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) { - bool st_valid = false; + bool do_chown, do_chmod; struct stat st; - int r; - /* Under the assumption that we are running privileged we first change the access mode and only then hand out - * ownership to avoid a window where access is too open. */ + /* Change ownership and access mode of the specified fd. Tries to do so safely, ensuring that at no + * point in time the access mode is above the old access mode under the old ownership or the new + * access mode under the new ownership. Note: this call tries hard to leave the access mode + * unaffected if the uid/gid is changed, i.e. it undoes implicit suid/sgid dropping the kernel does + * on chown(). + * + * This call is happy with O_PATH fds. */ - if (mode != MODE_INVALID) { - if ((mode & S_IFMT) != 0) { + if (fstat(fd, &st) < 0) + return -errno; - if (fstat(fd, &st) < 0) - return -errno; + do_chown = + (uid != UID_INVALID && st.st_uid != uid) || + (gid != GID_INVALID && st.st_gid != gid); - if ((mode & S_IFMT) != (st.st_mode & S_IFMT)) - return -EINVAL; + do_chmod = + !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */ + ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) || + do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown() + * modifies the access mode too */ - st_valid = true; - } + if (mode == MODE_INVALID) + mode = st.st_mode; /* If we only shall do a chown(), save original mode, since chown() might break it. */ + else if ((mode & S_IFMT) != 0 && ((mode ^ st.st_mode) & S_IFMT) != 0) + return -EINVAL; /* insist on the right file type if it was specified */ - if (fchmod(fd, mode & 07777) < 0) { - r = -errno; + if (do_chown && do_chmod) { + mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */ - if (!st_valid && fstat(fd, &st) < 0) + if (((minimal ^ st.st_mode) & 07777) != 0) + if (fchmod_opath(fd, minimal & 07777) < 0) return -errno; - - if ((mode & 07777) != (st.st_mode & 07777)) - return r; - - st_valid = true; - } } - if (uid != UID_INVALID || gid != GID_INVALID) - if (fchown(fd, uid, gid) < 0) { - r = -errno; - - if (!st_valid && fstat(fd, &st) < 0) - return -errno; + if (do_chown) + if (fchownat(fd, "", uid, gid, AT_EMPTY_PATH) < 0) + return -errno; - if (uid != UID_INVALID && st.st_uid != uid) - return r; - if (gid != GID_INVALID && st.st_gid != gid) - return r; - } + if (do_chmod) + if (fchmod_opath(fd, mode & 07777) < 0) + return -errno; - return 0; + return do_chown || do_chmod; } #endif /* NM_IGNORED */ @@ -411,13 +363,7 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi * something fchown(), fchmod(), futimensat() don't allow. */ xsprintf(fdpath, "/proc/self/fd/%i", fd); - if (mode != MODE_INVALID) - if (chmod(fdpath, mode) < 0) - ret = -errno; - - if (uid_is_valid(uid) || gid_is_valid(gid)) - if (chown(fdpath, uid, gid) < 0 && ret >= 0) - ret = -errno; + ret = fchmod_and_chown(fd, mode, uid, gid); if (stamp != USEC_INFINITY) { struct timespec ts[2]; @@ -1043,9 +989,9 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, /* Prefix what's left to do with what we just read, and start the loop again, but * remain in the current directory. */ - joined = strjoin(destination, todo); + joined = path_join(destination, todo); } else - joined = strjoin("/", destination, todo); + joined = path_join("/", destination, todo); if (!joined) return -ENOMEM; diff --git a/shared/systemd/src/basic/fs-util.h b/shared/systemd/src/basic/fs-util.h index b9651205..c5527cc4 100644 --- a/shared/systemd/src/basic/fs-util.h +++ b/shared/systemd/src/basic/fs-util.h @@ -7,12 +7,20 @@ #include <stdbool.h> #include <stdint.h> #include <sys/inotify.h> +#include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include "errno-util.h" #include "time-util.h" +#define MODE_INVALID ((mode_t) -1) + +/* The following macros add 1 when converting things, since 0 is a valid mode, while the pointer + * NULL is special */ +#define PTR_TO_MODE(p) ((mode_t) ((uintptr_t) (p)-1)) +#define MODE_TO_PTR(u) ((void *) ((uintptr_t) (u)+1)) + int unlink_noerrno(const char *path); int rmdir_parents(const char *path, const char *stop); diff --git a/shared/systemd/src/basic/hash-funcs.c b/shared/systemd/src/basic/hash-funcs.c index ec4f1de6..03695098 100644 --- a/shared/systemd/src/basic/hash-funcs.c +++ b/shared/systemd/src/basic/hash-funcs.c @@ -13,6 +13,9 @@ void string_hash_func(const char *p, struct siphash *state) { #if 0 /* NM_IGNORED */ DEFINE_HASH_OPS(string_hash_ops, char, string_hash_func, string_compare_func); +DEFINE_HASH_OPS_FULL(string_hash_ops_free_free, + char, string_hash_func, string_compare_func, free, + char, free); void path_hash_func(const char *q, struct siphash *state) { size_t n; diff --git a/shared/systemd/src/basic/hash-funcs.h b/shared/systemd/src/basic/hash-funcs.h index 3d2ae4b5..0d2d4283 100644 --- a/shared/systemd/src/basic/hash-funcs.h +++ b/shared/systemd/src/basic/hash-funcs.h @@ -76,6 +76,7 @@ struct hash_ops { void string_hash_func(const char *p, struct siphash *state); #define string_compare_func strcmp extern const struct hash_ops string_hash_ops; +extern const struct hash_ops string_hash_ops_free_free; void path_hash_func(const char *p, struct siphash *state); int path_compare_func(const char *a, const char *b) _pure_; diff --git a/shared/systemd/src/basic/hashmap.c b/shared/systemd/src/basic/hashmap.c index 9418dbd8..b1ae08cd 100644 --- a/shared/systemd/src/basic/hashmap.c +++ b/shared/systemd/src/basic/hashmap.c @@ -13,6 +13,7 @@ #include "macro.h" #include "memory-util.h" #include "mempool.h" +#include "missing.h" #include "process-util.h" #include "random-util.h" #include "set.h" @@ -287,7 +288,11 @@ _destructor_ static void cleanup_pools(void) { /* The pool is only allocated by the main thread, but the memory can * be passed to other threads. Let's clean up if we are the main thread * and no other threads are live. */ - if (!is_main_thread()) + /* We build our own is_main_thread() here, which doesn't use C11 + * TLS based caching of the result. That's because valgrind apparently + * doesn't like malloc() (which C11 TLS internally uses) to be called + * from a GCC destructors. */ + if (getpid() != gettid()) return; r = get_proc_field("/proc/self/status", "Threads", WHITESPACE, &t); @@ -730,8 +735,8 @@ bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const v return true; } -bool set_iterate(Set *s, Iterator *i, void **value) { - return internal_hashmap_iterate(HASHMAP_BASE(s), i, value, NULL); +bool set_iterate(const Set *s, Iterator *i, void **value) { + return internal_hashmap_iterate(HASHMAP_BASE((Set*) s), i, value, NULL); } #define HASHMAP_FOREACH_IDX(idx, h, i) \ @@ -1765,6 +1770,34 @@ int set_consume(Set *s, void *value) { return r; } +#if 0 /* NM_IGNORED */ +int hashmap_put_strdup(Hashmap **h, const char *k, const char *v) { + int r; + + r = hashmap_ensure_allocated(h, &string_hash_ops_free_free); + if (r < 0) + return r; + + _cleanup_free_ char *kdup = NULL, *vdup = NULL; + kdup = strdup(k); + vdup = strdup(v); + if (!kdup || !vdup) + return -ENOMEM; + + r = hashmap_put(*h, kdup, vdup); + if (r < 0) { + if (r == -EEXIST && streq(v, hashmap_get(*h, kdup))) + return 0; + return r; + } + + assert(r > 0); /* 0 would mean vdup is already in the hashmap, which cannot be */ + kdup = vdup = NULL; + + return 0; +} +#endif /* NM_IGNORED */ + int set_put_strdup(Set *s, const char *p) { char *c; diff --git a/shared/systemd/src/basic/hashmap.h b/shared/systemd/src/basic/hashmap.h index 41c8adb1..65adc925 100644 --- a/shared/systemd/src/basic/hashmap.h +++ b/shared/systemd/src/basic/hashmap.h @@ -76,7 +76,7 @@ typedef struct { #if ENABLE_DEBUG_HASHMAP # define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line -# define HASHMAP_DEBUG_SRC_ARGS , __func__, __FILE__, __LINE__ +# define HASHMAP_DEBUG_SRC_ARGS , __func__, PROJECT_FILE, __LINE__ # define HASHMAP_DEBUG_PASS_ARGS , func, file, line #else # define HASHMAP_DEBUG_PARAMS @@ -147,6 +147,8 @@ static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void * return hashmap_put(PLAIN_HASHMAP(h), key, value); } +int hashmap_put_strdup(Hashmap **h, const char *k, const char *v); + int hashmap_update(Hashmap *h, const void *key, void *value); static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) { return hashmap_update(PLAIN_HASHMAP(h), key, value); diff --git a/shared/systemd/src/basic/hexdecoct.c b/shared/systemd/src/basic/hexdecoct.c index c81c09e8..36174ec3 100644 --- a/shared/systemd/src/basic/hexdecoct.c +++ b/shared/systemd/src/basic/hexdecoct.c @@ -110,10 +110,12 @@ static int unhex_next(const char **p, size_t *l) { return r; } -int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) { +int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_len) { _cleanup_free_ uint8_t *buf = NULL; + size_t buf_size; const char *x; uint8_t *z; + int r; assert(ret); assert(ret_len); @@ -123,7 +125,8 @@ int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) { l = strlen(p); /* Note that the calculation of memory size is an upper boundary, as we ignore whitespace while decoding */ - buf = malloc((l + 1) / 2 + 1); + buf_size = (l + 1) / 2 + 1; + buf = malloc(buf_size); if (!buf) return -ENOMEM; @@ -133,12 +136,16 @@ int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) { a = unhex_next(&x, &l); if (a == -EPIPE) /* End of string */ break; - if (a < 0) - return a; + if (a < 0) { + r = a; + goto on_failure; + } b = unhex_next(&x, &l); - if (b < 0) - return b; + if (b < 0) { + r = b; + goto on_failure; + } *(z++) = (uint8_t) a << 4 | (uint8_t) b; } @@ -149,6 +156,12 @@ int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) { *ret = TAKE_PTR(buf); return 0; + +on_failure: + if (secure) + explicit_bzero_safe(buf, buf_size); + + return r; } #if 0 /* NM_IGNORED */ diff --git a/shared/systemd/src/basic/hexdecoct.h b/shared/systemd/src/basic/hexdecoct.h index fa6013ee..dfdff1e9 100644 --- a/shared/systemd/src/basic/hexdecoct.h +++ b/shared/systemd/src/basic/hexdecoct.h @@ -18,7 +18,10 @@ char hexchar(int x) _const_; int unhexchar(char c) _const_; char *hexmem(const void *p, size_t l); -int unhexmem(const char *p, size_t l, void **mem, size_t *len); +int unhexmem_full(const char *p, size_t l, bool secure, void **mem, size_t *len); +static inline int unhexmem(const char *p, size_t l, void **mem, size_t *len) { + return unhexmem_full(p, l, false, mem, len); +} char base32hexchar(int x) _const_; int unbase32hexchar(char c) _const_; diff --git a/shared/systemd/src/basic/in-addr-util.c b/shared/systemd/src/basic/in-addr-util.c index 5899f62f..91d687c2 100644 --- a/shared/systemd/src/basic/in-addr-util.c +++ b/shared/systemd/src/basic/in-addr-util.c @@ -11,6 +11,7 @@ #include <stdlib.h> #include "alloc-util.h" +#include "errno-util.h" #include "in-addr-util.h" #include "macro.h" #include "parse-util.h" @@ -93,12 +94,19 @@ int in_addr_is_localhost(int family, const union in_addr_union *u) { return -EAFNOSUPPORT; } +bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b) { + assert(a); + assert(b); + + return a->s_addr == b->s_addr; +} + int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) { assert(a); assert(b); if (family == AF_INET) - return a->in.s_addr == b->in.s_addr; + return in4_addr_equal(&a->in, &b->in); if (family == AF_INET6) return @@ -319,7 +327,7 @@ int in_addr_to_string(int family, const union in_addr_union *u, char **ret) { errno = 0; if (!inet_ntop(family, u, x, l)) - return errno > 0 ? -errno : -EINVAL; + return errno_or_else(EINVAL); *ret = TAKE_PTR(x); return 0; @@ -350,7 +358,7 @@ int in_addr_prefix_to_string(int family, const union in_addr_union *u, unsigned errno = 0; if (!inet_ntop(family, u, x, l)) - return errno > 0 ? -errno : -EINVAL; + return errno_or_else(EINVAL); p = x + strlen(x); l -= strlen(x); @@ -390,7 +398,7 @@ int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifin errno = 0; if (!inet_ntop(family, u, x, l)) - return errno > 0 ? -errno : -EINVAL; + return errno_or_else(EINVAL); sprintf(strchr(x, 0), "%%%i", ifindex); @@ -410,7 +418,7 @@ int in_addr_from_string(int family, const char *s, union in_addr_union *ret) { errno = 0; if (inet_pton(family, s, ret ?: &buffer) <= 0) - return errno > 0 ? -errno : -EINVAL; + return errno_or_else(EINVAL); return 0; } @@ -747,4 +755,16 @@ static int in_addr_data_compare_func(const struct in_addr_data *x, const struct } DEFINE_HASH_OPS(in_addr_data_hash_ops, struct in_addr_data, in_addr_data_hash_func, in_addr_data_compare_func); + +static void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state) { + assert(addr); + + siphash24_compress(addr, sizeof(*addr), state); +} + +static int in6_addr_compare_func(const struct in6_addr *a, const struct in6_addr *b) { + return memcmp(a, b, sizeof(*a)); +} + +DEFINE_HASH_OPS(in6_addr_hash_ops, struct in6_addr, in6_addr_hash_func, in6_addr_compare_func); #endif /* NM_IGNORED */ diff --git a/shared/systemd/src/basic/in-addr-util.h b/shared/systemd/src/basic/in-addr-util.h index a6a685b9..28afc7d8 100644 --- a/shared/systemd/src/basic/in-addr-util.h +++ b/shared/systemd/src/basic/in-addr-util.h @@ -32,6 +32,7 @@ int in_addr_is_localhost(int family, const union in_addr_union *u); bool in4_addr_is_non_local(const struct in_addr *a); +bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b); int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b); int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen); int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen); @@ -72,3 +73,4 @@ static inline size_t FAMILY_ADDRESS_SIZE(int family) { #define IN_ADDR_NULL ((union in_addr_union) { .in6 = {} }) extern const struct hash_ops in_addr_data_hash_ops; +extern const struct hash_ops in6_addr_hash_ops; diff --git a/shared/systemd/src/basic/io-util.c b/shared/systemd/src/basic/io-util.c index 3f47eff5..9669c463 100644 --- a/shared/systemd/src/basic/io-util.c +++ b/shared/systemd/src/basic/io-util.c @@ -264,9 +264,91 @@ ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) { char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value) { char *x; - x = strappend(field, value); + x = strjoin(field, value); if (x) iovec[(*n_iovec)++] = IOVEC_MAKE_STRING(x); return x; } + +char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value) { + char *x; + + x = set_iovec_string_field(iovec, n_iovec, field, value); + free(value); + return x; +} + +struct iovec_wrapper *iovw_new(void) { + return malloc0(sizeof(struct iovec_wrapper)); +} + +void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors) { + if (free_vectors) + for (size_t i = 0; i < iovw->count; i++) + free(iovw->iovec[i].iov_base); + + iovw->iovec = mfree(iovw->iovec); + iovw->count = 0; + iovw->size_bytes = 0; +} + +struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw) { + iovw_free_contents(iovw, true); + + return mfree(iovw); +} + +struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw) { + iovw_free_contents(iovw, false); + + return mfree(iovw); +} + +int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { + if (iovw->count >= IOV_MAX) + return -E2BIG; + + if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1)) + return log_oom(); + + iovw->iovec[iovw->count++] = IOVEC_MAKE(data, len); + return 0; +} + +int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value) { + _cleanup_free_ char *x = NULL; + int r; + + x = strjoin(field, value); + if (!x) + return log_oom(); + + r = iovw_put(iovw, x, strlen(x)); + if (r >= 0) + TAKE_PTR(x); + + return r; +} + +int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value) { + _cleanup_free_ _unused_ char *free_ptr = value; + + return iovw_put_string_field(iovw, field, value); +} + +void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) { + size_t i; + + for (i = 0; i < iovw->count; i++) + iovw->iovec[i].iov_base = (char *)iovw->iovec[i].iov_base - old + new; +} + +size_t iovw_size(struct iovec_wrapper *iovw) { + size_t n = 0, i; + + for (i = 0; i < iovw->count; i++) + n += iovw->iovec[i].iov_len; + + return n; +} #endif /* NM_IGNORED */ diff --git a/shared/systemd/src/basic/io-util.h b/shared/systemd/src/basic/io-util.h index 792a64ad..719e19e8 100644 --- a/shared/systemd/src/basic/io-util.h +++ b/shared/systemd/src/basic/io-util.h @@ -73,3 +73,20 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) { #define IOVEC_MAKE_STRING(string) (struct iovec) IOVEC_INIT_STRING(string) char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value); +char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value); + +struct iovec_wrapper { + struct iovec *iovec; + size_t count; + size_t size_bytes; +}; + +struct iovec_wrapper *iovw_new(void); +struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw); +struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw); +void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors); +int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len); +int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value); +int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value); +void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new); +size_t iovw_size(struct iovec_wrapper *iovw); diff --git a/shared/systemd/src/basic/log.h b/shared/systemd/src/basic/log.h index 98c5f4d7..b81c93a5 100644 --- a/shared/systemd/src/basic/log.h +++ b/shared/systemd/src/basic/log.h @@ -75,6 +75,12 @@ int log_get_max_level_realm(LogRealm realm) _pure_; * for the application itself. */ +#if 0 /* NM_IGNORED */ +assert_cc(STRLEN(__FILE__) > STRLEN(RELATIVE_SOURCE_PATH) + 1); +#define PROJECT_FILE (__FILE__ + STRLEN(RELATIVE_SOURCE_PATH) + 1) +#endif /* NM_IGNORED */ +#define PROJECT_FILE __FILE__ + int log_open(void); void log_close(void); void log_forget_fds(void); @@ -201,7 +207,6 @@ _noreturn_ void log_assert_failed_realm( #define log_assert_failed(text, ...) \ log_assert_failed_realm(LOG_REALM, (text), __VA_ARGS__) - _noreturn_ void log_assert_failed_unreachable_realm( LogRealm realm, const char *text, @@ -221,7 +226,7 @@ void log_assert_failed_return_realm( log_assert_failed_return_realm(LOG_REALM, (text), __VA_ARGS__) #define log_dispatch(level, error, buffer) \ - log_dispatch_internal(level, error, __FILE__, __LINE__, __func__, NULL, NULL, NULL, NULL, buffer) + log_dispatch_internal(level, error, PROJECT_FILE, __LINE__, __func__, NULL, NULL, NULL, NULL, buffer) #endif /* NM_IGNORED */ /* Logging with level */ @@ -230,7 +235,7 @@ void log_assert_failed_return_realm( int _level = (level), _e = (error), _realm = (realm); \ (log_get_max_level_realm(_realm) >= LOG_PRI(_level)) \ ? log_internal_realm(LOG_REALM_PLUS_LEVEL(_realm, _level), _e, \ - __FILE__, __LINE__, __func__, __VA_ARGS__) \ + PROJECT_FILE, __LINE__, __func__, __VA_ARGS__) \ : -ERRNO_VALUE(_e); \ }) @@ -266,20 +271,20 @@ int log_emergency_level(void); /* Structured logging */ #define log_struct_errno(level, error, ...) \ log_struct_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \ - error, __FILE__, __LINE__, __func__, __VA_ARGS__, NULL) + error, PROJECT_FILE, __LINE__, __func__, __VA_ARGS__, NULL) #define log_struct(level, ...) log_struct_errno(level, 0, __VA_ARGS__) #define log_struct_iovec_errno(level, error, iovec, n_iovec) \ log_struct_iovec_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \ - error, __FILE__, __LINE__, __func__, iovec, n_iovec) + error, PROJECT_FILE, __LINE__, __func__, iovec, n_iovec) #define log_struct_iovec(level, iovec, n_iovec) log_struct_iovec_errno(level, 0, iovec, n_iovec) /* This modifies the buffer passed! */ #define log_dump(level, buffer) \ log_dump_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \ - 0, __FILE__, __LINE__, __func__, buffer) + 0, PROJECT_FILE, __LINE__, __func__, buffer) -#define log_oom() log_oom_internal(LOG_REALM, __FILE__, __LINE__, __func__) +#define log_oom() log_oom_internal(LOG_REALM, PROJECT_FILE, __LINE__, __func__) bool log_on_console(void) _pure_; @@ -307,6 +312,7 @@ void log_set_prohibit_ipc(bool b); int log_dup_console(void); +#if 0 /* NM_IGNORED */ int log_syntax_internal( const char *unit, int level, @@ -317,6 +323,9 @@ int log_syntax_internal( int line, const char *func, const char *format, ...) _printf_(9, 10); +#endif /* NM_IGNORED */ +#define log_syntax_internal(unit, level, config_file, config_line, error, file, line, func, format, ...) \ + log_internal_realm((level), (error), file, (line), (func), "syntax[%s]: "format, (config_file), __VA_ARGS__) \ int log_syntax_invalid_utf8_internal( const char *unit, @@ -332,7 +341,7 @@ int log_syntax_invalid_utf8_internal( ({ \ int _level = (level), _e = (error); \ (log_get_max_level() >= LOG_PRI(_level)) \ - ? log_internal_realm(_level, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \ + ? log_syntax_internal(unit, _level, config_file, config_line, _e, PROJECT_FILE, __LINE__, __func__, __VA_ARGS__) \ : -ERRNO_VALUE(_e); \ }) @@ -340,7 +349,7 @@ int log_syntax_invalid_utf8_internal( ({ \ int _level = (level); \ (log_get_max_level() >= LOG_PRI(_level)) \ - ? log_syntax_invalid_utf8_internal(unit, _level, config_file, config_line, __FILE__, __LINE__, __func__, rvalue) \ + ? log_syntax_invalid_utf8_internal(unit, _level, config_file, config_line, PROJECT_FILE, __LINE__, __func__, rvalue) \ : -EINVAL; \ }) diff --git a/shared/systemd/src/basic/macro.h b/shared/systemd/src/basic/macro.h index 6a1fdab5..43c51326 100644 --- a/shared/systemd/src/basic/macro.h +++ b/shared/systemd/src/basic/macro.h @@ -106,6 +106,15 @@ _Pragma("GCC diagnostic push"); \ _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"") +#if HAVE_WSTRINGOP_TRUNCATION +# define DISABLE_WARNING_STRINGOP_TRUNCATION \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"") +#else +# define DISABLE_WARNING_STRINGOP_TRUNCATION \ + _Pragma("GCC diagnostic push") +#endif + #define REENABLE_WARNING \ _Pragma("GCC diagnostic pop") #else @@ -324,12 +333,12 @@ static inline int __coverity_check__(int condition) { #define assert_message_se(expr, message) \ do { \ if (_unlikely_(!(expr))) \ - log_assert_failed(message, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + log_assert_failed(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__); \ } while (false) #define assert_log(expr, message) ((_likely_(expr)) \ ? (true) \ - : (log_assert_failed_return(message, __FILE__, __LINE__, __PRETTY_FUNCTION__), false)) + : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__), false)) #endif /* __COVERITY__ */ @@ -344,18 +353,16 @@ static inline int __coverity_check__(int condition) { #endif #define assert_not_reached(t) \ - do { \ - log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ - } while (false) + log_assert_failed_unreachable(t, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__) #if defined(static_assert) #define assert_cc(expr) \ - static_assert(expr, #expr); + static_assert(expr, #expr) #else #define assert_cc(expr) \ struct CONCATENATE(_assert_struct_, __COUNTER__) { \ char x[(expr) ? 0 : -1]; \ - }; + } #endif #define assert_return(expr, r) \ @@ -464,7 +471,8 @@ static inline int __coverity_check__(int condition) { * type for the array, in the hope that checkers such as ubsan don't complain that the initializers for \ * the array are not representable by the base type. Ideally we'd use typeof(x) as base type, but that \ * doesn't work, as we want to use this on bitfields and gcc refuses typeof() on bitfields.) */ \ - assert_cc((sizeof((long double[]){__VA_ARGS__})/sizeof(long double)) <= 20); \ + static const long double __assert_in_set[] _unused_ = { __VA_ARGS__ }; \ + assert_cc(ELEMENTSOF(__assert_in_set) <= 20); \ switch(x) { \ FOR_EACH_MAKE_CASE(__VA_ARGS__) \ _found = true; \ diff --git a/shared/systemd/src/basic/memory-util.h b/shared/systemd/src/basic/memory-util.h index 915c24a5..9cb8ac3c 100644 --- a/shared/systemd/src/basic/memory-util.h +++ b/shared/systemd/src/basic/memory-util.h @@ -2,6 +2,7 @@ #pragma once #include <inttypes.h> +#include <malloc.h> #include <stdbool.h> #include <string.h> #include <sys/types.h> @@ -37,8 +38,8 @@ static inline int memcmp_nn(const void *s1, size_t n1, const void *s2, size_t n2 #define memzero(x,l) \ ({ \ size_t _l_ = (l); \ - void *_x_ = (x); \ - _l_ == 0 ? _x_ : memset(_x_, 0, _l_); \ + if (_l_ > 0) \ + memset(x, 0, _l_); \ }) #define zero(x) (memzero(&(x), sizeof(x))) @@ -78,6 +79,16 @@ static inline void* explicit_bzero_safe(void *p, size_t l) { void *explicit_bzero_safe(void *p, size_t l); #endif +static inline void erase_and_freep(void *p) { + void *ptr = *(void**) p; + + if (ptr) { + size_t l = malloc_usable_size(ptr); + explicit_bzero_safe(ptr, l); + free(ptr); + } +} + /* Use with _cleanup_ to erase a single 'char' when leaving scope */ static inline void erase_char(char *p) { explicit_bzero_safe(p, sizeof(char)); diff --git a/shared/systemd/src/basic/parse-util.c b/shared/systemd/src/basic/parse-util.c index 02ef426f..76ef6e09 100644 --- a/shared/systemd/src/basic/parse-util.c +++ b/shared/systemd/src/basic/parse-util.c @@ -6,6 +6,7 @@ #include <inttypes.h> #include <linux/oom.h> #include <locale.h> +#include <net/if.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -86,6 +87,9 @@ int parse_mode(const char *s, mode_t *ret) { int parse_ifindex(const char *s, int *ret) { int ifi, r; + assert(s); + assert(ret); + r = safe_atoi(s, &ifi); if (r < 0) return r; @@ -96,6 +100,24 @@ int parse_ifindex(const char *s, int *ret) { return 0; } +int parse_ifindex_or_ifname(const char *s, int *ret) { + int r; + + assert(s); + assert(ret); + + r = parse_ifindex(s, ret); + if (r >= 0) + return r; + + r = (int) if_nametoindex(s); + if (r <= 0) + return -errno; + + *ret = r; + return 0; +} + int parse_mtu(int family, const char *s, uint32_t *ret) { uint64_t u; size_t m; @@ -342,47 +364,6 @@ int parse_syscall_and_errno(const char *in, char **name, int *error) { return 0; } - -char *format_bytes(char *buf, size_t l, uint64_t t) { - unsigned i; - - /* This only does IEC units so far */ - - static const struct { - const char *suffix; - uint64_t factor; - } table[] = { - { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "M", UINT64_C(1024)*UINT64_C(1024) }, - { "K", UINT64_C(1024) }, - }; - - if (t == (uint64_t) -1) - return NULL; - - for (i = 0; i < ELEMENTSOF(table); i++) { - - if (t >= table[i].factor) { - snprintf(buf, l, - "%" PRIu64 ".%" PRIu64 "%s", - t / table[i].factor, - ((t*UINT64_C(10)) / table[i].factor) % UINT64_C(10), - table[i].suffix); - - goto finish; - } - } - - snprintf(buf, l, "%" PRIu64 "B", t); - -finish: - buf[l-1] = 0; - return buf; - -} #endif /* NM_IGNORED */ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) { diff --git a/shared/systemd/src/basic/parse-util.h b/shared/systemd/src/basic/parse-util.h index e47641b4..3a70b792 100644 --- a/shared/systemd/src/basic/parse-util.h +++ b/shared/systemd/src/basic/parse-util.h @@ -9,13 +9,12 @@ #include "macro.h" -#define MODE_INVALID ((mode_t) -1) - int parse_boolean(const char *v) _pure_; int parse_dev(const char *s, dev_t *ret); int parse_pid(const char *s, pid_t* ret_pid); int parse_mode(const char *s, mode_t *ret); int parse_ifindex(const char *s, int *ret); +int parse_ifindex_or_ifname(const char *s, int *ret); int parse_mtu(int family, const char *s, uint32_t *ret); int parse_size(const char *t, uint64_t base, uint64_t *size); @@ -23,9 +22,6 @@ int parse_range(const char *t, unsigned *lower, unsigned *upper); int parse_errno(const char *t); int parse_syscall_and_errno(const char *in, char **name, int *error); -#define FORMAT_BYTES_MAX 8 -char *format_bytes(char *buf, size_t l, uint64_t t); - int safe_atou_full(const char *s, unsigned base, unsigned *ret_u); static inline int safe_atou(const char *s, unsigned *ret_u) { diff --git a/shared/systemd/src/basic/path-util.c b/shared/systemd/src/basic/path-util.c index 29955a36..e39656bc 100644 --- a/shared/systemd/src/basic/path-util.c +++ b/shared/systemd/src/basic/path-util.c @@ -71,10 +71,7 @@ char *path_make_absolute(const char *p, const char *prefix) { if (path_is_absolute(p) || isempty(prefix)) return strdup(p); - if (endswith(prefix, "/")) - return strjoin(prefix, p); - else - return strjoin(prefix, "/", p); + return path_join(prefix, p); } int safe_getcwd(char **ret) { @@ -213,6 +210,18 @@ int path_make_relative(const char *from_dir, const char *to_path, char **_r) { return 0; } +char* path_startswith_strv(const char *p, char **set) { + char **s, *t; + + STRV_FOREACH(s, set) { + t = path_startswith(p, *s); + if (t) + return t; + } + + return NULL; +} + int path_strv_make_absolute_cwd(char **l) { char **s; int r; @@ -259,7 +268,7 @@ char **path_strv_resolve(char **l, const char *root) { if (root) { orig = *s; - t = prefix_root(root, orig); + t = path_join(root, orig); if (!t) { enomem = true; continue; @@ -389,6 +398,54 @@ char *path_simplify(char *path, bool kill_dots) { return path; } +#if 0 /* NM_IGNORED */ +int path_simplify_and_warn( + char *path, + unsigned flag, + const char *unit, + const char *filename, + unsigned line, + const char *lvalue) { + + bool fatal = flag & PATH_CHECK_FATAL; + + assert(!FLAGS_SET(flag, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)); + + if (!utf8_is_valid(path)) + return log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, path); + + if (flag & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) { + bool absolute; + + absolute = path_is_absolute(path); + + if (!absolute && (flag & PATH_CHECK_ABSOLUTE)) + return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + "%s= path is not absolute%s: %s", + lvalue, fatal ? "" : ", ignoring", path); + + if (absolute && (flag & PATH_CHECK_RELATIVE)) + return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + "%s= path is absolute%s: %s", + lvalue, fatal ? "" : ", ignoring", path); + } + + path_simplify(path, true); + + if (!path_is_valid(path)) + return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + "%s= path has invalid length (%zu bytes)%s.", + lvalue, strlen(path), fatal ? "" : ", ignoring"); + + if (!path_is_normalized(path)) + return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + "%s= path is not normalized%s: %s", + lvalue, fatal ? "" : ", ignoring", path); + + return 0; +} +#endif /* NM_IGNORED */ + char* path_startswith(const char *path, const char *prefix) { assert(path); assert(prefix); @@ -586,7 +643,7 @@ int find_binary(const char *name, char **ret) { if (!path_is_absolute(element)) continue; - j = strjoin(element, "/", name); + j = path_join(element, name); if (!j) return -ENOMEM; @@ -691,40 +748,6 @@ int mkfs_exists(const char *fstype) { return binary_is_good(mkfs); } -char *prefix_root(const char *root, const char *path) { - char *n, *p; - size_t l; - - /* If root is passed, prefixes path with it. Otherwise returns - * it as is. */ - - assert(path); - - /* First, drop duplicate prefixing slashes from the path */ - while (path[0] == '/' && path[1] == '/') - path++; - - if (empty_or_root(root)) - return strdup(path); - - l = strlen(root) + 1 + strlen(path) + 1; - - n = new(char, l); - if (!n) - return NULL; - - p = stpcpy(n, root); - - while (p > n && p[-1] == '/') - p--; - - if (path[0] != '/') - *(p++) = '/'; - - strcpy(p, path); - return n; -} - int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) { char *p; int r; @@ -1034,7 +1057,7 @@ int systemd_installation_has_version(const char *root, unsigned minimal_version) _cleanup_free_ char *path = NULL; char *c, **name; - path = prefix_root(root, pattern); + path = path_join(root, pattern); if (!path) return -ENOMEM; @@ -1104,50 +1127,4 @@ bool empty_or_root(const char *root) { return root[strspn(root, "/")] == 0; } - -int path_simplify_and_warn( - char *path, - unsigned flag, - const char *unit, - const char *filename, - unsigned line, - const char *lvalue) { - - bool fatal = flag & PATH_CHECK_FATAL; - - assert(!FLAGS_SET(flag, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)); - - if (!utf8_is_valid(path)) - return log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, path); - - if (flag & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) { - bool absolute; - - absolute = path_is_absolute(path); - - if (!absolute && (flag & PATH_CHECK_ABSOLUTE)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), - "%s= path is not absolute%s: %s", - lvalue, fatal ? "" : ", ignoring", path); - - if (absolute && (flag & PATH_CHECK_RELATIVE)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), - "%s= path is absolute%s: %s", - lvalue, fatal ? "" : ", ignoring", path); - } - - path_simplify(path, true); - - if (!path_is_valid(path)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), - "%s= path has invalid length (%zu bytes)%s.", - lvalue, strlen(path), fatal ? "" : ", ignoring"); - - if (!path_is_normalized(path)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), - "%s= path is not normalized%s: %s", - lvalue, fatal ? "" : ", ignoring", path); - - return 0; -} #endif /* NM_IGNORED */ diff --git a/shared/systemd/src/basic/path-util.h b/shared/systemd/src/basic/path-util.h index 5204adaa..cd6216bb 100644 --- a/shared/systemd/src/basic/path-util.h +++ b/shared/systemd/src/basic/path-util.h @@ -56,6 +56,14 @@ char* path_join_internal(const char *first, ...); char* path_simplify(char *path, bool kill_dots); +enum { + PATH_CHECK_FATAL = 1 << 0, /* If not set, then error message is appended with 'ignoring'. */ + PATH_CHECK_ABSOLUTE = 1 << 1, + PATH_CHECK_RELATIVE = 1 << 2, +}; + +int path_simplify_and_warn(char *path, unsigned flag, const char *unit, const char *filename, unsigned line, const char *lvalue); + static inline bool path_equal_ptr(const char *a, const char *b) { return !!a == !!b && (!a || path_equal(a, b)); } @@ -73,17 +81,8 @@ static inline bool path_equal_ptr(const char *a, const char *b) { _found; \ }) -#define PATH_STARTSWITH_SET(p, ...) \ - ({ \ - const char *_p = (p); \ - char *_found = NULL, **_i; \ - STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) { \ - _found = path_startswith(_p, *_i); \ - if (_found) \ - break; \ - } \ - _found; \ - }) +char* path_startswith_strv(const char *p, char **set); +#define PATH_STARTSWITH_SET(p, ...) path_startswith_strv(p, STRV_MAKE(__VA_ARGS__)) int path_strv_make_absolute_cwd(char **l); char** path_strv_resolve(char **l, const char *root); @@ -118,10 +117,8 @@ int mkfs_exists(const char *fstype); _slash && ((*_slash = 0), true); \ _slash = strrchr((prefix), '/')) -char *prefix_root(const char *root, const char *path); - -/* Similar to prefix_root(), but returns an alloca() buffer, or - * possibly a const pointer into the path parameter */ +/* Similar to path_join(), but only works for two components, and only the first one may be NULL and returns + * an alloca() buffer, or possibly a const pointer into the path parameter. */ #define prefix_roota(root, path) \ ({ \ const char* _path = (path), *_root = (root), *_ret; \ @@ -129,7 +126,7 @@ char *prefix_root(const char *root, const char *path); size_t _l; \ while (_path[0] == '/' && _path[1] == '/') \ _path ++; \ - if (empty_or_root(_root)) \ + if (isempty(_root)) \ _ret = _path; \ else { \ _l = strlen(_root) + 1 + strlen(_path) + 1; \ @@ -182,11 +179,3 @@ bool empty_or_root(const char *root); static inline const char *empty_to_root(const char *path) { return isempty(path) ? "/" : path; } - -enum { - PATH_CHECK_FATAL = 1 << 0, /* If not set, then error message is appended with 'ignoring'. */ - PATH_CHECK_ABSOLUTE = 1 << 1, - PATH_CHECK_RELATIVE = 1 << 2, -}; - -int path_simplify_and_warn(char *path, unsigned flag, const char *unit, const char *filename, unsigned line, const char *lvalue); diff --git a/shared/systemd/src/basic/process-util.c b/shared/systemd/src/basic/process-util.c index 7431be3e..317815fe 100644 --- a/shared/systemd/src/basic/process-util.c +++ b/shared/systemd/src/basic/process-util.c @@ -6,11 +6,9 @@ #include <errno.h> #include <limits.h> #include <linux/oom.h> -#include <sched.h> #include <signal.h> #include <stdbool.h> #include <stdio.h> -#include <stdio_ext.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> @@ -30,10 +28,12 @@ #include "alloc-util.h" #include "architecture.h" #include "escape.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "ioprio.h" +#include "locale-util.h" #include "log.h" #include "macro.h" #include "memory-util.h" @@ -48,9 +48,16 @@ #include "string-util.h" #include "terminal-util.h" #include "user-util.h" +#include "utf8.h" #if 0 /* NM_IGNORED */ -int get_process_state(pid_t pid) { + +/* The kernel limits userspace processes to TASK_COMM_LEN (16 bytes), but allows higher values for its own + * workers, e.g. "kworker/u9:3-kcryptd/253:0". Let's pick a fixed smallish limit that will work for the kernel. + */ +#define COMM_MAX_LEN 128 + +static int get_process_state(pid_t pid) { const char *p; char state; int r; @@ -86,7 +93,7 @@ int get_process_comm(pid_t pid, char **ret) { assert(ret); assert(pid >= 0); - escaped = new(char, TASK_COMM_LEN); + escaped = new(char, COMM_MAX_LEN); if (!escaped) return -ENOMEM; @@ -99,167 +106,92 @@ int get_process_comm(pid_t pid, char **ret) { return r; /* Escape unprintable characters, just in case, but don't grow the string beyond the underlying size */ - cellescape(escaped, TASK_COMM_LEN, comm); + cellescape(escaped, COMM_MAX_LEN, comm); *ret = TAKE_PTR(escaped); return 0; } -int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { +int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **line) { _cleanup_fclose_ FILE *f = NULL; - bool space = false; - char *k; - _cleanup_free_ char *ans = NULL; + _cleanup_free_ char *t = NULL, *ans = NULL; const char *p; - int c; + int r; + size_t k; + + /* This is supposed to be a safety guard against runaway command lines. */ + size_t max_length = sc_arg_max(); assert(line); assert(pid >= 0); - /* Retrieves a process' command line. Replaces unprintable characters while doing so by whitespace (coalescing - * multiple sequential ones into one). If max_length is != 0 will return a string of the specified size at most - * (the trailing NUL byte does count towards the length here!), abbreviated with a "..." ellipsis. If - * comm_fallback is true and the process has no command line set (the case for kernel threads), or has a - * command line that resolves to the empty string will return the "comm" name of the process instead. + /* Retrieves a process' command line. Replaces non-utf8 bytes by replacement character (�). If + * max_columns is != -1 will return a string of the specified console width at most, abbreviated with + * an ellipsis. If PROCESS_CMDLINE_COMM_FALLBACK is specified in flags and the process has no command + * line set (the case for kernel threads), or has a command line that resolves to the empty string + * will return the "comm" name of the process instead. This will use at most _SC_ARG_MAX bytes of + * input data. * * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and * comm_fallback is false). Returns 0 and sets *line otherwise. */ p = procfs_file_alloca(pid, "cmdline"); + r = fopen_unlocked(p, "re", &f); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); - - if (max_length == 0) { - /* This is supposed to be a safety guard against runaway command lines. */ - long l = sysconf(_SC_ARG_MAX); - assert(l > 0); - max_length = l; - } - - if (max_length == 1) { + /* We assume that each four-byte character uses one or two columns. If we ever check for combining + * characters, this assumption will need to be adjusted. */ + if ((size_t) 4 * max_columns + 1 < max_columns) + max_length = MIN(max_length, (size_t) 4 * max_columns + 1); - /* If there's only room for one byte, return the empty string */ - ans = new0(char, 1); - if (!ans) - return -ENOMEM; + t = new(char, max_length); + if (!t) + return -ENOMEM; - *line = TAKE_PTR(ans); - return 0; + k = fread(t, 1, max_length, f); + if (k > 0) { + /* Arguments are separated by NULs. Let's replace those with spaces. */ + for (size_t i = 0; i < k - 1; i++) + if (t[i] == '\0') + t[i] = ' '; + t[k] = '\0'; /* Normally, t[k] is already NUL, so this is just a guard in case of short read */ } else { - bool dotdotdot = false; - size_t left; - - ans = new(char, max_length); - if (!ans) - return -ENOMEM; - - k = ans; - left = max_length; - while ((c = getc(f)) != EOF) { - - if (isprint(c)) { - - if (space) { - if (left <= 2) { - dotdotdot = true; - break; - } - - *(k++) = ' '; - left--; - space = false; - } - - if (left <= 1) { - dotdotdot = true; - break; - } - - *(k++) = (char) c; - left--; - } else if (k > ans) - space = true; - } - - if (dotdotdot) { - if (max_length <= 4) { - k = ans; - left = max_length; - } else { - k = ans + max_length - 4; - left = 4; - - /* Eat up final spaces */ - while (k > ans && isspace(k[-1])) { - k--; - left++; - } - } - - strncpy(k, "...", left-1); - k[left-1] = 0; - } else - *k = 0; - } - - /* Kernel threads have no argv[] */ - if (isempty(ans)) { - _cleanup_free_ char *t = NULL; - int h; - - ans = mfree(ans); + /* We only treat getting nothing as an error. We *could* also get an error after reading some + * data, but we ignore that case, as such an error is rather unlikely and we prefer to get + * some data rather than none. */ + if (ferror(f)) + return -errno; - if (!comm_fallback) + if (!(flags & PROCESS_CMDLINE_COMM_FALLBACK)) return -ENOENT; - h = get_process_comm(pid, &t); - if (h < 0) - return h; + /* Kernel threads have no argv[] */ + _cleanup_free_ char *t2 = NULL; - size_t l = strlen(t); - - if (l + 3 <= max_length) { - ans = strjoin("[", t, "]"); - if (!ans) - return -ENOMEM; + r = get_process_comm(pid, &t2); + if (r < 0) + return r; - } else if (max_length <= 6) { - ans = new(char, max_length); - if (!ans) - return -ENOMEM; + mfree(t); + t = strjoin("[", t2, "]"); + if (!t) + return -ENOMEM; + } - memcpy(ans, "[...]", max_length-1); - ans[max_length-1] = 0; - } else { - t[max_length - 6] = 0; + delete_trailing_chars(t, WHITESPACE); - /* Chop off final spaces */ - delete_trailing_chars(t, WHITESPACE); + bool eight_bit = (flags & PROCESS_CMDLINE_USE_LOCALE) && !is_locale_utf8(); - ans = strjoin("[", t, "...]"); - if (!ans) - return -ENOMEM; - } - - *line = TAKE_PTR(ans); - return 0; - } - - k = realloc(ans, strlen(ans) + 1); - if (!k) + ans = escape_non_printable_full(t, max_columns, eight_bit); + if (!ans) return -ENOMEM; - ans = NULL; - *line = k; - + (void) str_realloc(&ans); + *line = TAKE_PTR(ans); return 0; } @@ -291,7 +223,7 @@ int rename_process(const char name[]) { * can use PR_SET_NAME, which sets the thread name for the calling thread. */ if (prctl(PR_SET_NAME, name) < 0) log_debug_errno(errno, "PR_SET_NAME failed: %m"); - if (l >= TASK_COMM_LEN) /* Linux process names can be 15 chars at max */ + if (l >= TASK_COMM_LEN) /* Linux userspace process names can be 15 chars at max */ truncated = true; /* Second step, change glibc's ID of the process name. */ @@ -518,14 +450,11 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) { return -EINVAL; p = procfs_file_alloca(pid, "status"); - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + r = fopen_unlocked(p, "re", &f); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; for (;;) { _cleanup_free_ char *line = NULL; @@ -607,14 +536,11 @@ int get_process_environ(pid_t pid, char **env) { p = procfs_file_alloca(pid, "environ"); - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + r = fopen_unlocked(p, "re", &f); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; for (;;) { char c; @@ -900,15 +826,11 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { path = procfs_file_alloca(pid, "environ"); - f = fopen(path, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - - return -errno; - } - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); + r = fopen_unlocked(path, "re", &f); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; l = strlen(field); for (;;) { @@ -1558,45 +1480,11 @@ int set_oom_score_adjust(int value) { WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER); } -int cpus_in_affinity_mask(void) { - size_t n = 16; - int r; - - for (;;) { - cpu_set_t *c; - - c = CPU_ALLOC(n); - if (!c) - return -ENOMEM; - - if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), c) >= 0) { - int k; - - k = CPU_COUNT_S(CPU_ALLOC_SIZE(n), c); - CPU_FREE(c); - - if (k <= 0) - return -EINVAL; - - return k; - } - - r = -errno; - CPU_FREE(c); - - if (r != -EINVAL) - return r; - if (n > SIZE_MAX/2) - return -ENOMEM; - n *= 2; - } -} - static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", [IOPRIO_CLASS_BE] = "best-effort", - [IOPRIO_CLASS_IDLE] = "idle" + [IOPRIO_CLASS_IDLE] = "idle", }; DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, IOPRIO_N_CLASSES); @@ -1617,7 +1505,7 @@ static const char* const sched_policy_table[] = { [SCHED_BATCH] = "batch", [SCHED_IDLE] = "idle", [SCHED_FIFO] = "fifo", - [SCHED_RR] = "rr" + [SCHED_RR] = "rr", }; DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX); diff --git a/shared/systemd/src/basic/process-util.h b/shared/systemd/src/basic/process-util.h index 3933bee6..20f663e2 100644 --- a/shared/systemd/src/basic/process-util.h +++ b/shared/systemd/src/basic/process-util.h @@ -31,9 +31,13 @@ _r_; \ }) -int get_process_state(pid_t pid); +typedef enum ProcessCmdlineFlags { + PROCESS_CMDLINE_COMM_FALLBACK = 1 << 0, + PROCESS_CMDLINE_USE_LOCALE = 1 << 1, +} ProcessCmdlineFlags; + int get_process_comm(pid_t pid, char **name); -int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line); +int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **line); int get_process_exe(pid_t pid, char **name); int get_process_uid(pid_t pid, uid_t *uid); int get_process_gid(pid_t pid, gid_t *gid); @@ -187,7 +191,7 @@ int set_oom_score_adjust(int value); #error "Unknown pid_t size" #endif -assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX) +assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX); /* Like TAKE_PTR() but for child PIDs, resetting them to 0 */ #define TAKE_PID(pid) \ @@ -196,5 +200,3 @@ assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX) (pid) = 0; \ _pid_; \ }) - -int cpus_in_affinity_mask(void); diff --git a/shared/systemd/src/basic/random-util.c b/shared/systemd/src/basic/random-util.c index b8b45958..c70871bf 100644 --- a/shared/systemd/src/basic/random-util.c +++ b/shared/systemd/src/basic/random-util.c @@ -30,13 +30,75 @@ #include "io-util.h" #include "missing.h" #include "random-util.h" +#include "siphash24.h" #include "time-util.h" int rdrand(unsigned long *ret) { + /* So, you are a "security researcher", and you wonder why we bother with using raw RDRAND here, + * instead of sticking to /dev/urandom or getrandom()? + * + * Here's why: early boot. On Linux, during early boot the random pool that backs /dev/urandom and + * getrandom() is generally not initialized yet. It is very common that initialization of the random + * pool takes a longer time (up to many minutes), in particular on embedded devices that have no + * explicit hardware random generator, as well as in virtualized environments such as major cloud + * installations that do not provide virtio-rng or a similar mechanism. + * + * In such an environment using getrandom() synchronously means we'd block the entire system boot-up + * until the pool is initialized, i.e. *very* long. Using getrandom() asynchronously (GRND_NONBLOCK) + * would mean acquiring randomness during early boot would simply fail. Using /dev/urandom would mean + * generating many kmsg log messages about our use of it before the random pool is properly + * initialized. Neither of these outcomes is desirable. + * + * Thus, for very specific purposes we use RDRAND instead of either of these three options. RDRAND + * provides us quickly and relatively reliably with random values, without having to delay boot, + * without triggering warning messages in kmsg. + * + * Note that we use RDRAND only under very specific circumstances, when the requirements on the + * quality of the returned entropy permit it. Specifically, here are some cases where we *do* use + * RDRAND: + * + * • UUID generation: UUIDs are supposed to be universally unique but are not cryptographic + * key material. The quality and trust level of RDRAND should hence be OK: UUIDs should be + * generated in a way that is reliably unique, but they do not require ultimate trust into + * the entropy generator. systemd generates a number of UUIDs during early boot, including + * 'invocation IDs' for every unit spawned that identify the specific invocation of the + * service globally, and a number of others. Other alternatives for generating these UUIDs + * have been considered, but don't really work: for example, hashing uuids from a local + * system identifier combined with a counter falls flat because during early boot disk + * storage is not yet available (think: initrd) and thus a system-specific ID cannot be + * stored or retrieved yet. + * + * • Hash table seed generation: systemd uses many hash tables internally. Hash tables are + * generally assumed to have O(1) access complexity, but can deteriorate to prohibitive + * O(n) access complexity if an attacker manages to trigger a large number of hash + * collisions. Thus, systemd (as any software employing hash tables should) uses seeded + * hash functions for its hash tables, with a seed generated randomly. The hash tables + * systemd employs watch the fill level closely and reseed if necessary. This allows use of + * a low quality RNG initially, as long as it improves should a hash table be under attack: + * the attacker after all needs to to trigger many collisions to exploit it for the purpose + * of DoS, but if doing so improves the seed the attack surface is reduced as the attack + * takes place. + * + * Some cases where we do NOT use RDRAND are: + * + * • Generation of cryptographic key material 🔑 + * + * • Generation of cryptographic salt values 🧂 + * + * This function returns: + * + * -EOPNOTSUPP → RDRAND is not available on this system 😔 + * -EAGAIN → The operation failed this time, but is likely to work if you try again a few + * times ♻ + * -EUCLEAN → We got some random value, but it looked strange, so we refused using it. + * This failure might or might not be temporary. 😕 + */ + #if defined(__i386__) || defined(__x86_64__) static int have_rdrand = -1; - unsigned char err; + unsigned long v; + uint8_t success; if (have_rdrand < 0) { uint32_t eax, ebx, ecx, edx; @@ -47,7 +109,12 @@ int rdrand(unsigned long *ret) { return -EOPNOTSUPP; } - have_rdrand = !!(ecx & (1U << 30)); +/* Compat with old gcc where bit_RDRND didn't exist yet */ +#ifndef bit_RDRND +#define bit_RDRND (1U << 30) +#endif + + have_rdrand = !!(ecx & bit_RDRND); } if (have_rdrand == 0) @@ -55,12 +122,24 @@ int rdrand(unsigned long *ret) { asm volatile("rdrand %0;" "setc %1" - : "=r" (*ret), - "=qm" (err)); - msan_unpoison(&err, sizeof(err)); - if (!err) + : "=r" (v), + "=qm" (success)); + msan_unpoison(&success, sizeof(success)); + if (!success) return -EAGAIN; + /* Apparently on some AMD CPUs RDRAND will sometimes (after a suspend/resume cycle?) report success + * via the carry flag but nonetheless return the same fixed value -1 in all cases. This appears to be + * a bad bug in the CPU or firmware. Let's deal with that and work-around this by explicitly checking + * for this special value (and also 0, just to be sure) and filtering it out. This is a work-around + * only however and something AMD really should fix properly. The Linux kernel should probably work + * around this issue by turning off RDRAND altogether on those CPUs. See: + * https://github.com/systemd/systemd/issues/11810 */ + if (v == 0 || v == ULONG_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), + "RDRAND returned suspicious value %lx, assuming bad hardware RNG, not using value.", v); + + *ret = v; return 0; #else return -EOPNOTSUPP; @@ -73,21 +152,32 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { bool got_some = false; int r; - /* Gathers some randomness from the kernel (or the CPU if the RANDOM_ALLOW_RDRAND flag is set). This call won't - * block, unless the RANDOM_BLOCK flag is set. If RANDOM_DONT_DRAIN is set, an error is returned if the random - * pool is not initialized. Otherwise it will always return some data from the kernel, regardless of whether - * the random pool is fully initialized or not. */ + /* Gathers some high-quality randomness from the kernel (or potentially mid-quality randomness from + * the CPU if the RANDOM_ALLOW_RDRAND flag is set). This call won't block, unless the RANDOM_BLOCK + * flag is set. If RANDOM_MAY_FAIL is set, an error is returned if the random pool is not + * initialized. Otherwise it will always return some data from the kernel, regardless of whether the + * random pool is fully initialized or not. If RANDOM_EXTEND_WITH_PSEUDO is set, and some but not + * enough better quality randomness could be acquired, the rest is filled up with low quality + * randomness. + * + * Of course, when creating cryptographic key material you really shouldn't use RANDOM_ALLOW_DRDRAND + * or even RANDOM_EXTEND_WITH_PSEUDO. + * + * When generating UUIDs it's fine to use RANDOM_ALLOW_RDRAND but not OK to use + * RANDOM_EXTEND_WITH_PSEUDO. In fact RANDOM_EXTEND_WITH_PSEUDO is only really fine when invoked via + * an "all bets are off" wrapper, such as random_bytes(), see below. */ if (n == 0) return 0; if (FLAGS_SET(flags, RANDOM_ALLOW_RDRAND)) - /* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality randomness is not - * required, as we don't trust it (who does?). Note that we only do a single iteration of RDRAND here, - * even though the Intel docs suggest calling this in a tight loop of 10 invocations or so. That's - * because we don't really care about the quality here. We generally prefer using RDRAND if the caller - * allows us too, since this way we won't drain the kernel randomness pool if we don't need it, as the - * pool's entropy is scarce. */ + /* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality randomness is + * not required, as we don't trust it (who does?). Note that we only do a single iteration of + * RDRAND here, even though the Intel docs suggest calling this in a tight loop of 10 + * invocations or so. That's because we don't really care about the quality here. We + * generally prefer using RDRAND if the caller allows us to, since this way we won't upset + * the kernel's random subsystem by accessing it before the pool is initialized (after all it + * will kmsg log about every attempt to do so)..*/ for (;;) { unsigned long u; size_t m; @@ -162,12 +252,13 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { break; } else if (errno == EAGAIN) { - /* The kernel has no entropy whatsoever. Let's remember to use the syscall the next - * time again though. + /* The kernel has no entropy whatsoever. Let's remember to use the syscall + * the next time again though. * - * If RANDOM_DONT_DRAIN is set, return an error so that random_bytes() can produce some - * pseudo-random bytes instead. Otherwise, fall back to /dev/urandom, which we know is empty, - * but the kernel will produce some bytes for us on a best-effort basis. */ + * If RANDOM_MAY_FAIL is set, return an error so that random_bytes() can + * produce some pseudo-random bytes instead. Otherwise, fall back to + * /dev/urandom, which we know is empty, but the kernel will produce some + * bytes for us on a best-effort basis. */ have_syscall = true; if (got_some && FLAGS_SET(flags, RANDOM_EXTEND_WITH_PSEUDO)) { @@ -176,7 +267,7 @@ int genuine_random_bytes(void *p, size_t n, RandomFlags flags) { return 0; } - if (FLAGS_SET(flags, RANDOM_DONT_DRAIN)) + if (FLAGS_SET(flags, RANDOM_MAY_FAIL)) return -ENODATA; /* Use /dev/urandom instead */ @@ -205,14 +296,19 @@ void initialize_srand(void) { return; #if HAVE_SYS_AUXV_H - /* The kernel provides us with 16 bytes of entropy in auxv, so let's - * try to make use of that to seed the pseudo-random generator. It's - * better than nothing... */ + /* The kernel provides us with 16 bytes of entropy in auxv, so let's try to make use of that to seed + * the pseudo-random generator. It's better than nothing... But let's first hash it to make it harder + * to recover the original value by watching any pseudo-random bits we generate. After all the + * AT_RANDOM data might be used by other stuff too (in particular: ASLR), and we probably shouldn't + * leak the seed for that. */ - auxv = (const void*) getauxval(AT_RANDOM); + auxv = ULONG_TO_PTR(getauxval(AT_RANDOM)); if (auxv) { - assert_cc(sizeof(x) <= 16); - memcpy(&x, auxv, sizeof(x)); + static const uint8_t auxval_hash_key[16] = { + 0x92, 0x6e, 0xfe, 0x1b, 0xcf, 0x00, 0x52, 0x9c, 0xcc, 0x42, 0xcf, 0xdc, 0x94, 0x1f, 0x81, 0x0f + }; + + x = (unsigned) siphash24(auxv, 16, auxval_hash_key); } else #endif x = 0; @@ -238,6 +334,11 @@ void initialize_srand(void) { void pseudo_random_bytes(void *p, size_t n) { uint8_t *q; + /* This returns pseudo-random data using libc's rand() function. You probably never want to call this + * directly, because why would you use this if you can get better stuff cheaply? Use random_bytes() + * instead, see below: it will fall back to this function if there's nothing better to get, but only + * then. */ + initialize_srand(); for (q = p; q < (uint8_t*) p + n; q += RAND_STEP) { @@ -259,7 +360,39 @@ void pseudo_random_bytes(void *p, size_t n) { void random_bytes(void *p, size_t n) { - if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO|RANDOM_DONT_DRAIN|RANDOM_ALLOW_RDRAND) >= 0) + /* This returns high quality randomness if we can get it cheaply. If we can't because for some reason + * it is not available we'll try some crappy fallbacks. + * + * What this function will do: + * + * • This function will preferably use the CPU's RDRAND operation, if it is available, in + * order to return "mid-quality" random values cheaply. + * + * • Use getrandom() with GRND_NONBLOCK, to return high-quality random values if they are + * cheaply available. + * + * • This function will return pseudo-random data, generated via libc rand() if nothing + * better is available. + * + * • This function will work fine in early boot + * + * • This function will always succeed + * + * What this function won't do: + * + * • This function will never fail: it will give you randomness no matter what. It might not + * be high quality, but it will return some, possibly generated via libc's rand() call. + * + * • This function will never block: if the only way to get good randomness is a blocking, + * synchronous getrandom() we'll instead provide you with pseudo-random data. + * + * This function is hence great for things like seeding hash tables, generating random numeric UNIX + * user IDs (that are checked for collisions before use) and such. + * + * This function is hence not useful for generating UUIDs or cryptographic key material. + */ + + if (genuine_random_bytes(p, n, RANDOM_EXTEND_WITH_PSEUDO|RANDOM_MAY_FAIL|RANDOM_ALLOW_RDRAND) >= 0) return; /* If for some reason some user made /dev/urandom unavailable to us, or the kernel has no entropy, use a PRNG instead. */ diff --git a/shared/systemd/src/basic/random-util.h b/shared/systemd/src/basic/random-util.h index 3e8c288d..148b6c78 100644 --- a/shared/systemd/src/basic/random-util.h +++ b/shared/systemd/src/basic/random-util.h @@ -8,11 +8,11 @@ typedef enum RandomFlags { RANDOM_EXTEND_WITH_PSEUDO = 1 << 0, /* If we can't get enough genuine randomness, but some, fill up the rest with pseudo-randomness */ RANDOM_BLOCK = 1 << 1, /* Rather block than return crap randomness (only if the kernel supports that) */ - RANDOM_DONT_DRAIN = 1 << 2, /* If we can't get any randomness at all, return early with -EAGAIN */ + RANDOM_MAY_FAIL = 1 << 2, /* If we can't get any randomness at all, return early with -ENODATA */ RANDOM_ALLOW_RDRAND = 1 << 3, /* Allow usage of the CPU RNG */ } RandomFlags; -int genuine_random_bytes(void *p, size_t n, RandomFlags flags); /* returns "genuine" randomness, optionally filled upwith pseudo random, if not enough is available */ +int genuine_random_bytes(void *p, size_t n, RandomFlags flags); /* returns "genuine" randomness, optionally filled up with pseudo random, if not enough is available */ void pseudo_random_bytes(void *p, size_t n); /* returns only pseudo-randommess (but possibly seeded from something better) */ void random_bytes(void *p, size_t n); /* returns genuine randomness if cheaply available, and pseudo randomness if not. */ diff --git a/shared/systemd/src/basic/set.h b/shared/systemd/src/basic/set.h index 2a80632b..2bb26c68 100644 --- a/shared/systemd/src/basic/set.h +++ b/shared/systemd/src/basic/set.h @@ -28,13 +28,13 @@ int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHM int set_put(Set *s, const void *key); /* no set_update */ /* no set_replace */ -static inline void *set_get(Set *s, void *key) { - return internal_hashmap_get(HASHMAP_BASE(s), key); +static inline void *set_get(const Set *s, void *key) { + return internal_hashmap_get(HASHMAP_BASE((Set *) s), key); } /* no set_get2 */ -static inline bool set_contains(Set *s, const void *key) { - return internal_hashmap_contains(HASHMAP_BASE(s), key); +static inline bool set_contains(const Set *s, const void *key) { + return internal_hashmap_contains(HASHMAP_BASE((Set *) s), key); } static inline void *set_remove(Set *s, const void *key) { @@ -59,19 +59,19 @@ static inline int set_move_one(Set *s, Set *other, const void *key) { return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key); } -static inline unsigned set_size(Set *s) { - return internal_hashmap_size(HASHMAP_BASE(s)); +static inline unsigned set_size(const Set *s) { + return internal_hashmap_size(HASHMAP_BASE((Set *) s)); } -static inline bool set_isempty(Set *s) { +static inline bool set_isempty(const Set *s) { return set_size(s) == 0; } -static inline unsigned set_buckets(Set *s) { - return internal_hashmap_buckets(HASHMAP_BASE(s)); +static inline unsigned set_buckets(const Set *s) { + return internal_hashmap_buckets(HASHMAP_BASE((Set *) s)); } -bool set_iterate(Set *s, Iterator *i, void **value); +bool set_iterate(const Set *s, Iterator *i, void **value); static inline void set_clear(Set *s) { internal_hashmap_clear(HASHMAP_BASE(s), NULL, NULL); diff --git a/shared/systemd/src/basic/siphash24.h b/shared/systemd/src/basic/siphash24.h index be1d3e00..c4e919df 100644 --- a/shared/systemd/src/basic/siphash24.h +++ b/shared/systemd/src/basic/siphash24.h @@ -47,6 +47,7 @@ siphash24 (const void *in, size_t inlen, const uint8_t k[16]) void siphash24_init(struct siphash *state, const uint8_t k[static 16]); void siphash24_compress(const void *in, size_t inlen, struct siphash *state); +void siphash24_compress_boolean(bool in, struct siphash *state); #define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state)) uint64_t siphash24_finalize(struct siphash *state); diff --git a/shared/systemd/src/basic/socket-util.c b/shared/systemd/src/basic/socket-util.c index b98b0461..b822ed03 100644 --- a/shared/systemd/src/basic/socket-util.c +++ b/shared/systemd/src/basic/socket-util.c @@ -81,7 +81,7 @@ int socket_address_parse(SocketAddress *a, const char *s) { errno = 0; if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0) - return errno > 0 ? -errno : -EINVAL; + return errno_or_else(EINVAL); e++; if (*e != ':') @@ -1224,17 +1224,34 @@ fallback: } #if 0 /* NM_IGNORED */ +/* Put a limit on how many times will attempt to call accept4(). We loop + * only on "transient" errors, but let's make sure we don't loop forever. */ +#define MAX_FLUSH_ITERATIONS 1024 + int flush_accept(int fd) { struct pollfd pollfd = { .fd = fd, .events = POLLIN, }; - int r; + int r, b; + socklen_t l = sizeof(b); - /* Similar to flush_fd() but flushes all incoming connection by accepting them and immediately closing them. */ + /* Similar to flush_fd() but flushes all incoming connections by accepting and immediately closing + * them. */ - for (;;) { + if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &b, &l) < 0) + return -errno; + + assert(l == sizeof(b)); + if (!b) /* Let's check if this socket accepts connections before calling accept(). accept4() can + * return EOPNOTSUPP if the fd is not a listening socket, which we should treat as a fatal + * error, or in case the incoming TCP connection triggered a network issue, which we want to + * treat as a transient error. Thus, let's rule out the first reason for EOPNOTSUPP early, so + * we can loop safely on transient errors below. */ + return -ENOTTY; + + for (unsigned iteration = 0;; iteration++) { int cfd; r = poll(&pollfd, 1, 0); @@ -1247,6 +1264,10 @@ int flush_accept(int fd) { if (r == 0) return 0; + if (iteration >= MAX_FLUSH_ITERATIONS) + return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), + "Failed to flush connections within " STRINGIFY(MAX_FLUSH_ITERATIONS) " iterations."); + cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); if (cfd < 0) { if (errno == EAGAIN) @@ -1374,7 +1395,7 @@ int socket_bind_to_ifname(int fd, const char *ifname) { } int socket_bind_to_ifindex(int fd, int ifindex) { - char ifname[IFNAMSIZ] = ""; + char ifname[IF_NAMESIZE + 1]; assert(fd >= 0); @@ -1392,7 +1413,7 @@ int socket_bind_to_ifindex(int fd, int ifindex) { return -errno; /* Fall back to SO_BINDTODEVICE on kernels < 5.0 which didn't have SO_BINDTOIFINDEX */ - if (!if_indextoname(ifindex, ifname)) + if (!format_ifname(ifindex, ifname)) return -errno; return socket_bind_to_ifname(fd, ifname); diff --git a/shared/systemd/src/basic/socket-util.h b/shared/systemd/src/basic/socket-util.h index 15443f1e..a0886e0e 100644 --- a/shared/systemd/src/basic/socket-util.h +++ b/shared/systemd/src/basic/socket-util.h @@ -3,12 +3,13 @@ #include <inttypes.h> #include <linux/netlink.h> +#include <linux/if_ether.h> #include <linux/if_infiniband.h> #include <linux/if_packet.h> -#include <netinet/ether.h> #include <netinet/in.h> #include <stdbool.h> #include <stddef.h> +#include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/un.h> diff --git a/shared/systemd/src/basic/string-util.c b/shared/systemd/src/basic/string-util.c index df519976..2d34603e 100644 --- a/shared/systemd/src/basic/string-util.c +++ b/shared/systemd/src/basic/string-util.c @@ -6,7 +6,6 @@ #include <stdarg.h> #include <stdint.h> #include <stdio.h> -#include <stdio_ext.h> #include <stdlib.h> #include <string.h> @@ -209,10 +208,6 @@ char *strnappend(const char *s, const char *suffix, size_t b) { return r; } -char *strappend(const char *s, const char *suffix) { - return strnappend(s, suffix, strlen_ptr(suffix)); -} - #if 0 /* NM_IGNORED */ char *strjoin_real(const char *x, ...) { va_list ap; @@ -736,10 +731,18 @@ char *strreplace(const char *text, const char *old_string, const char *new_strin return ret; } -static void advance_offsets(ssize_t diff, size_t offsets[static 2], size_t shift[static 2], size_t size) { +#if 0 /* NM_IGNORED */ +static void advance_offsets( + ssize_t diff, + size_t offsets[2], /* note: we can't use [static 2] here, since this may be NULL */ + size_t shift[static 2], + size_t size) { + if (!offsets) return; + assert(shift); + if ((size_t) diff < offsets[0]) shift[0] += size; if ((size_t) diff < offsets[1]) @@ -767,23 +770,20 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { * 2. Strips ANSI color sequences (a subset of CSI), i.e. ESC '[' … 'm' sequences * 3. Strips ANSI operating system sequences (CSO), i.e. ESC ']' … BEL sequences * - * Everything else will be left as it is. In particular other ANSI sequences are left as they are, as are any - * other special characters. Truncated ANSI sequences are left-as is too. This call is supposed to suppress the - * most basic formatting noise, but nothing else. + * Everything else will be left as it is. In particular other ANSI sequences are left as they are, as + * are any other special characters. Truncated ANSI sequences are left-as is too. This call is + * supposed to suppress the most basic formatting noise, but nothing else. * * Why care for CSO sequences? Well, to undo what terminal_urlify() and friends generate. */ isz = _isz ? *_isz : strlen(*ibuf); - f = open_memstream(&obuf, &osz); + /* Note we turn off internal locking on f for performance reasons. It's safe to do so since we + * created f here and it doesn't leave our scope. */ + f = open_memstream_unlocked(&obuf, &osz); if (!f) return NULL; - /* Note we turn off internal locking on f for performance reasons. It's safe to do so since we created f here - * and it doesn't leave our scope. */ - - (void) __fsetlocking(f, FSETLOCKING_BYCALLER); - for (i = *ibuf; i < *ibuf + isz + 1; i++) { switch (state) { @@ -858,8 +858,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { fclose(f); - free(*ibuf); - *ibuf = obuf; + free_and_replace(*ibuf, obuf); if (_isz) *_isz = osz; @@ -869,10 +868,9 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) { highlight[1] += shift[1]; } - return obuf; + return *ibuf; } -#if 0 /* NM_IGNORED */ char *strextend_with_separator(char **x, const char *separator, ...) { bool need_separator; size_t f, l, l_separator; @@ -1044,20 +1042,6 @@ int free_and_strndup(char **p, const char *s, size_t l) { return 1; } -char* string_erase(char *x) { - if (!x) - return NULL; - - /* A delicious drop of snake-oil! To be called on memory where - * we stored passphrases or so, after we used them. */ - explicit_bzero_safe(x, strlen(x)); - return x; -} - -char *string_free_erase(char *s) { - return mfree(string_erase(s)); -} - bool string_is_safe(const char *p) { const char *t; diff --git a/shared/systemd/src/basic/string-util.h b/shared/systemd/src/basic/string-util.h index b23f4c83..76767afc 100644 --- a/shared/systemd/src/basic/string-util.h +++ b/shared/systemd/src/basic/string-util.h @@ -108,7 +108,6 @@ const char* split(const char **state, size_t *l, const char *separator, SplitFla #define _FOREACH_WORD(word, length, s, separator, flags, state) \ for ((state) = (s), (word) = split(&(state), &(length), (separator), (flags)); (word); (word) = split(&(state), &(length), (separator), (flags))) -char *strappend(const char *s, const char *suffix); char *strnappend(const char *s, const char *suffix, size_t length); char *strjoin_real(const char *x, ...) _sentinel_; @@ -197,12 +196,6 @@ static inline int free_and_strdup_warn(char **p, const char *s) { } int free_and_strndup(char **p, const char *s, size_t l); -char *string_erase(char *x); - -char *string_free_erase(char *s); -DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase); -#define _cleanup_string_free_erase_ _cleanup_(string_free_erasep) - bool string_is_safe(const char *p) _pure_; static inline size_t strlen_ptr(const char *s) { @@ -212,6 +205,12 @@ static inline size_t strlen_ptr(const char *s) { return strlen(s); } +DISABLE_WARNING_STRINGOP_TRUNCATION; +static inline void strncpy_exact(char *buf, const char *src, size_t buf_len) { + strncpy(buf, src, buf_len); +} +REENABLE_WARNING; + /* Like startswith(), but operates on arbitrary memory blocks */ static inline void *memory_startswith(const void *p, size_t sz, const char *token) { size_t n; @@ -251,3 +250,16 @@ static inline void *memory_startswith_no_case(const void *p, size_t sz, const ch return (uint8_t*) p + n; } + +static inline char* str_realloc(char **p) { + /* Reallocate *p to actual size */ + + if (!*p) + return NULL; + + char *t = realloc(*p, strlen(*p) + 1); + if (!t) + return NULL; + + return (*p = t); +} diff --git a/shared/systemd/src/basic/strv.c b/shared/systemd/src/basic/strv.c index 8de40b9d..ba23178a 100644 --- a/shared/systemd/src/basic/strv.c +++ b/shared/systemd/src/basic/strv.c @@ -13,6 +13,7 @@ #include "escape.h" #include "extract-word.h" #include "fileio.h" +#include "memory-util.h" #include "nulstr-util.h" #include "sort-util.h" #include "string-util.h" @@ -80,9 +81,9 @@ char **strv_free_erase(char **l) { char **i; STRV_FOREACH(i, l) - string_erase(*i); + erase_and_freep(i); - return strv_free(l); + return mfree(l); } char **strv_copy(char * const *l) { @@ -227,6 +228,7 @@ rollback: return -ENOMEM; } +#if 0 /* NM_IGNORED */ int strv_extend_strv_concat(char ***a, char **b, const char *suffix) { int r; char **s; @@ -234,7 +236,7 @@ int strv_extend_strv_concat(char ***a, char **b, const char *suffix) { STRV_FOREACH(s, b) { char *v; - v = strappend(*s, suffix); + v = strjoin(*s, suffix); if (!v) return -ENOMEM; @@ -247,6 +249,7 @@ int strv_extend_strv_concat(char ***a, char **b, const char *suffix) { return 0; } +#endif /* NM_IGNORED */ char **strv_split_full(const char *s, const char *separator, SplitFlags flags) { const char *word, *state; @@ -896,3 +899,63 @@ int fputstrv(FILE *f, char **l, const char *separator, bool *space) { return 0; } + +static int string_strv_hashmap_put_internal(Hashmap *h, const char *key, const char *value) { + char **l; + int r; + + l = hashmap_get(h, key); + if (l) { + /* A list for this key already exists, let's append to it if it is not listed yet */ + if (strv_contains(l, value)) + return 0; + + r = strv_extend(&l, value); + if (r < 0) + return r; + + assert_se(hashmap_update(h, key, l) >= 0); + } else { + /* No list for this key exists yet, create one */ + _cleanup_strv_free_ char **l2 = NULL; + _cleanup_free_ char *t = NULL; + + t = strdup(key); + if (!t) + return -ENOMEM; + + r = strv_extend(&l2, value); + if (r < 0) + return r; + + r = hashmap_put(h, t, l2); + if (r < 0) + return r; + TAKE_PTR(t); + TAKE_PTR(l2); + } + + return 1; +} + +int string_strv_hashmap_put(Hashmap **h, const char *key, const char *value) { + int r; + + r = hashmap_ensure_allocated(h, &string_strv_hash_ops); + if (r < 0) + return r; + + return string_strv_hashmap_put_internal(*h, key, value); +} + +int string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value) { + int r; + + r = ordered_hashmap_ensure_allocated(h, &string_strv_hash_ops); + if (r < 0) + return r; + + return string_strv_hashmap_put_internal(PLAIN_HASHMAP(*h), key, value); +} + +DEFINE_HASH_OPS_FULL(string_strv_hash_ops, char, string_hash_func, string_compare_func, free, char*, strv_free); diff --git a/shared/systemd/src/basic/strv.h b/shared/systemd/src/basic/strv.h index aa5f95ab..e80964ac 100644 --- a/shared/systemd/src/basic/strv.h +++ b/shared/systemd/src/basic/strv.h @@ -9,6 +9,7 @@ #include "alloc-util.h" #include "extract-word.h" +#include "hashmap.h" #include "macro.h" #include "string-util.h" @@ -188,3 +189,7 @@ int fputstrv(FILE *f, char **l, const char *separator, bool *space); (b) = NULL; \ 0; \ }) + +extern const struct hash_ops string_strv_hash_ops; +int string_strv_hashmap_put(Hashmap **h, const char *key, const char *value); +int string_strv_ordered_hashmap_put(OrderedHashmap **h, const char *key, const char *value); diff --git a/shared/systemd/src/basic/strxcpyx.c b/shared/systemd/src/basic/strxcpyx.c new file mode 100644 index 00000000..301e6899 --- /dev/null +++ b/shared/systemd/src/basic/strxcpyx.c @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +/* + * Concatenates/copies strings. In any case, terminates in all cases + * with '\0' and moves the @dest pointer forward to the added '\0'. + * Returns the remaining size, and 0 if the string was truncated. + * + * Due to the intended usage, these helpers silently noop invocations + * having zero size. This is technically an exception to the above + * statement "terminates in all cases". It's unexpected for such calls to + * occur outside of a loop where this is the preferred behavior. + */ + +#include "nm-sd-adapt-shared.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include "strxcpyx.h" + +size_t strnpcpy(char **dest, size_t size, const char *src, size_t len) { + assert(dest); + assert(src); + + if (size == 0) + return 0; + + if (len >= size) { + if (size > 1) + *dest = mempcpy(*dest, src, size-1); + size = 0; + } else if (len > 0) { + *dest = mempcpy(*dest, src, len); + size -= len; + } + + *dest[0] = '\0'; + return size; +} + +size_t strpcpy(char **dest, size_t size, const char *src) { + assert(dest); + assert(src); + + return strnpcpy(dest, size, src, strlen(src)); +} + +size_t strpcpyf(char **dest, size_t size, const char *src, ...) { + va_list va; + int i; + + assert(dest); + assert(src); + + if (size == 0) + return 0; + + va_start(va, src); + i = vsnprintf(*dest, size, src, va); + if (i < (int)size) { + *dest += i; + size -= i; + } else + size = 0; + va_end(va); + return size; +} + +size_t strpcpyl(char **dest, size_t size, const char *src, ...) { + va_list va; + + assert(dest); + assert(src); + + va_start(va, src); + do { + size = strpcpy(dest, size, src); + src = va_arg(va, char *); + } while (src); + va_end(va); + return size; +} + +size_t strnscpy(char *dest, size_t size, const char *src, size_t len) { + char *s; + + assert(dest); + assert(src); + + s = dest; + return strnpcpy(&s, size, src, len); +} + +size_t strscpy(char *dest, size_t size, const char *src) { + assert(dest); + assert(src); + + return strnscpy(dest, size, src, strlen(src)); +} + +size_t strscpyl(char *dest, size_t size, const char *src, ...) { + va_list va; + char *s; + + assert(dest); + assert(src); + + va_start(va, src); + s = dest; + do { + size = strpcpy(&s, size, src); + src = va_arg(va, char *); + } while (src); + va_end(va); + + return size; +} diff --git a/shared/systemd/src/basic/strxcpyx.h b/shared/systemd/src/basic/strxcpyx.h new file mode 100644 index 00000000..9b668412 --- /dev/null +++ b/shared/systemd/src/basic/strxcpyx.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <stddef.h> + +#include "macro.h" + +size_t strnpcpy(char **dest, size_t size, const char *src, size_t len); +size_t strpcpy(char **dest, size_t size, const char *src); +size_t strpcpyf(char **dest, size_t size, const char *src, ...) _printf_(3, 4); +size_t strpcpyl(char **dest, size_t size, const char *src, ...) _sentinel_; +size_t strnscpy(char *dest, size_t size, const char *src, size_t len); +size_t strscpy(char *dest, size_t size, const char *src); +size_t strscpyl(char *dest, size_t size, const char *src, ...) _sentinel_; diff --git a/shared/systemd/src/basic/time-util.c b/shared/systemd/src/basic/time-util.c index 14b17bfc..aa790023 100644 --- a/shared/systemd/src/basic/time-util.c +++ b/shared/systemd/src/basic/time-util.c @@ -270,13 +270,12 @@ static char *format_timestamp_internal( assert(buf); - if (l < - 3 + /* week day */ - 1 + 10 + /* space and date */ - 1 + 8 + /* space and time */ - (us ? 1 + 6 : 0) + /* "." and microsecond part */ - 1 + 1 + /* space and shortest possible zone */ - 1) + if (l < (size_t) (3 + /* week day */ + 1 + 10 + /* space and date */ + 1 + 8 + /* space and time */ + (us ? 1 + 6 : 0) + /* "." and microsecond part */ + 1 + 1 + /* space and shortest possible zone */ + 1)) return NULL; /* Not enough space even for the shortest form. */ if (t <= 0 || t == USEC_INFINITY) return NULL; /* Timestamp is unset */ @@ -580,7 +579,6 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) { */ assert(t); - assert(usec); if (t[0] == '@' && !with_tz) return parse_sec(t + 1, usec); @@ -808,8 +806,8 @@ finish: else return -EINVAL; - *usec = ret; - + if (usec) + *usec = ret; return 0; } @@ -866,7 +864,7 @@ int parse_timestamp(const char *t, usec_t *usec) { if (munmap(shared, sizeof *shared) != 0) return negative_errno(); - if (tmp.return_value == 0) + if (tmp.return_value == 0 && usec) *usec = tmp.usec; return tmp.return_value; @@ -928,7 +926,6 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { bool something = false; assert(t); - assert(usec); assert(default_unit > 0); p = t; @@ -940,7 +937,8 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { if (*s != 0) return -EINVAL; - *usec = USEC_INFINITY; + if (usec) + *usec = USEC_INFINITY; return 0; } @@ -1012,8 +1010,8 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) { } } - *usec = r; - + if (usec) + *usec = r; return 0; } diff --git a/shared/systemd/src/basic/time-util.h b/shared/systemd/src/basic/time-util.h index a238f691..e3a529d9 100644 --- a/shared/systemd/src/basic/time-util.h +++ b/shared/systemd/src/basic/time-util.h @@ -81,15 +81,19 @@ triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) #define TRIPLE_TIMESTAMP_HAS_CLOCK(clock) \ IN_SET(clock, CLOCK_REALTIME, CLOCK_REALTIME_ALARM, CLOCK_MONOTONIC, CLOCK_BOOTTIME, CLOCK_BOOTTIME_ALARM) +static inline bool timestamp_is_set(usec_t timestamp) { + return timestamp > 0 && timestamp != USEC_INFINITY; +} + static inline bool dual_timestamp_is_set(const dual_timestamp *ts) { - return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) || - (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY)); + return timestamp_is_set(ts->realtime) || + timestamp_is_set(ts->monotonic); } static inline bool triple_timestamp_is_set(const triple_timestamp *ts) { - return ((ts->realtime > 0 && ts->realtime != USEC_INFINITY) || - (ts->monotonic > 0 && ts->monotonic != USEC_INFINITY) || - (ts->boottime > 0 && ts->boottime != USEC_INFINITY)); + return timestamp_is_set(ts->realtime) || + timestamp_is_set(ts->monotonic) || + timestamp_is_set(ts->boottime); } usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock); diff --git a/shared/systemd/src/basic/tmpfile-util.c b/shared/systemd/src/basic/tmpfile-util.c index 019121cb..c02ce3df 100644 --- a/shared/systemd/src/basic/tmpfile-util.c +++ b/shared/systemd/src/basic/tmpfile-util.c @@ -2,10 +2,12 @@ #include "nm-sd-adapt-shared.h" +#include <stdio.h> #include <sys/mman.h> #include "alloc-util.h" #include "fd-util.h" +#include "fileio.h" #include "fs-util.h" #include "hexdecoct.h" #include "macro.h" @@ -39,12 +41,15 @@ int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { return -errno; } - f = fdopen(fd, "w"); - if (!f) { - unlink_noerrno(t); + /* This assumes that returned FILE object is short-lived and used within the same single-threaded + * context and never shared externally, hence locking is not necessary. */ + + r = fdopen_unlocked(fd, "w", &f); + if (r < 0) { + unlink(t); free(t); safe_close(fd); - return -errno; + return r; } *_f = f; @@ -55,13 +60,11 @@ int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { /* This is much like mkostemp() but is subject to umask(). */ int mkostemp_safe(char *pattern) { - _cleanup_umask_ mode_t u = 0; + _unused_ _cleanup_umask_ mode_t u = umask(0077); int fd; assert(pattern); - u = umask(077); - fd = mkostemp(pattern, O_CLOEXEC); if (fd < 0) return -errno; @@ -322,7 +325,7 @@ int mkdtemp_malloc(const char *template, char **ret) { if (r < 0) return r; - p = strjoin(tmp, "/XXXXXX"); + p = path_join(tmp, "XXXXXX"); } if (!p) return -ENOMEM; diff --git a/shared/systemd/src/basic/umask-util.h b/shared/systemd/src/basic/umask-util.h index e964292e..cad74517 100644 --- a/shared/systemd/src/basic/umask-util.h +++ b/shared/systemd/src/basic/umask-util.h @@ -8,21 +8,19 @@ #include "macro.h" static inline void umaskp(mode_t *u) { - umask(*u); + umask(*u & 0777); } #define _cleanup_umask_ _cleanup_(umaskp) -struct _umask_struct_ { - mode_t mask; - bool quit; -}; +/* We make use of the fact here that the umask() concept is using only the lower 9 bits of mode_t, although + * mode_t has space for the file type in the bits further up. We simply OR in the file type mask S_IFMT to + * distinguish the first and the second iteration of the RUN_WITH_UMASK() loop, so that we can run the first + * one, and exit on the second. */ -static inline void _reset_umask_(struct _umask_struct_ *s) { - umask(s->mask); -}; +assert_cc((S_IFMT & 0777) == 0); #define RUN_WITH_UMASK(mask) \ - for (_cleanup_(_reset_umask_) struct _umask_struct_ _saved_umask_ = { umask(mask), false }; \ - !_saved_umask_.quit ; \ - _saved_umask_.quit = true) + for (_cleanup_umask_ mode_t _saved_umask_ = umask(mask) | S_IFMT; \ + FLAGS_SET(_saved_umask_, S_IFMT); \ + _saved_umask_ &= 0777) diff --git a/shared/systemd/src/basic/utf8.c b/shared/systemd/src/basic/utf8.c index f1c6ac1f..3c51fa1f 100644 --- a/shared/systemd/src/basic/utf8.c +++ b/shared/systemd/src/basic/utf8.c @@ -34,6 +34,7 @@ #include "gunicode.h" #include "hexdecoct.h" #include "macro.h" +#include "string-util.h" #include "utf8.h" bool unichar_is_valid(char32_t ch) { @@ -198,47 +199,94 @@ char *utf8_escape_invalid(const char *str) { } *s = '\0'; - + (void) str_realloc(&p); return p; } #if 0 /* NM_IGNORED */ -char *utf8_escape_non_printable(const char *str) { - char *p, *s; +static int utf8_char_console_width(const char *str) { + char32_t c; + int r; + + r = utf8_encoded_to_unichar(str, &c); + if (r < 0) + return r; + + /* TODO: we should detect combining characters */ + + return unichar_iswide(c) ? 2 : 1; +} + +char *utf8_escape_non_printable_full(const char *str, size_t console_width) { + char *p, *s, *prev_s; + size_t n = 0; /* estimated print width */ assert(str); - p = s = malloc(strlen(str) * 4 + 1); + if (console_width == 0) + return strdup(""); + + p = s = prev_s = malloc(strlen(str) * 4 + 1); if (!p) return NULL; - while (*str) { + for (;;) { int len; + char *saved_s = s; + + if (!*str) /* done! */ + goto finish; len = utf8_encoded_valid_unichar(str, (size_t) -1); if (len > 0) { if (utf8_is_printable(str, len)) { + int w; + + w = utf8_char_console_width(str); + assert(w >= 0); + if (n + w > console_width) + goto truncation; + s = mempcpy(s, str, len); str += len; + n += w; + } else { - while (len > 0) { + for (; len > 0; len--) { + if (n + 4 > console_width) + goto truncation; + *(s++) = '\\'; *(s++) = 'x'; *(s++) = hexchar((int) *str >> 4); *(s++) = hexchar((int) *str); str += 1; - len--; + n += 4; } } } else { - s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER); + if (n + 1 > console_width) + goto truncation; + + s = mempcpy(s, UTF8_REPLACEMENT_CHARACTER, strlen(UTF8_REPLACEMENT_CHARACTER)); str += 1; + n += 1; } + + prev_s = saved_s; } - *s = '\0'; + truncation: + /* Try to go back one if we don't have enough space for the ellipsis */ + if (n + 1 >= console_width) + s = prev_s; + + s = mempcpy(s, "…", strlen("…")); + finish: + *s = '\0'; + (void) str_realloc(&p); return p; } #endif /* NM_IGNORED */ @@ -532,15 +580,15 @@ size_t utf8_console_width(const char *str) { /* Returns the approximate width a string will take on screen when printed on a character cell * terminal/console. */ - while (*str != 0) { - char32_t c; + while (*str) { + int w; - if (utf8_encoded_to_unichar(str, &c) < 0) + w = utf8_char_console_width(str); + if (w < 0) return (size_t) -1; + n += w; str = utf8_next_char(str); - - n += unichar_iswide(c) ? 2 : 1; } return n; diff --git a/shared/systemd/src/basic/utf8.h b/shared/systemd/src/basic/utf8.h index 6df70921..62e99b72 100644 --- a/shared/systemd/src/basic/utf8.h +++ b/shared/systemd/src/basic/utf8.h @@ -22,7 +22,10 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pu #define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true) char *utf8_escape_invalid(const char *s); -char *utf8_escape_non_printable(const char *str); +char *utf8_escape_non_printable_full(const char *str, size_t console_width); +static inline char *utf8_escape_non_printable(const char *str) { + return utf8_escape_non_printable_full(str, (size_t) -1); +} size_t utf8_encode_unichar(char *out_utf8, char32_t g); size_t utf16_encode_unichar(char16_t *out, char32_t c); diff --git a/shared/systemd/src/shared/dns-domain.c b/shared/systemd/src/shared/dns-domain.c new file mode 100644 index 00000000..92543ebe --- /dev/null +++ b/shared/systemd/src/shared/dns-domain.c @@ -0,0 +1,1387 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "nm-sd-adapt-shared.h" + +#if 0 /* NM_IGNORED */ +#if HAVE_LIBIDN2 +# include <idn2.h> +#elif HAVE_LIBIDN +# include <idna.h> +# include <stringprep.h> +#endif +#endif + +#include <endian.h> +#include <netinet/in.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> + +#include "alloc-util.h" +#include "dns-domain.h" +#include "hashmap.h" +#include "hexdecoct.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "utf8.h" + +int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags) { + const char *n; + char *d, last_char = 0; + int r = 0; + + assert(name); + assert(*name); + + n = *name; + d = dest; + + for (;;) { + if (IN_SET(*n, 0, '.')) { + if (FLAGS_SET(flags, DNS_LABEL_LDH) && last_char == '-') + /* Trailing dash */ + return -EINVAL; + + if (*n == '.') + n++; + break; + } + + if (r >= DNS_LABEL_MAX) + return -EINVAL; + + if (sz <= 0) + return -ENOBUFS; + + if (*n == '\\') { + /* Escaped character */ + if (FLAGS_SET(flags, DNS_LABEL_NO_ESCAPES)) + return -EINVAL; + + n++; + + if (*n == 0) + /* Ending NUL */ + return -EINVAL; + + else if (IN_SET(*n, '\\', '.')) { + /* Escaped backslash or dot */ + + if (FLAGS_SET(flags, DNS_LABEL_LDH)) + return -EINVAL; + + last_char = *n; + if (d) + *(d++) = *n; + sz--; + r++; + n++; + + } else if (n[0] >= '0' && n[0] <= '9') { + unsigned k; + + /* Escaped literal ASCII character */ + + if (!(n[1] >= '0' && n[1] <= '9') || + !(n[2] >= '0' && n[2] <= '9')) + return -EINVAL; + + k = ((unsigned) (n[0] - '0') * 100) + + ((unsigned) (n[1] - '0') * 10) + + ((unsigned) (n[2] - '0')); + + /* Don't allow anything that doesn't + * fit in 8bit. Note that we do allow + * control characters, as some servers + * (e.g. cloudflare) are happy to + * generate labels with them + * inside. */ + if (k > 255) + return -EINVAL; + + if (FLAGS_SET(flags, DNS_LABEL_LDH) && + !valid_ldh_char((char) k)) + return -EINVAL; + + last_char = (char) k; + if (d) + *(d++) = (char) k; + sz--; + r++; + + n += 3; + } else + return -EINVAL; + + } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) { + + /* Normal character */ + + if (FLAGS_SET(flags, DNS_LABEL_LDH)) { + if (!valid_ldh_char(*n)) + return -EINVAL; + if (r == 0 && *n == '-') + /* Leading dash */ + return -EINVAL; + } + + last_char = *n; + if (d) + *(d++) = *n; + sz--; + r++; + n++; + } else + return -EINVAL; + } + + /* Empty label that is not at the end? */ + if (r == 0 && *n) + return -EINVAL; + + /* More than one trailing dot? */ + if (*n == '.') + return -EINVAL; + + if (sz >= 1 && d) + *d = 0; + + *name = n; + return r; +} + +#if 0 /* NM_IGNORED */ +/* @label_terminal: terminal character of a label, updated to point to the terminal character of + * the previous label (always skipping one dot) or to NULL if there are no more + * labels. */ +int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) { + const char *terminal; + int r; + + assert(name); + assert(label_terminal); + assert(dest); + + /* no more labels */ + if (!*label_terminal) { + if (sz >= 1) + *dest = 0; + + return 0; + } + + terminal = *label_terminal; + assert(IN_SET(*terminal, 0, '.')); + + /* Skip current terminal character (and accept domain names ending it ".") */ + if (*terminal == 0) + terminal--; + if (terminal >= name && *terminal == '.') + terminal--; + + /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ + for (;;) { + if (terminal < name) { + /* Reached the first label, so indicate that there are no more */ + terminal = NULL; + break; + } + + /* Find the start of the last label */ + if (*terminal == '.') { + const char *y; + unsigned slashes = 0; + + for (y = terminal - 1; y >= name && *y == '\\'; y--) + slashes++; + + if (slashes % 2 == 0) { + /* The '.' was not escaped */ + name = terminal + 1; + break; + } else { + terminal = y; + continue; + } + } + + terminal--; + } + + r = dns_label_unescape(&name, dest, sz, 0); + if (r < 0) + return r; + + *label_terminal = terminal; + + return r; +} +#endif /* NM_IGNORED */ + +int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { + char *q; + + /* DNS labels must be between 1 and 63 characters long. A + * zero-length label does not exist. See RFC 2182, Section + * 11. */ + + if (l <= 0 || l > DNS_LABEL_MAX) + return -EINVAL; + if (sz < 1) + return -ENOBUFS; + + assert(p); + assert(dest); + + q = dest; + while (l > 0) { + + if (IN_SET(*p, '.', '\\')) { + + /* Dot or backslash */ + + if (sz < 3) + return -ENOBUFS; + + *(q++) = '\\'; + *(q++) = *p; + + sz -= 2; + + } else if (IN_SET(*p, '_', '-') || + (*p >= '0' && *p <= '9') || + (*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z')) { + + /* Proper character */ + + if (sz < 2) + return -ENOBUFS; + + *(q++) = *p; + sz -= 1; + + } else { + + /* Everything else */ + + if (sz < 5) + return -ENOBUFS; + + *(q++) = '\\'; + *(q++) = '0' + (char) ((uint8_t) *p / 100); + *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10); + *(q++) = '0' + (char) ((uint8_t) *p % 10); + + sz -= 4; + } + + p++; + l--; + } + + *q = 0; + return (int) (q - dest); +} + +#if 0 /* NM_IGNORED */ +int dns_label_escape_new(const char *p, size_t l, char **ret) { + _cleanup_free_ char *s = NULL; + int r; + + assert(p); + assert(ret); + + if (l <= 0 || l > DNS_LABEL_MAX) + return -EINVAL; + + s = new(char, DNS_LABEL_ESCAPED_MAX); + if (!s) + return -ENOMEM; + + r = dns_label_escape(p, l, s, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + *ret = TAKE_PTR(s); + + return r; +} + +#if HAVE_LIBIDN +int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { + _cleanup_free_ uint32_t *input = NULL; + size_t input_size, l; + const char *p; + bool contains_8bit = false; + char buffer[DNS_LABEL_MAX+1]; + + assert(encoded); + assert(decoded); + + /* Converts an U-label into an A-label */ + + if (encoded_size <= 0) + return -EINVAL; + + for (p = encoded; p < encoded + encoded_size; p++) + if ((uint8_t) *p > 127) + contains_8bit = true; + + if (!contains_8bit) { + if (encoded_size > DNS_LABEL_MAX) + return -EINVAL; + + return 0; + } + + input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); + if (!input) + return -ENOMEM; + + if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0) + return -EINVAL; + + l = strlen(buffer); + + /* Verify that the result is not longer than one DNS label. */ + if (l <= 0 || l > DNS_LABEL_MAX) + return -EINVAL; + if (l > decoded_max) + return -ENOBUFS; + + memcpy(decoded, buffer, l); + + /* If there's room, append a trailing NUL byte, but only then */ + if (decoded_max > l) + decoded[l] = 0; + + return (int) l; +} + +int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { + size_t input_size, output_size; + _cleanup_free_ uint32_t *input = NULL; + _cleanup_free_ char *result = NULL; + uint32_t *output = NULL; + size_t w; + + /* To be invoked after unescaping. Converts an A-label into an U-label. */ + + assert(encoded); + assert(decoded); + + if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX) + return -EINVAL; + + if (!memory_startswith(encoded, encoded_size, IDNA_ACE_PREFIX)) + return 0; + + input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); + if (!input) + return -ENOMEM; + + output_size = input_size; + output = newa(uint32_t, output_size); + + idna_to_unicode_44i(input, input_size, output, &output_size, 0); + + result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w); + if (!result) + return -ENOMEM; + if (w <= 0) + return -EINVAL; + if (w > decoded_max) + return -ENOBUFS; + + memcpy(decoded, result, w); + + /* Append trailing NUL byte if there's space, but only then. */ + if (decoded_max > w) + decoded[w] = 0; + + return w; +} +#endif +#endif /* NM_IGNORED */ + +int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_ret) { + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + const char *p; + bool first = true; + int r; + + if (a) + p = a; + else if (b) + p = TAKE_PTR(b); + else + goto finish; + + for (;;) { + char label[DNS_LABEL_MAX]; + + r = dns_label_unescape(&p, label, sizeof label, flags); + if (r < 0) + return r; + if (r == 0) { + if (*p != 0) + return -EINVAL; + + if (b) { + /* Now continue with the second string, if there is one */ + p = TAKE_PTR(b); + continue; + } + + break; + } + + if (_ret) { + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + r = dns_label_escape(label, r, ret + n + !first, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + if (!first) + ret[n] = '.'; + } else { + char escaped[DNS_LABEL_ESCAPED_MAX]; + + r = dns_label_escape(label, r, escaped, sizeof(escaped)); + if (r < 0) + return r; + } + + if (!first) + n++; + else + first = false; + + n += r; + } + +finish: + if (n > DNS_HOSTNAME_MAX) + return -EINVAL; + + if (_ret) { + if (n == 0) { + /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */ + if (!GREEDY_REALLOC(ret, allocated, 2)) + return -ENOMEM; + + ret[n++] = '.'; + } else { + if (!GREEDY_REALLOC(ret, allocated, n + 1)) + return -ENOMEM; + } + + ret[n] = 0; + *_ret = TAKE_PTR(ret); + } + + return 0; +} + +#if 0 /* NM_IGNORED */ +void dns_name_hash_func(const char *p, struct siphash *state) { + int r; + + assert(p); + + for (;;) { + char label[DNS_LABEL_MAX+1]; + + r = dns_label_unescape(&p, label, sizeof label, 0); + if (r < 0) + break; + if (r == 0) + break; + + ascii_strlower_n(label, r); + siphash24_compress(label, r, state); + siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */ + } + + /* enforce that all names are terminated by the empty label */ + string_hash_func("", state); +} + +int dns_name_compare_func(const char *a, const char *b) { + const char *x, *y; + int r, q; + + assert(a); + assert(b); + + x = a + strlen(a); + y = b + strlen(b); + + for (;;) { + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + + if (x == NULL && y == NULL) + return 0; + + r = dns_label_unescape_suffix(a, &x, la, sizeof(la)); + q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb)); + if (r < 0 || q < 0) + return CMP(r, q); + + r = ascii_strcasecmp_nn(la, r, lb, q); + if (r != 0) + return r; + } +} + +DEFINE_HASH_OPS(dns_name_hash_ops, char, dns_name_hash_func, dns_name_compare_func); + +int dns_name_equal(const char *x, const char *y) { + int r, q; + + assert(x); + assert(y); + + for (;;) { + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + + r = dns_label_unescape(&x, la, sizeof la, 0); + if (r < 0) + return r; + + q = dns_label_unescape(&y, lb, sizeof lb, 0); + if (q < 0) + return q; + + if (r != q) + return false; + if (r == 0) + return true; + + if (ascii_strcasecmp_n(la, lb, r) != 0) + return false; + } +} + +int dns_name_endswith(const char *name, const char *suffix) { + const char *n, *s, *saved_n = NULL; + int r, q; + + assert(name); + assert(suffix); + + n = name; + s = suffix; + + for (;;) { + char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + + r = dns_label_unescape(&n, ln, sizeof ln, 0); + if (r < 0) + return r; + + if (!saved_n) + saved_n = n; + + q = dns_label_unescape(&s, ls, sizeof ls, 0); + if (q < 0) + return q; + + if (r == 0 && q == 0) + return true; + if (r == 0 && saved_n == n) + return false; + + if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { + + /* Not the same, let's jump back, and try with the next label again */ + s = suffix; + n = TAKE_PTR(saved_n); + } + } +} + +int dns_name_startswith(const char *name, const char *prefix) { + const char *n, *p; + int r, q; + + assert(name); + assert(prefix); + + n = name; + p = prefix; + + for (;;) { + char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; + + r = dns_label_unescape(&p, lp, sizeof lp, 0); + if (r < 0) + return r; + if (r == 0) + return true; + + q = dns_label_unescape(&n, ln, sizeof ln, 0); + if (q < 0) + return q; + + if (r != q) + return false; + if (ascii_strcasecmp_n(ln, lp, r) != 0) + return false; + } +} + +int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) { + const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix; + int r, q; + + assert(name); + assert(old_suffix); + assert(new_suffix); + assert(ret); + + n = name; + s = old_suffix; + + for (;;) { + char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + + if (!saved_before) + saved_before = n; + + r = dns_label_unescape(&n, ln, sizeof ln, 0); + if (r < 0) + return r; + + if (!saved_after) + saved_after = n; + + q = dns_label_unescape(&s, ls, sizeof ls, 0); + if (q < 0) + return q; + + if (r == 0 && q == 0) + break; + if (r == 0 && saved_after == n) { + *ret = NULL; /* doesn't match */ + return 0; + } + + if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { + + /* Not the same, let's jump back, and try with the next label again */ + s = old_suffix; + n = TAKE_PTR(saved_after); + saved_before = NULL; + } + } + + /* Found it! Now generate the new name */ + prefix = strndupa(name, saved_before - name); + + r = dns_name_concat(prefix, new_suffix, 0, ret); + if (r < 0) + return r; + + return 1; +} + +int dns_name_between(const char *a, const char *b, const char *c) { + /* Determine if b is strictly greater than a and strictly smaller than c. + We consider the order of names to be circular, so that if a is + strictly greater than c, we consider b to be between them if it is + either greater than a or smaller than c. This is how the canonical + DNS name order used in NSEC records work. */ + + if (dns_name_compare_func(a, c) < 0) + /* + a and c are properly ordered: + a<---b--->c + */ + return dns_name_compare_func(a, b) < 0 && + dns_name_compare_func(b, c) < 0; + else + /* + a and c are equal or 'reversed': + <--b--c a-----> + or: + <-----c a--b--> + */ + return dns_name_compare_func(b, c) < 0 || + dns_name_compare_func(a, b) < 0; +} + +int dns_name_reverse(int family, const union in_addr_union *a, char **ret) { + const uint8_t *p; + int r; + + assert(a); + assert(ret); + + p = (const uint8_t*) a; + + if (family == AF_INET) + r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]); + else if (family == AF_INET6) + r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa", + hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4), + hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4), + hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4), + hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4), + hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4), + hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4), + hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4), + hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4)); + else + return -EAFNOSUPPORT; + if (r < 0) + return -ENOMEM; + + return 0; +} + +int dns_name_address(const char *p, int *family, union in_addr_union *address) { + int r; + + assert(p); + assert(family); + assert(address); + + r = dns_name_endswith(p, "in-addr.arpa"); + if (r < 0) + return r; + if (r > 0) { + uint8_t a[4]; + unsigned i; + + for (i = 0; i < ELEMENTSOF(a); i++) { + char label[DNS_LABEL_MAX+1]; + + r = dns_label_unescape(&p, label, sizeof label, 0); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + if (r > 3) + return -EINVAL; + + r = safe_atou8(label, &a[i]); + if (r < 0) + return r; + } + + r = dns_name_equal(p, "in-addr.arpa"); + if (r <= 0) + return r; + + *family = AF_INET; + address->in.s_addr = htobe32(((uint32_t) a[3] << 24) | + ((uint32_t) a[2] << 16) | + ((uint32_t) a[1] << 8) | + (uint32_t) a[0]); + + return 1; + } + + r = dns_name_endswith(p, "ip6.arpa"); + if (r < 0) + return r; + if (r > 0) { + struct in6_addr a; + unsigned i; + + for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) { + char label[DNS_LABEL_MAX+1]; + int x, y; + + r = dns_label_unescape(&p, label, sizeof label, 0); + if (r <= 0) + return r; + if (r != 1) + return -EINVAL; + x = unhexchar(label[0]); + if (x < 0) + return -EINVAL; + + r = dns_label_unescape(&p, label, sizeof label, 0); + if (r <= 0) + return r; + if (r != 1) + return -EINVAL; + y = unhexchar(label[0]); + if (y < 0) + return -EINVAL; + + a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x; + } + + r = dns_name_equal(p, "ip6.arpa"); + if (r <= 0) + return r; + + *family = AF_INET6; + address->in6 = a; + return 1; + } + + return 0; +} +#endif /* NM_IGNORED */ + +bool dns_name_is_root(const char *name) { + + assert(name); + + /* There are exactly two ways to encode the root domain name: + * as empty string, or with a single dot. */ + + return STR_IN_SET(name, "", "."); +} + +bool dns_name_is_single_label(const char *name) { + int r; + + assert(name); + + r = dns_name_parent(&name); + if (r <= 0) + return false; + + return dns_name_is_root(name); +} + +/* Encode a domain name according to RFC 1035 Section 3.1, without compression */ +int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) { + uint8_t *label_length, *out; + int r; + + assert(domain); + assert(buffer); + + out = buffer; + + do { + /* Reserve a byte for label length */ + if (len <= 0) + return -ENOBUFS; + len--; + label_length = out; + out++; + + /* Convert and copy a single label. Note that + * dns_label_unescape() returns 0 when it hits the end + * of the domain name, which we rely on here to encode + * the trailing NUL byte. */ + r = dns_label_unescape(&domain, (char *) out, len, 0); + if (r < 0) + return r; + + /* Optionally, output the name in DNSSEC canonical + * format, as described in RFC 4034, section 6.2. Or + * in other words: in lower-case. */ + if (canonical) + ascii_strlower_n((char*) out, (size_t) r); + + /* Fill label length, move forward */ + *label_length = r; + out += r; + len -= r; + + } while (r != 0); + + /* Verify the maximum size of the encoded name. The trailing + * dot + NUL byte account are included this time, hence + * compare against DNS_HOSTNAME_MAX + 2 (which is 255) this + * time. */ + if (out - buffer > DNS_HOSTNAME_MAX + 2) + return -EINVAL; + + return out - buffer; +} + +#if 0 /* NM_IGNORED */ +static bool srv_type_label_is_valid(const char *label, size_t n) { + size_t k; + + assert(label); + + if (n < 2) /* Label needs to be at least 2 chars long */ + return false; + + if (label[0] != '_') /* First label char needs to be underscore */ + return false; + + /* Second char must be a letter */ + if (!(label[1] >= 'A' && label[1] <= 'Z') && + !(label[1] >= 'a' && label[1] <= 'z')) + return false; + + /* Third and further chars must be alphanumeric or a hyphen */ + for (k = 2; k < n; k++) { + if (!(label[k] >= 'A' && label[k] <= 'Z') && + !(label[k] >= 'a' && label[k] <= 'z') && + !(label[k] >= '0' && label[k] <= '9') && + label[k] != '-') + return false; + } + + return true; +} + +bool dns_srv_type_is_valid(const char *name) { + unsigned c = 0; + int r; + + if (!name) + return false; + + for (;;) { + char label[DNS_LABEL_MAX]; + + /* This more or less implements RFC 6335, Section 5.1 */ + + r = dns_label_unescape(&name, label, sizeof label, 0); + if (r < 0) + return false; + if (r == 0) + break; + + if (c >= 2) + return false; + + if (!srv_type_label_is_valid(label, r)) + return false; + + c++; + } + + return c == 2; /* exactly two labels */ +} + +bool dnssd_srv_type_is_valid(const char *name) { + return dns_srv_type_is_valid(name) && + ((dns_name_endswith(name, "_tcp") > 0) || + (dns_name_endswith(name, "_udp") > 0)); /* Specific to DNS-SD. RFC 6763, Section 7 */ +} + +bool dns_service_name_is_valid(const char *name) { + size_t l; + + /* This more or less implements RFC 6763, Section 4.1.1 */ + + if (!name) + return false; + + if (!utf8_is_valid(name)) + return false; + + if (string_has_cc(name, NULL)) + return false; + + l = strlen(name); + if (l <= 0) + return false; + if (l > 63) + return false; + + return true; +} + +int dns_service_join(const char *name, const char *type, const char *domain, char **ret) { + char escaped[DNS_LABEL_ESCAPED_MAX]; + _cleanup_free_ char *n = NULL; + int r; + + assert(type); + assert(domain); + assert(ret); + + if (!dns_srv_type_is_valid(type)) + return -EINVAL; + + if (!name) + return dns_name_concat(type, domain, 0, ret); + + if (!dns_service_name_is_valid(name)) + return -EINVAL; + + r = dns_label_escape(name, strlen(name), escaped, sizeof(escaped)); + if (r < 0) + return r; + + r = dns_name_concat(type, domain, 0, &n); + if (r < 0) + return r; + + return dns_name_concat(escaped, n, 0, ret); +} + +static bool dns_service_name_label_is_valid(const char *label, size_t n) { + char *s; + + assert(label); + + if (memchr(label, 0, n)) + return false; + + s = strndupa(label, n); + return dns_service_name_is_valid(s); +} + +int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) { + _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; + const char *p = joined, *q = NULL, *d = NULL; + char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX]; + int an, bn, cn, r; + unsigned x = 0; + + assert(joined); + + /* Get first label from the full name */ + an = dns_label_unescape(&p, a, sizeof(a), 0); + if (an < 0) + return an; + + if (an > 0) { + x++; + + /* If there was a first label, try to get the second one */ + bn = dns_label_unescape(&p, b, sizeof(b), 0); + if (bn < 0) + return bn; + + if (bn > 0) { + x++; + + /* If there was a second label, try to get the third one */ + q = p; + cn = dns_label_unescape(&p, c, sizeof(c), 0); + if (cn < 0) + return cn; + + if (cn > 0) + x++; + } else + cn = 0; + } else + an = 0; + + if (x >= 2 && srv_type_label_is_valid(b, bn)) { + + if (x >= 3 && srv_type_label_is_valid(c, cn)) { + + if (dns_service_name_label_is_valid(a, an)) { + /* OK, got <name> . <type> . <type2> . <domain> */ + + name = strndup(a, an); + if (!name) + return -ENOMEM; + + type = strjoin(b, ".", c); + if (!type) + return -ENOMEM; + + d = p; + goto finish; + } + + } else if (srv_type_label_is_valid(a, an)) { + + /* OK, got <type> . <type2> . <domain> */ + + name = NULL; + + type = strjoin(a, ".", b); + if (!type) + return -ENOMEM; + + d = q; + goto finish; + } + } + + name = NULL; + type = NULL; + d = joined; + +finish: + r = dns_name_normalize(d, 0, &domain); + if (r < 0) + return r; + + if (_domain) + *_domain = TAKE_PTR(domain); + + if (_type) + *_type = TAKE_PTR(type); + + if (_name) + *_name = TAKE_PTR(name); + + return 0; +} + +static int dns_name_build_suffix_table(const char *name, const char *table[]) { + const char *p; + unsigned n = 0; + int r; + + assert(name); + assert(table); + + p = name; + for (;;) { + if (n > DNS_N_LABELS_MAX) + return -EINVAL; + + table[n] = p; + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + + n++; + } + + return (int) n; +} + +int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { + const char* labels[DNS_N_LABELS_MAX+1]; + int n; + + assert(name); + assert(ret); + + n = dns_name_build_suffix_table(name, labels); + if (n < 0) + return n; + + if ((unsigned) n < n_labels) + return -EINVAL; + + *ret = labels[n - n_labels]; + return (int) (n - n_labels); +} + +int dns_name_skip(const char *a, unsigned n_labels, const char **ret) { + int r; + + assert(a); + assert(ret); + + for (; n_labels > 0; n_labels--) { + r = dns_name_parent(&a); + if (r < 0) + return r; + if (r == 0) { + *ret = ""; + return 0; + } + } + + *ret = a; + return 1; +} + +int dns_name_count_labels(const char *name) { + unsigned n = 0; + const char *p; + int r; + + assert(name); + + p = name; + for (;;) { + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + + if (n >= DNS_N_LABELS_MAX) + return -EINVAL; + + n++; + } + + return (int) n; +} + +int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) { + int r; + + assert(a); + assert(b); + + r = dns_name_skip(a, n_labels, &a); + if (r <= 0) + return r; + + return dns_name_equal(a, b); +} + +int dns_name_common_suffix(const char *a, const char *b, const char **ret) { + const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1]; + int n = 0, m = 0, k = 0, r, q; + + assert(a); + assert(b); + assert(ret); + + /* Determines the common suffix of domain names a and b */ + + n = dns_name_build_suffix_table(a, a_labels); + if (n < 0) + return n; + + m = dns_name_build_suffix_table(b, b_labels); + if (m < 0) + return m; + + for (;;) { + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + const char *x, *y; + + if (k >= n || k >= m) { + *ret = a_labels[n - k]; + return 0; + } + + x = a_labels[n - 1 - k]; + r = dns_label_unescape(&x, la, sizeof la, 0); + if (r < 0) + return r; + + y = b_labels[m - 1 - k]; + q = dns_label_unescape(&y, lb, sizeof lb, 0); + if (q < 0) + return q; + + if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) { + *ret = a_labels[n - k]; + return 0; + } + + k++; + } +} + +int dns_name_apply_idna(const char *name, char **ret) { + /* Return negative on error, 0 if not implemented, positive on success. */ + +#if HAVE_LIBIDN2 + int r; + _cleanup_free_ char *t = NULL; + + assert(name); + assert(ret); + + r = idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t, + IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); + log_debug("idn2_lookup_u8: %s → %s", name, t); + if (r == IDN2_OK) { + if (!startswith(name, "xn--")) { + _cleanup_free_ char *s = NULL; + + r = idn2_to_unicode_8z8z(t, &s, 0); + if (r != IDN2_OK) { + log_debug("idn2_to_unicode_8z8z(\"%s\") failed: %d/%s", + t, r, idn2_strerror(r)); + return 0; + } + + if (!streq_ptr(name, s)) { + log_debug("idn2 roundtrip failed: \"%s\" → \"%s\" → \"%s\", ignoring.", + name, t, s); + return 0; + } + } + + *ret = TAKE_PTR(t); + + return 1; /* *ret has been written */ + } + + log_debug("idn2_lookup_u8(\"%s\") failed: %d/%s", name, r, idn2_strerror(r)); + if (r == IDN2_2HYPHEN) + /* The name has two hyphens — forbidden by IDNA2008 in some cases */ + return 0; + if (IN_SET(r, IDN2_TOO_BIG_DOMAIN, IDN2_TOO_BIG_LABEL)) + return -ENOSPC; + return -EINVAL; +#elif HAVE_LIBIDN + _cleanup_free_ char *buf = NULL; + size_t n = 0, allocated = 0; + bool first = true; + int r, q; + + assert(name); + assert(ret); + + for (;;) { + char label[DNS_LABEL_MAX]; + + r = dns_label_unescape(&name, label, sizeof label, 0); + if (r < 0) + return r; + if (r == 0) + break; + + q = dns_label_apply_idna(label, r, label, sizeof label); + if (q < 0) + return q; + if (q > 0) + r = q; + + if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + if (first) + first = false; + else + buf[n++] = '.'; + + n += r; + } + + if (n > DNS_HOSTNAME_MAX) + return -EINVAL; + + if (!GREEDY_REALLOC(buf, allocated, n + 1)) + return -ENOMEM; + + buf[n] = 0; + *ret = TAKE_PTR(buf); + + return 1; +#else + return 0; +#endif +} + +int dns_name_is_valid_or_address(const char *name) { + /* Returns > 0 if the specified name is either a valid IP address formatted as string or a valid DNS name */ + + if (isempty(name)) + return 0; + + if (in_addr_from_string_auto(name, NULL, NULL) >= 0) + return 1; + + return dns_name_is_valid(name); +} +#endif /* NM_IGNORED */ diff --git a/shared/systemd/src/shared/dns-domain.h b/shared/systemd/src/shared/dns-domain.h new file mode 100644 index 00000000..88b3eb11 --- /dev/null +++ b/shared/systemd/src/shared/dns-domain.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include "hashmap.h" +#include "in-addr-util.h" + +/* Length of a single label, with all escaping removed, excluding any trailing dot or NUL byte */ +#define DNS_LABEL_MAX 63 + +/* Worst case length of a single label, with all escaping applied and room for a trailing NUL byte. */ +#define DNS_LABEL_ESCAPED_MAX (DNS_LABEL_MAX*4+1) + +/* Maximum length of a full hostname, consisting of a series of unescaped labels, and no trailing dot or NUL byte */ +#define DNS_HOSTNAME_MAX 253 + +/* Maximum length of a full hostname, on the wire, including the final NUL byte */ +#define DNS_WIRE_FORMAT_HOSTNAME_MAX 255 + +/* Maximum number of labels per valid hostname */ +#define DNS_N_LABELS_MAX 127 + +typedef enum DNSLabelFlags { + DNS_LABEL_LDH = 1 << 0, /* Follow the "LDH" rule — only letters, digits, and internal hyphens. */ + DNS_LABEL_NO_ESCAPES = 1 << 1, /* Do not treat backslashes specially */ +} DNSLabelFlags; + +int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags); +int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz); +int dns_label_escape(const char *p, size_t l, char *dest, size_t sz); +int dns_label_escape_new(const char *p, size_t l, char **ret); + +static inline int dns_name_parent(const char **name) { + return dns_label_unescape(name, NULL, DNS_LABEL_MAX, 0); +} + +#if 0 /* NM_IGNORED */ +#if HAVE_LIBIDN +int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); +int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); +#endif +#endif /* NM_IGNORED */ + +int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **ret); + +static inline int dns_name_normalize(const char *s, DNSLabelFlags flags, char **ret) { + /* dns_name_concat() normalizes as a side-effect */ + return dns_name_concat(s, NULL, flags, ret); +} + +static inline int dns_name_is_valid(const char *s) { + int r; + + /* dns_name_normalize() verifies as a side effect */ + r = dns_name_normalize(s, 0, NULL); + if (r == -EINVAL) + return 0; + if (r < 0) + return r; + return 1; +} + +static inline int dns_name_is_valid_ldh(const char *s) { + int r; + + r = dns_name_concat(s, NULL, DNS_LABEL_LDH|DNS_LABEL_NO_ESCAPES, NULL); + if (r == -EINVAL) + return 0; + if (r < 0) + return r; + return 1; +} + +void dns_name_hash_func(const char *s, struct siphash *state); +int dns_name_compare_func(const char *a, const char *b); +extern const struct hash_ops dns_name_hash_ops; + +int dns_name_between(const char *a, const char *b, const char *c); +int dns_name_equal(const char *x, const char *y); +int dns_name_endswith(const char *name, const char *suffix); +int dns_name_startswith(const char *name, const char *prefix); + +int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret); + +int dns_name_reverse(int family, const union in_addr_union *a, char **ret); +int dns_name_address(const char *p, int *family, union in_addr_union *a); + +bool dns_name_is_root(const char *name); +bool dns_name_is_single_label(const char *name); + +int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical); + +bool dns_srv_type_is_valid(const char *name); +bool dnssd_srv_type_is_valid(const char *name); +bool dns_service_name_is_valid(const char *name); + +int dns_service_join(const char *name, const char *type, const char *domain, char **ret); +int dns_service_split(const char *joined, char **name, char **type, char **domain); + +int dns_name_suffix(const char *name, unsigned n_labels, const char **ret); +int dns_name_count_labels(const char *name); + +int dns_name_skip(const char *a, unsigned n_labels, const char **ret); +int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b); + +int dns_name_common_suffix(const char *a, const char *b, const char **ret); + +int dns_name_apply_idna(const char *name, char **ret); + +int dns_name_is_valid_or_address(const char *name); |