/src/mosquitto/src/loop.c
Line | Count | Source |
1 | | /* |
2 | | Copyright (c) 2009-2021 Roger Light <roger@atchoo.org> |
3 | | |
4 | | All rights reserved. This program and the accompanying materials |
5 | | are made available under the terms of the Eclipse Public License 2.0 |
6 | | and Eclipse Distribution License v1.0 which accompany this distribution. |
7 | | |
8 | | The Eclipse Public License is available at |
9 | | https://www.eclipse.org/legal/epl-2.0/ |
10 | | and the Eclipse Distribution License is available at |
11 | | http://www.eclipse.org/org/documents/edl-v10.php. |
12 | | |
13 | | SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
14 | | |
15 | | Contributors: |
16 | | Roger Light - initial implementation and documentation. |
17 | | Tatsuzo Osawa - Add epoll. |
18 | | */ |
19 | | |
20 | | #include "config.h" |
21 | | |
22 | | #ifndef WIN32 |
23 | | # define _GNU_SOURCE |
24 | | #endif |
25 | | |
26 | | #ifndef WIN32 |
27 | | #include <unistd.h> |
28 | | #else |
29 | | #include <process.h> |
30 | | #include <winsock2.h> |
31 | | #include <ws2tcpip.h> |
32 | | #endif |
33 | | |
34 | | #include <errno.h> |
35 | | #include <signal.h> |
36 | | #include <stdio.h> |
37 | | #include <string.h> |
38 | | #ifndef WIN32 |
39 | | # include <sys/socket.h> |
40 | | #endif |
41 | | #include <time.h> |
42 | | #include <utlist.h> |
43 | | |
44 | | #if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS |
45 | | # include <libwebsockets.h> |
46 | | #endif |
47 | | |
48 | | #include "mosquitto_broker_internal.h" |
49 | | #include "mosquitto/mqtt_protocol.h" |
50 | | #include "packet_mosq.h" |
51 | | #include "property_common.h" |
52 | | #include "send_mosq.h" |
53 | | #include "sys_tree.h" |
54 | | #include "util_mosq.h" |
55 | | |
56 | | extern int g_run; |
57 | | |
58 | | #if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS && LWS_LIBRARY_VERSION_NUMBER == 3002000 |
59 | | |
60 | | |
61 | | void lws__sul_callback(struct lws_sorted_usec_list *l) |
62 | | { |
63 | | } |
64 | | |
65 | | static struct lws_sorted_usec_list sul; |
66 | | #endif |
67 | | |
68 | | |
69 | | static int single_publish(struct mosquitto *context, struct mosquitto__message_v5 *pub_msg, uint32_t message_expiry) |
70 | 0 | { |
71 | 0 | struct mosquitto__base_msg *base_msg; |
72 | 0 | uint16_t mid; |
73 | |
|
74 | 0 | base_msg = mosquitto_calloc(1, sizeof(struct mosquitto__base_msg)); |
75 | 0 | if(base_msg == NULL){ |
76 | 0 | return MOSQ_ERR_NOMEM; |
77 | 0 | } |
78 | | |
79 | 0 | base_msg->data.topic = pub_msg->topic; |
80 | 0 | pub_msg->topic = NULL; |
81 | 0 | base_msg->data.retain = 0; |
82 | 0 | base_msg->data.payloadlen = (uint32_t)pub_msg->payloadlen; |
83 | 0 | base_msg->data.payload = mosquitto_malloc(base_msg->data.payloadlen+1); |
84 | 0 | if(base_msg->data.payload == NULL){ |
85 | 0 | db__msg_store_free(base_msg); |
86 | 0 | return MOSQ_ERR_NOMEM; |
87 | 0 | } |
88 | | /* Ensure payload is always zero terminated, this is the reason for the extra byte above */ |
89 | 0 | ((uint8_t *)base_msg->data.payload)[base_msg->data.payloadlen] = 0; |
90 | 0 | memcpy(base_msg->data.payload, pub_msg->payload, base_msg->data.payloadlen); |
91 | |
|
92 | 0 | if(pub_msg->properties){ |
93 | 0 | base_msg->data.properties = pub_msg->properties; |
94 | 0 | pub_msg->properties = NULL; |
95 | 0 | } |
96 | |
|
97 | 0 | if(db__message_store(context, base_msg, &message_expiry, mosq_mo_broker)){ |
98 | 0 | return 1; |
99 | 0 | } |
100 | | |
101 | 0 | if(pub_msg->qos){ |
102 | 0 | mid = mosquitto__mid_generate(context); |
103 | 0 | }else{ |
104 | 0 | mid = 0; |
105 | 0 | } |
106 | 0 | return db__message_insert_outgoing(context, 0, mid, (uint8_t)pub_msg->qos, 0, base_msg, 0, true, true); |
107 | 0 | } |
108 | | |
109 | | |
110 | | static void read_message_expiry_interval(mosquitto_property **proplist, uint32_t *message_expiry) |
111 | 0 | { |
112 | 0 | mosquitto_property *p, *previous = NULL; |
113 | |
|
114 | 0 | *message_expiry = MSG_EXPIRY_INFINITE; |
115 | |
|
116 | 0 | if(!proplist){ |
117 | 0 | return; |
118 | 0 | } |
119 | | |
120 | 0 | p = *proplist; |
121 | 0 | while(p){ |
122 | 0 | if(mosquitto_property_identifier(p) == MQTT_PROP_MESSAGE_EXPIRY_INTERVAL){ |
123 | 0 | *message_expiry = mosquitto_property_int32_value(p); |
124 | 0 | if(p == *proplist){ |
125 | 0 | *proplist = mosquitto_property_next(p); |
126 | 0 | }else{ |
127 | 0 | previous->next = mosquitto_property_next(p); |
128 | 0 | } |
129 | 0 | mosquitto_property_free(&p); |
130 | 0 | return; |
131 | |
|
132 | 0 | } |
133 | 0 | previous = p; |
134 | 0 | p = mosquitto_property_next(p); |
135 | 0 | } |
136 | 0 | } |
137 | | |
138 | | |
139 | | static void queue_plugin_msgs(void) |
140 | 0 | { |
141 | 0 | struct mosquitto__message_v5 *msg, *tmp; |
142 | 0 | struct mosquitto *context; |
143 | 0 | uint32_t message_expiry; |
144 | |
|
145 | 0 | DL_FOREACH_SAFE(db.plugin_msgs, msg, tmp){ |
146 | 0 | DL_DELETE(db.plugin_msgs, msg); |
147 | |
|
148 | 0 | read_message_expiry_interval(&msg->properties, &message_expiry); |
149 | |
|
150 | 0 | if(msg->clientid){ |
151 | 0 | HASH_FIND(hh_id, db.contexts_by_id, msg->clientid, strlen(msg->clientid), context); |
152 | 0 | if(context){ |
153 | 0 | single_publish(context, msg, message_expiry); |
154 | 0 | } |
155 | 0 | }else{ |
156 | 0 | db__messages_easy_queue(NULL, msg->topic, (uint8_t)msg->qos, (uint32_t)msg->payloadlen, msg->payload, msg->retain, message_expiry, &msg->properties); |
157 | 0 | } |
158 | 0 | mosquitto_FREE(msg->topic); |
159 | 0 | mosquitto_FREE(msg->payload); |
160 | 0 | mosquitto_property_free_all(&msg->properties); |
161 | 0 | mosquitto_FREE(msg->clientid); |
162 | 0 | mosquitto_FREE(msg); |
163 | 0 | } |
164 | 0 | } |
165 | | |
166 | | |
167 | | void loop__update_next_event(time_t new_ms) |
168 | 0 | { |
169 | 0 | if(new_ms > 0 && new_ms < db.next_event_ms){ |
170 | 0 | db.next_event_ms = new_ms; |
171 | 0 | } |
172 | 0 | } |
173 | | |
174 | | |
175 | | int mosquitto_main_loop(struct mosquitto__listener_sock *listensock, int listensock_count) |
176 | 0 | { |
177 | 0 | #ifdef WITH_PERSISTENCE |
178 | 0 | time_t last_backup = mosquitto_time(); |
179 | 0 | #endif |
180 | 0 | int rc; |
181 | | |
182 | |
|
183 | 0 | watchdog__init(); |
184 | | #if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS && LWS_LIBRARY_VERSION_NUMBER == 3002000 |
185 | | memset(&sul, 0, sizeof(struct lws_sorted_usec_list)); |
186 | | #endif |
187 | |
|
188 | 0 | db.now_s = mosquitto_time(); |
189 | 0 | db.now_real_s = time(NULL); |
190 | |
|
191 | 0 | #ifdef WITH_BRIDGE |
192 | 0 | rc = bridge__register_local_connections(); |
193 | 0 | if(rc){ |
194 | 0 | return rc; |
195 | 0 | } |
196 | 0 | #endif |
197 | | |
198 | 0 | while(g_run){ |
199 | 0 | retain__expiry_check(); |
200 | 0 | queue_plugin_msgs(); |
201 | 0 | context__free_disused(); |
202 | |
|
203 | 0 | db.next_event_ms = 86400000; |
204 | 0 | #ifdef WITH_SYS_TREE |
205 | 0 | if(db.config->sys_interval > 0){ |
206 | 0 | sys_tree__update(false); |
207 | 0 | } |
208 | 0 | #endif |
209 | |
|
210 | 0 | keepalive__check(); |
211 | 0 | watchdog__check(); |
212 | |
|
213 | 0 | #ifdef WITH_BRIDGE |
214 | 0 | bridge_check(); |
215 | 0 | #endif |
216 | 0 | plugin__handle_tick(); |
217 | 0 | session_expiry__check(); |
218 | 0 | will_delay__check(); |
219 | |
|
220 | 0 | rc = mux__handle(listensock, listensock_count); |
221 | 0 | if(rc){ |
222 | 0 | return rc; |
223 | 0 | } |
224 | | |
225 | 0 | #ifdef WITH_PERSISTENCE |
226 | 0 | if(db.config->persistence && db.config->autosave_interval){ |
227 | 0 | if(db.config->autosave_on_changes){ |
228 | 0 | if(db.persistence_changes >= db.config->autosave_interval){ |
229 | 0 | persist__backup(false); |
230 | 0 | db.persistence_changes = 0; |
231 | 0 | } |
232 | 0 | }else{ |
233 | 0 | if(last_backup + db.config->autosave_interval < db.now_s){ |
234 | 0 | persist__backup(false); |
235 | 0 | last_backup = db.now_s; |
236 | 0 | } |
237 | 0 | } |
238 | 0 | } |
239 | 0 | #endif |
240 | |
|
241 | 0 | rc = signal__flag_check(); |
242 | 0 | if(rc){ |
243 | 0 | return rc; |
244 | 0 | } |
245 | |
|
246 | | #if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS |
247 | | for(int i=0; i<db.config->listener_count; i++){ |
248 | | /* Extremely hacky, should be using the lws provided external poll |
249 | | * interface, but their interface has changed recently and ours |
250 | | * will soon, so for now websockets clients are second class |
251 | | * citizens. */ |
252 | | if(db.config->listeners[i].ws_context){ |
253 | | #if LWS_LIBRARY_VERSION_NUMBER > 3002000 |
254 | | lws_service(db.config->listeners[i].ws_context, -1); |
255 | | #elif LWS_LIBRARY_VERSION_NUMBER == 3002000 |
256 | | lws_sul_schedule(db.config->listeners[i].ws_context, 0, &sul, lws__sul_callback, 10); |
257 | | lws_service(db.config->listeners[i].ws_context, 0); |
258 | | #else |
259 | | lws_service(db.config->listeners[i].ws_context, 0); |
260 | | #endif |
261 | | |
262 | | } |
263 | | } |
264 | | #endif |
265 | 0 | } |
266 | | |
267 | 0 | return MOSQ_ERR_SUCCESS; |
268 | 0 | } |
269 | | |
270 | | |
271 | | void do_disconnect(struct mosquitto *context, int reason) |
272 | 44 | { |
273 | 44 | const char *id; |
274 | | #if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS |
275 | | bool is_duplicate = false; |
276 | | #endif |
277 | | |
278 | 44 | if(context->state == mosq_cs_disconnected){ |
279 | 0 | return; |
280 | 0 | } |
281 | | #if defined(WITH_WEBSOCKETS) && WITH_WEBSOCKETS == WS_IS_LWS |
282 | | if(context->wsi){ |
283 | | if(context->state == mosq_cs_duplicate){ |
284 | | is_duplicate = true; |
285 | | } |
286 | | |
287 | | if(context->state != mosq_cs_disconnecting && context->state != mosq_cs_disconnect_with_will){ |
288 | | mosquitto__set_state(context, mosq_cs_disconnect_ws); |
289 | | } |
290 | | if(context->wsi){ |
291 | | lws_callback_on_writable(context->wsi); |
292 | | } |
293 | | if(context->sock != INVALID_SOCKET){ |
294 | | HASH_DELETE(hh_sock, db.contexts_by_sock, context); |
295 | | mux__delete(context); |
296 | | context->sock = INVALID_SOCKET; |
297 | | } |
298 | | if(is_duplicate){ |
299 | | /* This occurs if another client is taking over the same client id. |
300 | | * It is important to remove this from the by_id hash here, so it |
301 | | * doesn't leave us with multiple clients in the hash with the same |
302 | | * id. Websockets doesn't actually close the connection here, |
303 | | * unlike for normal clients, which means there is extra time when |
304 | | * there could be two clients with the same id in the hash. */ |
305 | | context__remove_from_by_id(context); |
306 | | } |
307 | | }else |
308 | | #endif |
309 | 44 | { |
310 | 44 | if(db.config->connection_messages == true){ |
311 | 0 | if(context->id){ |
312 | 0 | id = context->id; |
313 | 0 | }else{ |
314 | 0 | id = context->address; |
315 | 0 | } |
316 | 0 | if(context->state != mosq_cs_disconnecting && context->state != mosq_cs_disconnect_with_will){ |
317 | 0 | switch(reason){ |
318 | 0 | case MOSQ_ERR_SUCCESS: |
319 | 0 | break; |
320 | 0 | case MOSQ_ERR_MALFORMED_PACKET: |
321 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to malformed packet.", id); |
322 | 0 | break; |
323 | 0 | case MOSQ_ERR_PROTOCOL: |
324 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to protocol error.", id); |
325 | 0 | break; |
326 | 0 | case MOSQ_ERR_CONN_LOST: |
327 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s closed its connection.", id); |
328 | 0 | break; |
329 | 0 | case MOSQ_ERR_AUTH: |
330 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, not authorised.", id); |
331 | 0 | break; |
332 | 0 | case MOSQ_ERR_KEEPALIVE: |
333 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s has exceeded timeout, disconnecting.", id); |
334 | 0 | break; |
335 | 0 | case MOSQ_ERR_OVERSIZE_PACKET: |
336 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to oversize packet.", id); |
337 | 0 | break; |
338 | 0 | case MOSQ_ERR_PAYLOAD_SIZE: |
339 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to oversize payload.", id); |
340 | 0 | break; |
341 | 0 | case MOSQ_ERR_NOMEM: |
342 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to out of memory.", id); |
343 | 0 | break; |
344 | 0 | case MOSQ_ERR_NOT_SUPPORTED: |
345 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to using not allowed feature (QoS too high, retain not supported, or bad AUTH method).", id); |
346 | 0 | break; |
347 | 0 | case MOSQ_ERR_ADMINISTRATIVE_ACTION: |
348 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s been disconnected by administrative action.", id); |
349 | 0 | break; |
350 | 0 | case MOSQ_ERR_ERRNO: |
351 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected: %s.", id, strerror(errno)); |
352 | 0 | break; |
353 | 0 | case MOSQ_ERR_RECEIVE_MAXIMUM_EXCEEDED: |
354 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected due to exceeding the receive maximum.", id); |
355 | 0 | break; |
356 | 0 | case MOSQ_ERR_IMPLEMENTATION_SPECIFIC: |
357 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, implementation specific error.", id); |
358 | 0 | break; |
359 | 0 | case MOSQ_ERR_CLIENT_IDENTIFIER_NOT_VALID: |
360 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, client identifier not valid.", id); |
361 | 0 | break; |
362 | 0 | case MOSQ_ERR_BAD_USERNAME_OR_PASSWORD: |
363 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, bad username or password.", id); |
364 | 0 | break; |
365 | 0 | case MOSQ_ERR_SERVER_UNAVAILABLE: |
366 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, server unavailable.", id); |
367 | 0 | break; |
368 | 0 | case MOSQ_ERR_SERVER_BUSY: |
369 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, server busy.", id); |
370 | 0 | break; |
371 | 0 | case MOSQ_ERR_BANNED: |
372 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, client banned.", id); |
373 | 0 | break; |
374 | 0 | case MOSQ_ERR_BAD_AUTHENTICATION_METHOD: |
375 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, bad authentication method.", id); |
376 | 0 | break; |
377 | 0 | case MOSQ_ERR_QUOTA_EXCEEDED: |
378 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, quota exceeded.", id); |
379 | 0 | break; |
380 | 0 | case MOSQ_ERR_CONNECTION_RATE_EXCEEDED: |
381 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, connection rate exceeded.", id); |
382 | 0 | break; |
383 | 0 | case MOSQ_ERR_SESSION_TAKEN_OVER: |
384 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, session taken over.", id); |
385 | 0 | break; |
386 | 0 | case MOSQ_ERR_TOPIC_ALIAS_INVALID: |
387 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, topic alias invalid.", id); |
388 | 0 | break; |
389 | 0 | case MOSQ_ERR_HTTP_BAD_ORIGIN: |
390 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected, non-matching http origin.", id); |
391 | 0 | break; |
392 | 0 | case MOSQ_ERR_PROXY: |
393 | | /* This was a proxy v2 health check connection, so don't report */ |
394 | 0 | break; |
395 | 0 | default: |
396 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Bad socket read/write on client %s: %s", id, mosquitto_strerror(reason)); |
397 | 0 | break; |
398 | 0 | } |
399 | 0 | }else{ |
400 | 0 | if(reason == MOSQ_ERR_ADMINISTRATIVE_ACTION){ |
401 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s been disconnected by administrative action.", id); |
402 | 0 | }else{ |
403 | 0 | log__printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected.", id); |
404 | 0 | } |
405 | 0 | } |
406 | 0 | } |
407 | 44 | mux__delete(context); |
408 | 44 | context__disconnect(context, reason); |
409 | 44 | } |
410 | 44 | } |