1 /*
2 * Copyright (C) 2021-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "coap_client.h"
17 #include <stdlib.h>
18 #include <string.h>
19 #include <securec.h>
20 #ifndef _WIN32
21 #include <signal.h>
22 #include <unistd.h>
23 #include <netdb.h>
24 #include <sys/stat.h>
25 #include <sys/types.h>
26 #include <arpa/inet.h>
27 #include <sys/select.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #endif
31 #include <coap3/coap_session_internal.h>
32 #include "nstackx_device.h"
33 #include "nstackx_error.h"
34 #include "nstackx_dfinder_log.h"
35 #include "nstackx_util.h"
36 #include "nstackx_statistics.h"
37
38 #define TAG "nStackXCoAP"
39
40 #define FLAGS_BLOCK 0x01
41 #define DEFAULT_COAP_BUFFER_LENGTH 256
42 #define COAP_CERT_CHAIN_DEPTH 2
43 #define COAP_MULTICAST_ADDR "ff02::1"
44
45 /*
46 * the initial timeout will be set to a random duration between COAP_ACK_TIMEOUT and
47 * (COAP_ACK_TIMEOUT * COAP_ACK_RANDOM_FACTOR).
48 */
49 #define DFINDER_COAP_ACK_TIMEOUT ((coap_fixed_point_t){1, 0}) // 1 seconds
50 #define DFINDER_COAP_ACK_RANDOM_FACTOR ((coap_fixed_point_t){1, 200}) // 1.2
51 #define DFINDER_COAP_MAX_RETRANSMIT_TIMES 2 // retransmit 2 times for CON packets
52
CoapResolveAddress(const coap_str_const_t * server,struct sockaddr * dst)53 int32_t CoapResolveAddress(const coap_str_const_t *server, struct sockaddr *dst)
54 {
55 struct addrinfo *res = NULL;
56 struct addrinfo *ainfo = NULL;
57 struct addrinfo hints;
58 char addrstr[DEFAULT_COAP_BUFFER_LENGTH]; /* Get a char array with length 256 to save host name. */
59
60 if (server == NULL || server->s == NULL || dst == NULL ||
61 (dst->sa_family != AF_INET && dst->sa_family != AF_INET6)) {
62 return NSTACKX_EINVAL;
63 }
64 (void)memset_s(addrstr, sizeof(addrstr), 0, sizeof(addrstr));
65 if (server->length) {
66 if (memcpy_s(addrstr, sizeof(addrstr), server->s, server->length) != EOK) {
67 DFINDER_LOGD(TAG, "addrstr copy error");
68 return NSTACKX_EFAILED;
69 }
70 } else {
71 if (memcpy_s(addrstr, sizeof(addrstr), "localhost", strlen("localhost")) != EOK) {
72 DFINDER_LOGD(TAG, "addrstr copy error");
73 return NSTACKX_EFAILED;
74 }
75 }
76
77 (void)memset_s((char *)&hints, sizeof(hints), 0, sizeof(hints));
78 hints.ai_socktype = SOCK_DGRAM;
79 hints.ai_family = AF_UNSPEC;
80
81 int32_t error = getaddrinfo(addrstr, NULL, &hints, &res);
82 if (error != 0) {
83 DFINDER_LOGE(TAG, "getaddrinfo error");
84 return error;
85 }
86
87 socklen_t len = 0;
88 for (ainfo = res; ainfo != NULL; ainfo = ainfo->ai_next) {
89 if (ainfo->ai_family != dst->sa_family) {
90 continue;
91 }
92 len = ainfo->ai_addrlen;
93 size_t dstLen = dst->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
94 if (memcpy_s(dst, dstLen, ainfo->ai_addr, len) != EOK) {
95 DFINDER_LOGE(TAG, "ai_addr copy error");
96 error = NSTACKX_EFAILED;
97 }
98 break;
99 }
100 freeaddrinfo(res);
101 return (error == NSTACKX_EFAILED) ? error : (int32_t)len;
102 }
103
CoapMessageHandler(coap_session_t * session,const coap_pdu_t * sent,const coap_pdu_t * received,const coap_mid_t id)104 coap_response_t CoapMessageHandler(coap_session_t *session,
105 const coap_pdu_t *sent, const coap_pdu_t *received, const coap_mid_t id)
106 {
107 if (received == NULL) {
108 DFINDER_LOGE(TAG, "received error");
109 goto FAIL;
110 }
111 (void)session;
112 (void)sent;
113 (void)id;
114 coap_opt_t *blockOpt1 = NULL;
115 coap_opt_t *blockOpt2 = NULL;
116 coap_opt_iterator_t optIter;
117
118 (void)memset_s(&optIter, sizeof(optIter), 0, sizeof(optIter));
119 if (coap_pdu_get_type(received) == COAP_MESSAGE_RST) {
120 DFINDER_LOGD(TAG, "got RST");
121 goto FAIL;
122 }
123
124 if (coap_check_option(received, COAP_OPTION_OBSERVE, &optIter)) {
125 DFINDER_LOGE(TAG, "observe not support.");
126 goto FAIL;
127 }
128 blockOpt2 = coap_check_option(received, COAP_OPTION_BLOCK2, &optIter);
129 blockOpt1 = coap_check_option(received, COAP_OPTION_BLOCK1, &optIter);
130 if ((blockOpt1 != NULL) || (blockOpt2 != NULL)) {
131 DFINDER_LOGE(TAG, "block not support.");
132 goto FAIL;
133 }
134
135 coap_pdu_code_t rcv_code = coap_pdu_get_code(received);
136 DFINDER_LOGD(TAG, "%d.%02d", COAP_RESPONSE_CLASS(rcv_code), rcv_code & 0x1F);
137 return COAP_RESPONSE_OK;
138
139 FAIL:
140 IncStatistics(STATS_INVALID_RESPONSE_MSG);
141 return COAP_RESPONSE_FAIL;
142 }
143
InitAddrinfo(struct addrinfo * hints)144 static void InitAddrinfo(struct addrinfo *hints)
145 {
146 if (hints == NULL) {
147 return;
148 }
149 (void)memset_s(hints, sizeof(struct addrinfo), 0, sizeof(struct addrinfo));
150 hints->ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
151 hints->ai_socktype = SOCK_DGRAM; /* Coap uses UDP */
152 hints->ai_flags = AI_PASSIVE | AI_NUMERICHOST;
153 }
154
CoapBindToDevice(int sockfd,uint8_t af,const union InetAddr * addr)155 static int CoapBindToDevice(int sockfd, uint8_t af, const union InetAddr *addr)
156 {
157 struct sockaddr_in sockIp;
158 if (af == AF_INET) {
159 (void)memset_s(&sockIp, sizeof(struct sockaddr_in), 0, sizeof(struct sockaddr_in));
160 (void)memcpy_s(&sockIp.sin_addr, sizeof(struct in_addr), &(addr->in), sizeof(struct in_addr));
161 return BindToDevice(sockfd, &sockIp);
162 }
163 return NSTACKX_EOK;
164 }
165
CoapAddIpv6Multicast(int sockfd)166 static int CoapAddIpv6Multicast(int sockfd)
167 {
168 struct ipv6_mreq mreq;
169 if (inet_pton(AF_INET6, COAP_MULTICAST_ADDR, &mreq.ipv6mr_multiaddr) <= 0) {
170 DFINDER_LOGE(TAG, "inet_pton multicast addr fail");
171 return NSTACKX_EFAILED;
172 }
173 mreq.ipv6mr_interface = 0; // use the default interface
174 if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) {
175 DFINDER_LOGE(TAG, "setsockopt add multicast group fail");
176 return NSTACKX_EFAILED;
177 }
178
179 return NSTACKX_EOK;
180 }
181
CoapCreateEndpoint(coap_context_t * ctx,coap_address_t * addr,uint8_t af,const union InetAddr * ip)182 coap_endpoint_t *CoapCreateEndpoint(coap_context_t *ctx, coap_address_t *addr,
183 uint8_t af, const union InetAddr *ip)
184 {
185 DFINDER_LOGI(TAG, "Initializing CoapCreateEndpoint");
186 coap_endpoint_t *ep = coap_new_endpoint(ctx, addr, COAP_PROTO_UDP);
187 if (ep == NULL) {
188 DFINDER_LOGE(TAG, "coap_new_endpoint get null");
189 return NULL;
190 }
191 if (CoapBindToDevice(ep->sock.fd, af, ip) != NSTACKX_EOK) {
192 DFINDER_LOGE(TAG, "bind to device fail");
193 coap_free_endpoint(ep);
194 return NULL;
195 }
196 if (af == AF_INET6 && CoapAddIpv6Multicast(ep->sock.fd) != NSTACKX_EOK) {
197 DFINDER_LOGE(TAG, "add ipv6 multicast fail");
198 coap_free_endpoint(ep);
199 return NULL;
200 }
201 return ep;
202 }
203
CoapGetContextEx(const char * node,const char * port,uint8_t af,const union InetAddr * ip)204 static coap_context_t *CoapGetContextEx(const char *node, const char *port,
205 uint8_t af, const union InetAddr *ip)
206 {
207 struct addrinfo hints;
208 struct addrinfo *result = NULL;
209 struct addrinfo *rp = NULL;
210 coap_endpoint_t *ep = NULL;
211 coap_context_t *ctx = coap_new_context(NULL);
212 if (ctx == NULL) {
213 DFINDER_LOGE(TAG, "coap_new_context return null");
214 return NULL;
215 }
216 InitAddrinfo(&hints);
217
218 if (getaddrinfo(node, port, &hints, &result) != 0) {
219 DFINDER_LOGE(TAG, "getaddrinfo fail, errno: %d, desc: %s", errno, strerror(errno));
220 coap_free_context(ctx);
221 return NULL;
222 }
223 coap_address_t addr;
224 /* iterate through results until success */
225 for (rp = result; rp != NULL; rp = rp->ai_next) {
226 if (rp->ai_addrlen > (socklen_t)sizeof(addr.addr)) {
227 continue;
228 }
229
230 coap_address_init(&addr);
231 addr.size = rp->ai_addrlen;
232 if (rp->ai_family != af || memcpy_s(&addr.addr, sizeof(addr.addr), rp->ai_addr, rp->ai_addrlen) != EOK) {
233 continue;
234 }
235 ep = CoapCreateEndpoint(ctx, &addr, af, ip);
236 if (ep == NULL) {
237 DFINDER_LOGE(TAG, "coap_new_endpoint return null");
238 coap_free_context(ctx);
239 ctx = NULL;
240 }
241 break;
242 }
243 freeaddrinfo(result);
244 return ctx;
245 }
246
CoapGetContext(const char * node,const char * port,uint8_t af,const union InetAddr * ip)247 coap_context_t *CoapGetContext(const char *node, const char *port,
248 uint8_t af, const union InetAddr *ip)
249 {
250 coap_context_t *context = CoapGetContextEx(node, port, af, ip);
251 if (context == NULL) {
252 IncStatistics(STATS_CREATE_CONTEX_FAILED);
253 }
254 return context;
255 }
256
CoapGetSessionInner(struct addrinfo * result,coap_context_t * ctx,const CoapServerParameter * coapServerParameter)257 static coap_session_t *CoapGetSessionInner(struct addrinfo *result, coap_context_t *ctx,
258 const CoapServerParameter *coapServerParameter)
259 {
260 coap_session_t *session = NULL;
261 struct addrinfo *rp = NULL;
262 coap_proto_t proto = coapServerParameter->proto;
263 const coap_address_t *dst = coapServerParameter->dst;
264
265 if (proto != COAP_PROTO_UDP) {
266 DFINDER_LOGE(TAG, "unsupported proto");
267 return NULL;
268 }
269
270 for (rp = result; rp != NULL; rp = rp->ai_next) {
271 coap_address_t bindAddr;
272 if (rp->ai_addrlen > (socklen_t)sizeof(bindAddr.addr) || rp->ai_addr == NULL ||
273 dst->addr.sa.sa_family != rp->ai_addr->sa_family) {
274 continue;
275 }
276 (void)memset_s(&bindAddr, sizeof(bindAddr), 0, sizeof(bindAddr));
277 coap_address_init(&bindAddr);
278 bindAddr.size = rp->ai_addrlen;
279 if (memcpy_s(&bindAddr.addr, sizeof(bindAddr.addr), rp->ai_addr, rp->ai_addrlen) != EOK) {
280 DFINDER_LOGE(TAG, "ai_addr copy error");
281 continue;
282 }
283 char ip[NSTACKX_MAX_IP_STRING_LEN];
284 if (bindAddr.addr.sa.sa_family == AF_INET) {
285 (void)inet_ntop(AF_INET, &(bindAddr.addr.sin.sin_addr), ip, sizeof(ip));
286 } else {
287 (void)inet_ntop(AF_INET6, &(bindAddr.addr.sin6.sin6_addr), ip, sizeof(ip));
288 }
289 session = coap_new_client_session(ctx, &bindAddr, dst, proto);
290 if (session != NULL) {
291 break;
292 } else {
293 DFINDER_LOGE(TAG, "coap_new_client_session error");
294 }
295 }
296 return session;
297 }
298
CoapSetAckTimeOut(coap_session_t * session)299 static void CoapSetAckTimeOut(coap_session_t *session)
300 {
301 if (session == NULL) {
302 return;
303 }
304 coap_session_set_ack_timeout(session, DFINDER_COAP_ACK_TIMEOUT);
305 coap_session_set_ack_random_factor(session, DFINDER_COAP_ACK_RANDOM_FACTOR);
306 coap_session_set_max_retransmit(session, DFINDER_COAP_MAX_RETRANSMIT_TIMES);
307 }
308
CoapGetSessionEx(coap_context_t * ctx,const char * localAddr,const char * localPort,const CoapServerParameter * coapServerParameter)309 static coap_session_t *CoapGetSessionEx(coap_context_t *ctx, const char *localAddr, const char *localPort,
310 const CoapServerParameter *coapServerParameter)
311 {
312 coap_session_t *session = NULL;
313 coap_proto_t proto;
314 const coap_address_t *dst = NULL;
315
316 if (coapServerParameter == NULL) {
317 return NULL;
318 }
319
320 proto = coapServerParameter->proto;
321 dst = coapServerParameter->dst;
322 if (proto != COAP_PROTO_UDP) {
323 DFINDER_LOGE(TAG, "unsupported proto");
324 return NULL;
325 }
326
327 if (dst == NULL) {
328 return session;
329 }
330
331 /* reuse the existed session */
332 session = coap_session_get_by_peer(ctx, dst, 0);
333 if (session != NULL) {
334 CoapSetAckTimeOut(session);
335 (void)coap_session_reference(session);
336 return session;
337 }
338
339 if (localAddr != NULL) {
340 struct addrinfo hints;
341 struct addrinfo *result = NULL;
342 (void)memset_s(&hints, sizeof(struct addrinfo), 0, sizeof(struct addrinfo));
343 hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
344 hints.ai_socktype = COAP_PROTO_RELIABLE(proto) ? SOCK_STREAM : SOCK_DGRAM; /* Coap uses UDP */
345 hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
346 int s = getaddrinfo(localAddr, localPort, &hints, &result);
347 if (s != 0) {
348 DFINDER_LOGE(TAG, "getaddrinfo failed, error: %d, desc: %s", errno, strerror(errno));
349 return NULL;
350 }
351 session = CoapGetSessionInner(result, ctx, coapServerParameter);
352 freeaddrinfo(result);
353 } else {
354 session = coap_new_client_session(ctx, NULL, dst, proto);
355 if (session == NULL) {
356 DFINDER_LOGE(TAG, "coap_new_client_session failed with null local addr");
357 }
358 }
359 CoapSetAckTimeOut(session);
360 return session;
361 }
362
CoapGetSession(coap_context_t * ctx,const char * localAddr,const char * localPort,const CoapServerParameter * coapServerParameter)363 coap_session_t *CoapGetSession(coap_context_t *ctx, const char *localAddr, const char *localPort,
364 const CoapServerParameter *coapServerParameter)
365 {
366 coap_session_t *session = CoapGetSessionEx(ctx, localAddr, localPort, coapServerParameter);
367 if (session == NULL) {
368 IncStatistics(STATS_CREATE_SESSION_FAILED);
369 }
370 return session;
371 }
372
IsCoapCtxEndpointSocket(const coap_context_t * ctx,int fd)373 uint8_t IsCoapCtxEndpointSocket(const coap_context_t *ctx, int fd)
374 {
375 coap_endpoint_t *iterator = NULL;
376 coap_endpoint_t *listeningEndpoints = NULL;
377 coap_endpoint_t *tmp = NULL;
378 if (ctx == NULL) {
379 DFINDER_LOGW(TAG, "coap context passed in is null");
380 return NSTACKX_FALSE;
381 }
382 listeningEndpoints = coap_context_get_endpoint(ctx);
383 LL_FOREACH_SAFE(listeningEndpoints, iterator, tmp) {
384 if (iterator->sock.fd == fd) {
385 return NSTACKX_TRUE;
386 }
387 }
388 return NSTACKX_FALSE;
389 }
390