summary refs log tree commit diff
path: root/shared/n-dhcp4
diff options
context:
space:
mode:
Diffstat (limited to 'shared/n-dhcp4')
-rw-r--r--shared/n-dhcp4/src/n-dhcp4-c-connection.c67
-rw-r--r--shared/n-dhcp4/src/n-dhcp4-c-probe.c6
-rw-r--r--shared/n-dhcp4/src/n-dhcp4-client.c150
-rw-r--r--shared/n-dhcp4/src/n-dhcp4-private.h77
-rw-r--r--shared/n-dhcp4/src/n-dhcp4.h15
5 files changed, 244 insertions, 71 deletions
diff --git a/shared/n-dhcp4/src/n-dhcp4-c-connection.c b/shared/n-dhcp4/src/n-dhcp4-c-connection.c
index 30514e28..8c32a984 100644
--- a/shared/n-dhcp4/src/n-dhcp4-c-connection.c
+++ b/shared/n-dhcp4/src/n-dhcp4-c-connection.c
@@ -23,6 +23,7 @@
  * @connection:                 connection to operate on
  * @client_config:              client configuration to use
  * @probe_config:               client probe configuration to use
+ * @log_queue:                  the log queue for logging events
  * @fd_epoll:                   epoll context to attach to, or -1
  *
  * This initializes a new client connection using the configuration given in
@@ -47,11 +48,13 @@
 int n_dhcp4_c_connection_init(NDhcp4CConnection *connection,
                               NDhcp4ClientConfig *client_config,
                               NDhcp4ClientProbeConfig *probe_config,
+                              NDhcp4LogQueue *log_queue,
                               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;
+        connection->log_queue = log_queue;
 
         /*
          * We explicitly allow initializing connections with an invalid
@@ -1066,25 +1069,27 @@ static int n_dhcp4_c_connection_send_request(NDhcp4CConnection *connection,
         }
 
         if (request->userdata.client_addr == INADDR_ANY) {
-                n_dhcp4_c_log(connection->client_config, LOG_INFO,
-                              "send %s to %s%s",
-                              message_type_to_str(request->userdata.message_type),
-                              broadcast ?
-                              "255.255.255.255" :
-                              inet_ntop(AF_INET, &connection->server_ip,
-                                        server_addr, sizeof(server_addr)),
-                              error_msg);
+                n_dhcp4_log(connection->log_queue,
+                            LOG_INFO,
+                            "send %s to %s%s",
+                            message_type_to_str(request->userdata.message_type),
+                            broadcast ?
+                            "255.255.255.255" :
+                            inet_ntop(AF_INET, &connection->server_ip,
+                                      server_addr, sizeof(server_addr)),
+                            error_msg);
         } else {
-                n_dhcp4_c_log(connection->client_config, LOG_INFO,
-                              "send %s of %s to %s%s",
-                              message_type_to_str(request->userdata.message_type),
-                              inet_ntop(AF_INET, &request->userdata.client_addr,
-                                        client_addr, sizeof(client_addr)),
-                              broadcast ?
-                              "255.255.255.255" :
-                              inet_ntop(AF_INET, &connection->server_ip,
-                                        server_addr, sizeof(server_addr)),
-                              error_msg);
+                n_dhcp4_log(connection->log_queue,
+                            LOG_INFO,
+                            "send %s of %s to %s%s",
+                            message_type_to_str(request->userdata.message_type),
+                            inet_ntop(AF_INET, &request->userdata.client_addr,
+                                      client_addr, sizeof(client_addr)),
+                            broadcast ?
+                            "255.255.255.255" :
+                            inet_ntop(AF_INET, &connection->server_ip,
+                                      server_addr, sizeof(server_addr)),
+                            error_msg);
         }
 
         ++request->userdata.n_send;
@@ -1207,19 +1212,21 @@ int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection,
                 return N_DHCP4_E_AGAIN;
 
         if (type == N_DHCP4_MESSAGE_OFFER || type == N_DHCP4_MESSAGE_ACK) {
-                n_dhcp4_c_log(connection->client_config, LOG_INFO,
-                              "received %s of %s from %s",
-                              message_type_to_str(type),
-                              inet_ntop(AF_INET, &message->message.header.yiaddr,
-                                        client_addr, sizeof(client_addr)),
-                              inet_ntop(AF_INET, &message->message.header.siaddr,
-                                        serv_addr, sizeof(serv_addr)));
+                n_dhcp4_log(connection->log_queue,
+                            LOG_INFO,
+                            "received %s of %s from %s",
+                            message_type_to_str(type),
+                            inet_ntop(AF_INET, &message->message.header.yiaddr,
+                                      client_addr, sizeof(client_addr)),
+                            inet_ntop(AF_INET, &message->message.header.siaddr,
+                                      serv_addr, sizeof(serv_addr)));
         } else {
-                n_dhcp4_c_log(connection->client_config, LOG_INFO,
-                              "received %s from %s",
-                              message_type_to_str(type),
-                              inet_ntop(AF_INET, &message->message.header.siaddr,
-                                        serv_addr, sizeof(serv_addr)));
+                n_dhcp4_log(connection->log_queue,
+                            LOG_INFO,
+                            "received %s from %s",
+                            message_type_to_str(type),
+                            inet_ntop(AF_INET, &message->message.header.siaddr,
+                                      serv_addr, sizeof(serv_addr)));
         }
 
         switch (type) {
diff --git a/shared/n-dhcp4/src/n-dhcp4-c-probe.c b/shared/n-dhcp4/src/n-dhcp4-c-probe.c
index 5e971298..f3d4f265 100644
--- a/shared/n-dhcp4/src/n-dhcp4-c-probe.c
+++ b/shared/n-dhcp4/src/n-dhcp4-c-probe.c
@@ -429,9 +429,13 @@ int n_dhcp4_client_probe_new(NDhcp4ClientProbe **probep,
          */
         n_dhcp4_client_probe_config_initialize_random_seed(probe->config);
 
+        /* The new probe keeps a reference on @client. So we are sure that &client->log_queue
+         * stays alive as long as we need it. */
+
         r = n_dhcp4_c_connection_init(&probe->connection,
                                       client->config,
                                       probe->config,
+                                      &client->log_queue,
                                       active ? client->fd_epoll : -1);
         if (r)
                 return r;
@@ -1091,7 +1095,7 @@ int n_dhcp4_client_probe_transition_accept(NDhcp4ClientProbe *probe, NDhcp4Incom
 
                 probe->state = N_DHCP4_CLIENT_PROBE_STATE_BOUND;
 
-                n_dhcp4_client_arm_timer (probe->client);
+                n_dhcp4_client_arm_timer(probe->client);
 
                 break;
 
diff --git a/shared/n-dhcp4/src/n-dhcp4-client.c b/shared/n-dhcp4/src/n-dhcp4-client.c
index 6b015e81..403a6932 100644
--- a/shared/n-dhcp4/src/n-dhcp4-client.c
+++ b/shared/n-dhcp4/src/n-dhcp4-client.c
@@ -94,9 +94,6 @@ int n_dhcp4_client_config_dup(NDhcp4ClientConfig *config, NDhcp4ClientConfig **d
         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;
-        dup->log.level = config->log.level;
-        dup->log.func = config->log.func;
-        dup->log.data = config->log.data;
 
         r = n_dhcp4_client_config_set_client_id(dup,
                                                 config->client_id,
@@ -227,37 +224,64 @@ _c_public_ void n_dhcp4_client_config_set_broadcast_mac(NDhcp4ClientConfig *conf
  * 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
+ * @n_id:                       length of the client id in bytes. The length
+ *                              must be from 2 up to 255 bytes. Set it to 0
+ *                              to unset the client-id.
  *
  * This sets the client-id property of @config. It copies the entire client-id
  * buffer into the configuration.
+ * See RFC 2132 (section 9.14) for the format of the Client Identifier.
  *
  * 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;
 
+        if (n_id == 0) {
+                config->client_id = c_free(config->client_id);
+                config->n_client_id = 0;
+                return 0;
+        }
+
+        if (n_id < 2 || n_id > 255)
+                return -EINVAL;
+
         t = malloc(n_id + 1);
         if (!t)
                 return -ENOMEM;
 
+        memcpy(t, id, n_id);
+        t[n_id] = 0; /* safety 0 for debugging */
+
         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;
 }
 
-_c_public_ void n_dhcp4_client_config_set_log_level(NDhcp4ClientConfig *config, int level) {
-        config->log.level = level;
-}
-
-_c_public_ void n_dhcp4_client_config_set_log_func(NDhcp4ClientConfig *config, NDhcp4LogFunc func, void *data) {
-        config->log.func = func;
-        config->log.data = data;
+/**
+ * n_dhcp4_client_set_log_level() - set the logging level of the client
+ * @client:                         the client to operate on
+ * @level:                          the minimum syslog logging level that is
+ *                                  still logged. For example, set to LOG_NOTICE
+ *                                  to receive logging events with level LOG_NOTICE
+ *                                  and higher. Set to -1 to disable generating
+ *                                  logging events (which is also the default).
+ *
+ * By enabling logging, you can get N_DHCP4_CLIENT_EVENT_LOG events.
+ *
+ * From the logging event you may steal the message if (and only if) "allow_steal_message"
+ * is true. In that case, clear the message field and free the message yourself.
+ *
+ * If a logging event cannot be logged due to out of memory, one message
+ * gets logged that messages are missing. Until the event with that message
+ * gets dropped, no further logging events will be queued.
+ *
+ * You may change the logging level at any time, but it does not affect
+ * logging events that are already queued.
+  */
+_c_public_ void n_dhcp4_client_set_log_level(NDhcp4Client *client, int level) {
+        client->log_queue.log_level = level;
 }
 
 /**
@@ -313,6 +337,16 @@ NDhcp4CEventNode *n_dhcp4_c_event_node_free(NDhcp4CEventNode *node) {
         case N_DHCP4_CLIENT_EVENT_EXTENDED:
                 node->event.extended.lease = n_dhcp4_client_lease_unref(node->event.extended.lease);
                 break;
+        case N_DHCP4_CLIENT_EVENT_LOG:
+                if (_c_unlikely_(!node->event.log.allow_steal_message)) {
+                        /* @node is the static node "nomem_node". It must not be
+                         * freed. */
+                        c_list_unlink(&node->client_link);
+                        node->is_public = false;
+                        return NULL;
+                }
+                node->event.log.message = c_free((char *)node->event.log.message);
+                break;
         default:
                 break;
         }
@@ -395,8 +429,11 @@ _c_public_ int n_dhcp4_client_new(NDhcp4Client **clientp, NDhcp4ClientConfig *co
 
         ev.data.u32 = N_DHCP4_CLIENT_EPOLL_TIMER;
         r = epoll_ctl(client->fd_epoll, EPOLL_CTL_ADD, client->fd_timer, &ev);
-        if (r < 0)
+        if (r < 0) {
+                close(client->fd_timer);
+                client->fd_timer = -1;
                 return -errno;
+        }
 
         *clientp = client;
         client = NULL;
@@ -487,6 +524,78 @@ int n_dhcp4_client_raise(NDhcp4Client *client, NDhcp4CEventNode **nodep, unsigne
 }
 
 /**
+ * n_dhcp4_log_queue_fmt() - add a logging event.
+ * @client:                  the NDhcp4LogQueue to operate on
+ * @level:                   the syslog logging level
+ * @fmt:                     the format string for the message
+ * @...                      printf arguments for logging
+ *
+ * Appends a logging event to the event queue if logging is
+ * enabled and the logging level sufficiently high.
+ *
+ * Queuing a logging event might fail with out of memory.
+ * In that case, a static event will be queued that informs
+ * about lost messages.
+ */
+void n_dhcp4_log_queue_fmt(NDhcp4LogQueue *log_queue,
+                           int level,
+                           const char *fmt,
+                           ...) {
+        NDhcp4CEventNode *node;
+        char *message;
+        va_list ap;
+        int r;
+
+        if (level > log_queue->log_level)
+                return;
+
+        /* Currently the logging queue is only implemented for
+         * the client. Nobody would enable logging except a
+         * client instance. */
+        c_assert(log_queue->is_client);
+
+        if (!c_list_is_empty (&log_queue->nomem_node.client_link)) {
+                /* we have the nomem_node queued after a recent out
+                 * of memory. This disables all logging messages until
+                 * the event gets popped.
+                 *
+                 * The reason is that we can only queue the nomem_node once,
+                 * so if we now try to append another event and succeed, the
+                 * user wouldn't know which messages got dropped. Instead,
+                 * just drop them all!! */
+                return;
+        }
+
+        r = n_dhcp4_c_event_node_new(&node);
+        if (r < 0)
+                goto handle_nomem;
+
+        va_start(ap, fmt);
+        r = vasprintf(&message, fmt, ap);
+        va_end(ap);
+
+        if (r < 0) {
+                n_dhcp4_c_event_node_free(node);
+                goto handle_nomem;
+        }
+
+        node->event = (NDhcp4ClientEvent) {
+                .event = N_DHCP4_CLIENT_EVENT_LOG,
+                .log = {
+                        .level = level,
+                        .message = message,
+                        .allow_steal_message = true,
+                },
+        };
+
+        c_list_link_tail(log_queue->event_list, &node->client_link);
+        return;
+
+handle_nomem:
+        c_list_link_tail(log_queue->event_list, &log_queue->nomem_node.client_link);
+}
+
+/**
  * n_dhcp4_client_arm_timer() - update timer
  * @client:                     client to operate on
  *
@@ -682,9 +791,10 @@ _c_public_ int n_dhcp4_client_dispatch(NDhcp4Client *client) {
                                 /* continue normally */
                         } else if (r) {
                                 if (r >= _N_DHCP4_E_INTERNAL) {
-                                        n_dhcp4_c_log(client->config, LOG_ERR,
-                                                      "invalid internal error code %d after dispatch",
-                                                      r);
+                                        n_dhcp4_log(&client->log_queue,
+                                                    LOG_ERR,
+                                                    "invalid internal error code %d after dispatch",
+                                                    r);
                                         return N_DHCP4_E_INTERNAL;
                                 }
                                 return r;
@@ -753,6 +863,8 @@ _c_public_ int n_dhcp4_client_dispatch(NDhcp4Client *client) {
  *                                   the client attempted several incompatible
  *                                   probes in parallel, then the most recent
  *                                   ones will be cancelled asynchronously.
+ * * N_DHCP4_CLIENT_EVENT_LOG:       A logging event if n_dhcp4_client_set_log_level()
+ *                                   is enabled.
  *
  * Return: 0 on success, negative error code on failure.
  */
diff --git a/shared/n-dhcp4/src/n-dhcp4-private.h b/shared/n-dhcp4/src/n-dhcp4-private.h
index e285d95d..90f8f0c3 100644
--- a/shared/n-dhcp4/src/n-dhcp4-private.h
+++ b/shared/n-dhcp4/src/n-dhcp4-private.h
@@ -24,6 +24,7 @@ typedef struct NDhcp4Outgoing NDhcp4Outgoing;
 typedef struct NDhcp4SConnection NDhcp4SConnection;
 typedef struct NDhcp4SConnectionIp NDhcp4SConnectionIp;
 typedef struct NDhcp4SEventNode NDhcp4SEventNode;
+typedef struct NDhcp4LogQueue NDhcp4LogQueue;
 
 /* specs */
 
@@ -242,11 +243,6 @@ struct NDhcp4ClientConfig {
         size_t n_broadcast_mac;
         uint8_t *client_id;
         size_t n_client_id;
-        struct {
-                int level;
-                NDhcp4LogFunc func;
-                void *data;
-        } log;
 };
 
 #define N_DHCP4_CLIENT_CONFIG_NULL(_x) {                                        \
@@ -290,9 +286,42 @@ struct NDhcp4CEventNode {
                 .probe_link = C_LIST_INIT((_x).probe_link),                     \
         }
 
+struct NDhcp4LogQueue {
+        CList *event_list;
+        NDhcp4CEventNode nomem_node;
+        int log_level;
+        bool is_client : 1;
+};
+
+#define N_DHCP4_LOG_QUEUE_NULL_DEFUNCT() {                                      \
+                .log_level = -1,                                                \
+                .is_client = false,                                             \
+        }
+
+#define N_DHCP4_LOG_QUEUE_NULL_CLIENT(client) {                                 \
+                .event_list = &((client).event_list),                           \
+                .log_level = -1,                                                \
+                .is_client = true,                                              \
+                .nomem_node = {                                                 \
+                        .client_link = C_LIST_INIT((client).log_queue.nomem_node.client_link), \
+                        .probe_link = C_LIST_INIT((client).log_queue.nomem_node.probe_link), \
+                        .event = {                                              \
+                                .event = N_DHCP4_CLIENT_EVENT_LOG,              \
+                                .log = {                                        \
+                                        .level = LOG_CRIT,                      \
+                                        .message = "one or more logging messages dropped due to out of memory", \
+                                        .allow_steal_message = false,           \
+                                },                                              \
+                        },                                                      \
+                        .is_public = false,                                     \
+                },                                                              \
+        }
+
 struct NDhcp4CConnection {
         NDhcp4ClientConfig *client_config;
         NDhcp4ClientProbeConfig *probe_config;
+        NDhcp4LogQueue *log_queue;
+
         int fd_epoll;
 
         unsigned int state;             /* current connection state */
@@ -324,6 +353,9 @@ struct NDhcp4Client {
         unsigned long n_refs;
         NDhcp4ClientConfig *config;
         CList event_list;
+
+        NDhcp4LogQueue log_queue;
+
         int fd_epoll;
         int fd_timer;
 
@@ -339,6 +371,7 @@ struct NDhcp4Client {
                 .event_list = C_LIST_INIT((_x).event_list),                     \
                 .fd_epoll = -1,                                                 \
                 .fd_timer = -1,                                                 \
+                .log_queue = N_DHCP4_LOG_QUEUE_NULL_CLIENT(_x),                 \
         }
 
 struct NDhcp4ClientProbe {
@@ -570,6 +603,7 @@ NDhcp4CEventNode *n_dhcp4_c_event_node_free(NDhcp4CEventNode *node);
 int n_dhcp4_c_connection_init(NDhcp4CConnection *connection,
                               NDhcp4ClientConfig *client_config,
                               NDhcp4ClientProbeConfig *probe_config,
+                              NDhcp4LogQueue *log_queue,
                               int fd_epoll);
 void n_dhcp4_c_connection_deinit(NDhcp4CConnection *connection);
 
@@ -698,19 +732,28 @@ static inline uint64_t n_dhcp4_gettime(clockid_t clock) {
         return ts.tv_sec * 1000ULL * 1000ULL * 1000ULL + ts.tv_nsec;
 }
 
-#define n_dhcp4_c_log(_config, _level, ...)                                    \
+void n_dhcp4_log_queue_fmt(NDhcp4LogQueue *log_queue,
+                           int level,
+                           const char *fmt,
+                           ...) _c_printf_(3, 4);
+
+/**
+ * n_dhcp4_log() - append a logging event
+ * @x_log_queue:   the logging event queue
+ * @x_level:       the syslog logging level for the message.
+ * @...:           the format string and arguments.
+ *
+ * Warning: this macro only evaluates the format arguments if the logging
+ * level is enabled.
+ */
+#define n_dhcp4_log(x_log_queue, x_level, ...)                                 \
         do {                                                                   \
-                const NDhcp4ClientConfig *__config = _config;                  \
-                int __level = _level;                                          \
+                NDhcp4LogQueue *const _log_queue = (x_log_queue);              \
+                const int _level = (x_level);                                  \
                                                                                \
-                if (__level <= __config->log.level && __config->log.func) {    \
-                        if (1) {                                               \
-                                _config->log.func(__level,                     \
-                                                  __config->log.data,          \
-                                                  __VA_ARGS__);                \
-                        } else {                                               \
-                                /* To have the compiler check arguments */     \
-                                printf(__VA_ARGS__);                           \
-                        }                                                      \
+                if (_level <= _log_queue->log_level) {                         \
+                        n_dhcp4_log_queue_fmt(_log_queue,                      \
+                                              _level,                          \
+                                              __VA_ARGS__);                    \
                 }                                                              \
         } while (0)
diff --git a/shared/n-dhcp4/src/n-dhcp4.h b/shared/n-dhcp4/src/n-dhcp4.h
index d3747662..81452848 100644
--- a/shared/n-dhcp4/src/n-dhcp4.h
+++ b/shared/n-dhcp4/src/n-dhcp4.h
@@ -29,8 +29,6 @@ typedef struct NDhcp4ServerEvent NDhcp4ServerEvent;
 typedef struct NDhcp4ServerIp NDhcp4ServerIp;
 typedef struct NDhcp4ServerLease NDhcp4ServerLease;
 
-typedef void (*NDhcp4LogFunc)(int level, void *data, const char *fmt, ...);
-
 #define N_DHCP4_CLIENT_START_DELAY_RFC2131 (UINT64_C(9000))
 
 enum {
@@ -63,6 +61,7 @@ enum {
         N_DHCP4_CLIENT_EVENT_EXTENDED,
         N_DHCP4_CLIENT_EVENT_EXPIRED,
         N_DHCP4_CLIENT_EVENT_CANCELLED,
+        N_DHCP4_CLIENT_EVENT_LOG,
         _N_DHCP4_CLIENT_EVENT_N,
 };
 
@@ -88,6 +87,14 @@ struct NDhcp4ClientEvent {
                 struct {
                         NDhcp4ClientProbe *probe;
                 } retracted, expired, cancelled;
+                struct {
+                        /* If allow_steal_message is true, then the user may steal the message when handling
+                         * the event. In that case, set the message field to %NULL and free it yourself
+                         * with free(). */
+                        const char *message;
+                        int level;
+                        bool allow_steal_message;
+                } log;
         };
 };
 
@@ -113,8 +120,6 @@ void n_dhcp4_client_config_set_request_broadcast(NDhcp4ClientConfig *config, boo
 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);
-void n_dhcp4_client_config_set_log_level(NDhcp4ClientConfig *config, int level);
-void n_dhcp4_client_config_set_log_func(NDhcp4ClientConfig *config, NDhcp4LogFunc func, void *data);
 
 /* client-probe configs */
 
@@ -141,6 +146,8 @@ 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);
 
+void n_dhcp4_client_set_log_level(NDhcp4Client *client, int level);
+
 int n_dhcp4_client_update_mtu(NDhcp4Client *client, uint16_t mtu);
 
 int n_dhcp4_client_probe(NDhcp4Client *client,