Files
libcoap/examples/coap-server.c
T
Jon Shallow 0e02d751da Add in internal epoll support to expose a monitor-able file descriptor
If epoll is available, use it within libcoap to speed things up, as well as
present to applications a file descriptor that can be used in a select() or
returned as an event in a epoll_wait() call.

Whenever coap_new_context() is called. set up a new epoll infrastructure with
epoll_create() and destroy it with coap_free_context().

For each new endpoint or session, add the socket fd to epoll.  When the socket
is closed, remove the socket fd from epoll.

Whenever a socket wants a write, the appropriate epoll event gets EPOLLOUT
added and when the write happens reset the event back to just EPOLLIN.

As there may be a packet retry event sometime in the future, a timerfd is
created and added to the epoll structure (done in coap_new_context) which gets
freed off in coap_free_context. If coap_run_once() determines that there is
a future retry, timerfd is informed of when the event is to occur.  When
timerfd times out, the application file descriptor will indicate a read is
available.

A new function coap_context_get_coap_fd(ctx) returns the application file
descriptor.  The only other API change is that if wait_ms is set to 1 for
coap_run_once(ctx, wait_ms), there is no waiting for a new packet to arrive.

coap_run_once() is called, whether epoll support is available or not, to
process all the underlying required i/o.

If there is no epoll support, coap_run_once() continues to use the old
select() method, otherwise it will use epoll_wait(). If there is epoll
support, a modified version of coap_read() (coap_io_do_events()) is
used instead.

Makefile.am:

Do not expose coap_epoll_ctl_mod() and coap_io_do_events() to applications.

configure.ac:

Test for the presence of sys/epoll.h and sys/timerfd.h and if so setup
COAP_EPOLL_SUPPORT to be used for #ifdef testing.
Include new man page coap_io.txt.

examples/coap-server.c:

Include support for using this new exposed file descriptor.

include/coap2/coap_io.h:

Define COAP_MAX_EPOLL_EVENTS (to limit the number of events processed at once)
and include coap_endpoint_t and coap_session_t in coap_socket_t.
Include (internal) definition for coap_epoll_ctl_mod()

include/coap2/net.h:

Add epfd and eptimerfd to coap_context_t.
Define coap_context_get_coap_fd() for applications to use.
Include (internal) definition for coap_io_do_events().
Define COAP_RUN_BLOCK and COAP_RUN_NONBLOCK.

libcoap-2.map:
libcoap-2.sym:

Include coap_context_get_coap_fd.

configure.ac:
man/Makefile.am:
man/coap.txt.in:
man/coap_io.txt.in: (New)

Include the new coap_in(3) man page that documents coap_run_once() and
coap_context_get_coap_fd().

src/coap_io.c:
src/coap_openssl.c:
src/coap_session.c:
src/net.c:

Add in the epoll support.

src/net.c:

Add in coap_context_get_coap_fd() function.
2019-08-30 11:51:15 +01:00

1069 lines
33 KiB
C

/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* coap -- simple implementation of the Constrained Application Protocol (CoAP)
* as defined in RFC 7252
*
* Copyright (C) 2010--2019 Olaf Bergmann <bergmann@tzi.org> and others
*
* This file is part of the CoAP library libcoap. Please see README for terms
* of use.
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
#ifdef _WIN32
#define strcasecmp _stricmp
#include "getopt.c"
#if !defined(S_ISDIR)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif
#else
#include <unistd.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <dirent.h>
#endif
/* Need to refresh time once per sec */
#define COAP_RESOURCE_CHECK_TIME 1
#include <coap2/coap.h>
#ifndef min
#define min(a,b) ((a) < (b) ? (a) : (b))
#endif
/* temporary storage for dynamic resource representations */
static int quit = 0;
/* changeable clock base (see handle_put_time()) */
static time_t clock_offset;
static time_t my_clock_base = 0;
struct coap_resource_t *time_resource = NULL;
static int resource_flags = COAP_RESOURCE_FLAGS_NOTIFY_CON;
static char *cert_file = NULL; /* Combined certificate and private key in PEM */
static char *ca_file = NULL; /* CA for cert_file - for cert checking in PEM */
static char *root_ca_file = NULL; /* List of trusted Root CAs in PEM */
static int require_peer_cert = 1; /* By default require peer cert */
#define MAX_KEY 64 /* Maximum length of a key (i.e., PSK) in bytes. */
static uint8_t key[MAX_KEY];
static ssize_t key_length = 0;
int key_defined = 0;
static const char *hint = "CoAP";
static int support_dynamic = 0;
#ifndef WITHOUT_ASYNC
/* This variable is used to mimic long-running tasks that require
* asynchronous responses. */
static coap_async_state_t *async = NULL;
#endif /* WITHOUT_ASYNC */
#ifdef __GNUC__
#define UNUSED_PARAM __attribute__ ((unused))
#else /* not a GCC */
#define UNUSED_PARAM
#endif /* GCC */
/* SIGINT handler: set quit to 1 for graceful termination */
static void
handle_sigint(int signum UNUSED_PARAM) {
quit = 1;
}
#define INDEX "This is a test server made with libcoap (see https://libcoap.net)\n" \
"Copyright (C) 2010--2019 Olaf Bergmann <bergmann@tzi.org> and others\n\n"
static void
hnd_get_index(coap_context_t *ctx UNUSED_PARAM,
struct coap_resource_t *resource,
coap_session_t *session,
coap_pdu_t *request,
coap_binary_t *token,
coap_string_t *query UNUSED_PARAM,
coap_pdu_t *response) {
coap_add_data_blocked_response(resource, session, request, response, token,
COAP_MEDIATYPE_TEXT_PLAIN, 0x2ffff,
strlen(INDEX),
(const uint8_t *)INDEX);
}
static void
hnd_get_time(coap_context_t *ctx UNUSED_PARAM,
struct coap_resource_t *resource,
coap_session_t *session,
coap_pdu_t *request,
coap_binary_t *token,
coap_string_t *query,
coap_pdu_t *response) {
unsigned char buf[40];
size_t len;
time_t now;
coap_tick_t t;
(void)request;
/* FIXME: return time, e.g. in human-readable by default and ticks
* when query ?ticks is given. */
if (my_clock_base) {
/* calculate current time */
coap_ticks(&t);
now = my_clock_base + (t / COAP_TICKS_PER_SECOND);
if (query != NULL
&& coap_string_equal(query, coap_make_str_const("ticks"))) {
/* output ticks */
len = snprintf((char *)buf, sizeof(buf), "%u", (unsigned int)now);
} else { /* output human-readable time */
struct tm *tmp;
tmp = gmtime(&now);
if (!tmp) {
/* If 'now' is not valid */
response->code = COAP_RESPONSE_CODE(404);
return;
}
else {
len = strftime((char *)buf, sizeof(buf), "%b %d %H:%M:%S", tmp);
}
}
coap_add_data_blocked_response(resource, session, request, response, token,
COAP_MEDIATYPE_TEXT_PLAIN, 1,
len,
buf);
}
else {
/* if my_clock_base was deleted, we pretend to have no such resource */
response->code = COAP_RESPONSE_CODE(404);
}
}
static void
hnd_put_time(coap_context_t *ctx UNUSED_PARAM,
struct coap_resource_t *resource,
coap_session_t *session UNUSED_PARAM,
coap_pdu_t *request,
coap_binary_t *token UNUSED_PARAM,
coap_string_t *query UNUSED_PARAM,
coap_pdu_t *response) {
coap_tick_t t;
size_t size;
unsigned char *data;
/* FIXME: re-set my_clock_base to clock_offset if my_clock_base == 0
* and request is empty. When not empty, set to value in request payload
* (insist on query ?ticks). Return Created or Ok.
*/
/* if my_clock_base was deleted, we pretend to have no such resource */
response->code =
my_clock_base ? COAP_RESPONSE_CODE(204) : COAP_RESPONSE_CODE(201);
coap_resource_notify_observers(resource, NULL);
/* coap_get_data() sets size to 0 on error */
(void)coap_get_data(request, &size, &data);
if (size == 0) /* re-init */
my_clock_base = clock_offset;
else {
my_clock_base = 0;
coap_ticks(&t);
while(size--)
my_clock_base = my_clock_base * 10 + *data++;
my_clock_base -= t / COAP_TICKS_PER_SECOND;
/* Sanity check input value */
if (!gmtime(&my_clock_base)) {
unsigned char buf[3];
response->code = COAP_RESPONSE_CODE(400);
coap_add_option(response,
COAP_OPTION_CONTENT_FORMAT,
coap_encode_var_safe(buf, sizeof(buf),
COAP_MEDIATYPE_TEXT_PLAIN), buf);
coap_add_data(response, 22, (const uint8_t*)"Invalid set time value");
/* re-init as value is bad */
my_clock_base = clock_offset;
}
}
}
static void
hnd_delete_time(coap_context_t *ctx UNUSED_PARAM,
struct coap_resource_t *resource UNUSED_PARAM,
coap_session_t *session UNUSED_PARAM,
coap_pdu_t *request UNUSED_PARAM,
coap_binary_t *token UNUSED_PARAM,
coap_string_t *query UNUSED_PARAM,
coap_pdu_t *response UNUSED_PARAM) {
my_clock_base = 0; /* mark clock as "deleted" */
/* type = request->hdr->type == COAP_MESSAGE_CON */
/* ? COAP_MESSAGE_ACK : COAP_MESSAGE_NON; */
}
#ifndef WITHOUT_ASYNC
static void
hnd_get_async(coap_context_t *ctx,
struct coap_resource_t *resource UNUSED_PARAM,
coap_session_t *session,
coap_pdu_t *request,
coap_binary_t *token UNUSED_PARAM,
coap_string_t *query UNUSED_PARAM,
coap_pdu_t *response) {
unsigned long delay = 5;
size_t size;
if (async) {
if (async->id != request->tid) {
coap_opt_filter_t f;
coap_option_filter_clear(f);
response->code = COAP_RESPONSE_CODE(503);
}
return;
}
if (query) {
const uint8_t *p = query->s;
delay = 0;
for (size = query->length; size; --size, ++p)
delay = delay * 10 + (*p - '0');
}
async = coap_register_async(ctx,
session,
request,
COAP_ASYNC_SEPARATE | COAP_ASYNC_CONFIRM,
(void *)(COAP_TICKS_PER_SECOND * delay));
}
static void
check_async(coap_context_t *ctx,
coap_tick_t now) {
coap_pdu_t *response;
coap_async_state_t *tmp;
size_t size = 13;
if (!async || now < async->created + (unsigned long)async->appdata)
return;
response = coap_pdu_init(async->flags & COAP_ASYNC_CONFIRM
? COAP_MESSAGE_CON
: COAP_MESSAGE_NON,
COAP_RESPONSE_CODE(205), 0, size);
if (!response) {
coap_log(LOG_DEBUG, "check_async: insufficient memory, we'll try later\n");
async->appdata =
(void *)((unsigned long)async->appdata + 15 * COAP_TICKS_PER_SECOND);
return;
}
response->tid = coap_new_message_id(async->session);
if (async->tokenlen)
coap_add_token(response, async->tokenlen, async->token);
coap_add_data(response, 4, (const uint8_t *)"done");
if (coap_send(async->session, response) == COAP_INVALID_TID) {
coap_log(LOG_DEBUG, "check_async: cannot send response for message\n");
}
coap_remove_async(ctx, async->session, async->id, &tmp);
coap_free_async(async);
async = NULL;
}
#endif /* WITHOUT_ASYNC */
typedef struct dynamic_resource_t {
coap_string_t *uri_path;
coap_string_t *value;
coap_resource_t *resource;
int created;
uint16_t media_type;
} dynamic_resource_t;
static int dynamic_count = 0;
static dynamic_resource_t *dynamic_entry = NULL;
/*
* Regular DELETE handler - used by resources created by the
* Unknown Resource PUT handler
*/
static void
hnd_delete(coap_context_t *ctx,
coap_resource_t *resource,
coap_session_t *session UNUSED_PARAM,
coap_pdu_t *request UNUSED_PARAM,
coap_binary_t *token UNUSED_PARAM,
coap_string_t *query UNUSED_PARAM,
coap_pdu_t *response UNUSED_PARAM
) {
int i;
coap_string_t *uri_path;
/* get the uri_path */
uri_path = coap_get_uri_path(request);
if (!uri_path) {
response->code = COAP_RESPONSE_CODE(404);
return;
}
for (i = 0; i < dynamic_count; i++) {
if (coap_string_equal(uri_path, dynamic_entry[i].uri_path)) {
/* Dynamic entry no longer required - delete it */
coap_delete_string(dynamic_entry[i].value);
if (dynamic_count-i > 1) {
memmove (&dynamic_entry[i],
&dynamic_entry[i+1],
(dynamic_count-i-1) * sizeof (dynamic_entry[0]));
}
dynamic_count--;
break;
}
}
/* Dynamic resource no longer required - delete it */
coap_delete_resource(ctx, resource);
response->code = COAP_RESPONSE_CODE(202);
return;
}
/*
* Regular GET handler - used by resources created by the
* Unknown Resource PUT handler
*/
static void
hnd_get(coap_context_t *ctx UNUSED_PARAM,
coap_resource_t *resource,
coap_session_t *session,
coap_pdu_t *request,
coap_binary_t *token,
coap_string_t *query UNUSED_PARAM,
coap_pdu_t *response
) {
coap_str_const_t *uri_path;
int i;
dynamic_resource_t *resource_entry = NULL;
coap_str_const_t value = { 0, NULL };
/*
* request will be NULL if an Observe triggered request, so the uri_path,
* if needed, must be abstracted from the resource.
* The uri_path string is a const pointer
*/
uri_path = coap_resource_get_uri_path(resource);
if (!uri_path) {
response->code = COAP_RESPONSE_CODE(404);
return;
}
for (i = 0; i < dynamic_count; i++) {
if (coap_string_equal(uri_path, dynamic_entry[i].uri_path)) {
break;
}
}
if (i == dynamic_count) {
response->code = COAP_RESPONSE_CODE(404);
return;
}
resource_entry = &dynamic_entry[i];
if (resource_entry->value) {
value.length = resource_entry->value->length;
value.s = resource_entry->value->s;
}
coap_add_data_blocked_response(resource, session, request, response, token,
resource_entry->media_type, -1,
value.length,
value.s);
return;
}
/*
* Regular PUT handler - used by resources created by the
* Unknown Resource PUT handler
*/
static void
hnd_put(coap_context_t *ctx UNUSED_PARAM,
coap_resource_t *resource,
coap_session_t *session UNUSED_PARAM,
coap_pdu_t *request,
coap_binary_t *token UNUSED_PARAM,
coap_string_t *query UNUSED_PARAM,
coap_pdu_t *response
) {
coap_string_t *uri_path;
int i;
size_t size;
uint8_t *data;
coap_block_t block1;
dynamic_resource_t *resource_entry = NULL;
unsigned char buf[6]; /* space to hold encoded/decoded uints */
coap_opt_iterator_t opt_iter;
coap_opt_t *option;
/* get the uri_path */
uri_path = coap_get_uri_path(request);
if (!uri_path) {
response->code = COAP_RESPONSE_CODE(404);
return;
}
/*
* Locate the correct dynamic block for this request
*/
for (i = 0; i < dynamic_count; i++) {
if (coap_string_equal(uri_path, dynamic_entry[i].uri_path)) {
break;
}
}
if (i == dynamic_count) {
if (dynamic_count >= support_dynamic) {
/* Should have been caught in hnd_unknown_put() */
response->code = COAP_RESPONSE_CODE(406);
coap_delete_string(uri_path);
return;
}
dynamic_count++;
dynamic_entry = realloc (dynamic_entry, dynamic_count * sizeof(dynamic_entry[0]));
if (dynamic_entry) {
dynamic_entry[i].uri_path = uri_path;
dynamic_entry[i].value = NULL;
dynamic_entry[i].resource = resource;
dynamic_entry[i].created = 1;
response->code = COAP_RESPONSE_CODE(201);
if ((option = coap_check_option(request, COAP_OPTION_CONTENT_TYPE, &opt_iter)) != NULL) {
dynamic_entry[i].media_type =
coap_decode_var_bytes (coap_opt_value (option), coap_opt_length (option));
}
else {
dynamic_entry[i].media_type = COAP_MEDIATYPE_TEXT_PLAIN;
}
/* Store media type of new resource in ct. We can use buf here
* as coap_add_attr() will copy the passed string. */
memset(buf, 0, sizeof(buf));
snprintf((char *)buf, sizeof(buf), "%d", dynamic_entry[i].media_type);
/* ensure that buf is always zero-terminated */
assert(buf[sizeof(buf) - 1] == '\0');
buf[sizeof(buf) - 1] = '\0';
coap_add_attr(resource,
coap_make_str_const("ct"),
coap_make_str_const(buf),
0);
} else {
dynamic_count--;
response->code = COAP_RESPONSE_CODE(500);
return;
}
} else {
/* Need to do this as coap_get_uri_path() created it */
coap_delete_string(uri_path);
response->code = COAP_RESPONSE_CODE(204);
dynamic_entry[i].created = 0;
coap_resource_notify_observers(dynamic_entry[i].resource, NULL);
}
resource_entry = &dynamic_entry[i];
if (coap_get_block(request, COAP_OPTION_BLOCK1, &block1)) {
/* handle BLOCK1 */
if (coap_get_data(request, &size, &data) && (size > 0)) {
size_t offset = block1.num << (block1.szx + 4);
coap_string_t *value = resource_entry->value;
if (offset == 0) {
if (value) {
coap_delete_string(value);
value = NULL;
}
}
else if (offset >
(resource_entry->value ? resource_entry->value->length : 0)) {
/* Upload is not sequential - block missing */
response->code = COAP_RESPONSE_CODE(408);
return;
}
else if (offset <
(resource_entry->value ? resource_entry->value->length : 0)) {
/* Upload is not sequential - block duplicated */
goto just_respond;
}
/* Add in new block to end of current data */
resource_entry->value = coap_new_string(offset + size);
memcpy (&resource_entry->value->s[offset], data, size);
resource_entry->value->length = offset + size;
if (value) {
memcpy (resource_entry->value->s, value->s, value->length);
coap_delete_string(value);
}
}
just_respond:
if (block1.m) {
response->code = COAP_RESPONSE_CODE(231);
}
else if (resource_entry->created) {
response->code = COAP_RESPONSE_CODE(201);
}
else {
response->code = COAP_RESPONSE_CODE(204);
}
coap_add_option(response,
COAP_OPTION_BLOCK1,
coap_encode_var_safe(buf, sizeof(buf),
((block1.num << 4) |
(block1.m << 3) |
block1.szx)),
buf);
}
else if (coap_get_data(request, &size, &data) && (size > 0)) {
/* Not a BLOCK1 with data */
if (resource_entry->value) {
coap_delete_string(resource_entry->value);
resource_entry->value = NULL;
}
resource_entry->value = coap_new_string(size);
memcpy (resource_entry->value->s, data, size);
resource_entry->value->length = size;
}
else {
/* Not a BLOCK1 and no data */
if (resource_entry->value) {
coap_delete_string(resource_entry->value);
resource_entry->value = NULL;
}
}
}
/*
* Unknown Resource PUT handler
*/
static void
hnd_unknown_put(coap_context_t *ctx,
coap_resource_t *resource UNUSED_PARAM,
coap_session_t *session,
coap_pdu_t *request,
coap_binary_t *token,
coap_string_t *query,
coap_pdu_t *response
) {
coap_resource_t *r;
coap_string_t *uri_path;
/* get the uri_path - will will get used by coap_resource_init() */
uri_path = coap_get_uri_path(request);
if (!uri_path) {
response->code = COAP_RESPONSE_CODE(404);
return;
}
if (dynamic_count >= support_dynamic) {
response->code = COAP_RESPONSE_CODE(406);
return;
}
/*
* Create a resource to handle the new URI
* uri_path will get deleted when the resource is removed
*/
r = coap_resource_init((coap_str_const_t*)uri_path,
COAP_RESOURCE_FLAGS_RELEASE_URI | resource_flags);
coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"Dynamic\""), 0);
coap_register_handler(r, COAP_REQUEST_PUT, hnd_put);
coap_register_handler(r, COAP_REQUEST_DELETE, hnd_delete);
/* We possibly want to Observe the GETs */
coap_resource_set_get_observable(r, 1);
coap_register_handler(r, COAP_REQUEST_GET, hnd_get);
coap_add_resource(ctx, r);
/* Do the PUT for this first call */
hnd_put(ctx, r, session, request, token, query, response);
return;
}
static void
init_resources(coap_context_t *ctx) {
coap_resource_t *r;
r = coap_resource_init(NULL, 0);
coap_register_handler(r, COAP_REQUEST_GET, hnd_get_index);
coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"General Info\""), 0);
coap_add_resource(ctx, r);
/* store clock base to use in /time */
my_clock_base = clock_offset;
r = coap_resource_init(coap_make_str_const("time"), resource_flags);
coap_register_handler(r, COAP_REQUEST_GET, hnd_get_time);
coap_register_handler(r, COAP_REQUEST_PUT, hnd_put_time);
coap_register_handler(r, COAP_REQUEST_DELETE, hnd_delete_time);
coap_resource_set_get_observable(r, 1);
coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"Internal Clock\""), 0);
coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("\"ticks\""), 0);
coap_add_attr(r, coap_make_str_const("if"), coap_make_str_const("\"clock\""), 0);
coap_add_resource(ctx, r);
time_resource = r;
if (support_dynamic > 0) {
/* Create a resource to handle PUTs to unknown URIs */
r = coap_resource_unknown_init(hnd_unknown_put);
coap_add_resource(ctx, r);
}
#ifndef WITHOUT_ASYNC
r = coap_resource_init(coap_make_str_const("async"), 0);
coap_register_handler(r, COAP_REQUEST_GET, hnd_get_async);
coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0);
coap_add_resource(ctx, r);
#endif /* WITHOUT_ASYNC */
}
static int
verify_cn_callback(const char *cn,
const uint8_t *asn1_public_cert UNUSED_PARAM,
size_t asn1_length UNUSED_PARAM,
coap_session_t *session UNUSED_PARAM,
unsigned depth,
int validated UNUSED_PARAM,
void *arg UNUSED_PARAM
) {
coap_log(LOG_INFO, "CN '%s' presented by client (%s)\n",
cn, depth ? "CA" : "Certificate");
return 1;
}
static coap_dtls_key_t *
verify_sni_callback(const char *sni,
void *arg UNUSED_PARAM
) {
static coap_dtls_key_t dtls_key;
/* Just use the defined keys for now */
memset (&dtls_key, 0, sizeof(dtls_key));
dtls_key.key_type = COAP_PKI_KEY_PEM;
dtls_key.key.pem.public_cert = cert_file;
dtls_key.key.pem.private_key = cert_file;
dtls_key.key.pem.ca_file = ca_file;
if (sni[0]) {
coap_log(LOG_INFO, "SNI '%s' requested\n", sni);
}
else {
coap_log(LOG_DEBUG, "SNI not requested\n");
}
return &dtls_key;
}
static void
fill_keystore(coap_context_t *ctx) {
if (cert_file) {
coap_dtls_pki_t dtls_pki;
memset (&dtls_pki, 0, sizeof(dtls_pki));
dtls_pki.version = COAP_DTLS_PKI_SETUP_VERSION;
if (ca_file) {
/*
* Add in additional certificate checking.
* This list of enabled can be tuned for the specific
* requirements - see 'man coap_encryption'.
*/
dtls_pki.verify_peer_cert = 1;
dtls_pki.require_peer_cert = require_peer_cert;
dtls_pki.allow_self_signed = 1;
dtls_pki.allow_expired_certs = 1;
dtls_pki.cert_chain_validation = 1;
dtls_pki.cert_chain_verify_depth = 2;
dtls_pki.check_cert_revocation = 1;
dtls_pki.allow_no_crl = 1;
dtls_pki.allow_expired_crl = 1;
dtls_pki.validate_cn_call_back = verify_cn_callback;
dtls_pki.cn_call_back_arg = NULL;
dtls_pki.validate_sni_call_back = verify_sni_callback;
dtls_pki.sni_call_back_arg = NULL;
}
dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM;
dtls_pki.pki_key.key.pem.public_cert = cert_file;
dtls_pki.pki_key.key.pem.private_key = cert_file;
dtls_pki.pki_key.key.pem.ca_file = ca_file;
/* If general root CAs are defined */
if (root_ca_file) {
struct stat stbuf;
if ((stat(root_ca_file, &stbuf) == 0) && S_ISDIR(stbuf.st_mode)) {
coap_context_set_pki_root_cas(ctx, NULL, root_ca_file);
} else {
coap_context_set_pki_root_cas(ctx, root_ca_file, NULL);
}
}
if (key_defined)
coap_context_set_psk(ctx, hint, key, key_length);
coap_context_set_pki(ctx, &dtls_pki);
}
else if (key_defined) {
coap_context_set_psk(ctx, hint, key, key_length);
}
else if (coap_dtls_is_supported() || coap_tls_is_supported()) {
coap_log(LOG_DEBUG,
"(D)TLS not enabled as neither -k or -c options specified\n");
}
}
static void
usage( const char *program, const char *version) {
const char *p;
char buffer[64];
p = strrchr( program, '/' );
if ( p )
program = ++p;
fprintf( stderr, "%s v%s -- a small CoAP implementation\n"
"(c) 2010,2011,2015-2018 Olaf Bergmann <bergmann@tzi.org> and others\n\n"
"%s\n\n"
"Usage: %s [-d max] [-g group] [-l loss] [-p port] [-v num]\n"
"\t\t[-A address] [-N]\n"
"\t\t[[-k key] [-h hint]]\n"
"\t\t[[-c certfile][-C cafile] [-n] [-R root_cafile]]\n"
"General Options\n"
"\t-d max \t\tAllow dynamic creation of up to a total of max\n"
"\t \t\tresources. If max is reached, a 4.06 code is returned\n"
"\t \t\tuntil one of the dynamic resources has been deleted\n"
"\t-g group\tJoin the given multicast group\n"
"\t-l list\t\tFail to send some datagrams specified by a comma\n"
"\t \t\tseparated list of numbers or number ranges\n"
"\t \t\t(for debugging only)\n"
"\t-l loss%%\tRandomly fail to send datagrams with the specified\n"
"\t \t\tprobability - 100%% all datagrams, 0%% no datagrams\n"
"\t \t\t(for debugging only)\n"
"\t-p port\t\tListen on specified port\n"
"\t-v num \t\tVerbosity level (default 3, maximum is 9). Above 7,\n"
"\t \t\tthere is increased verbosity in GnuTLS logging\n"
"\t-A address\tInterface address to bind to\n"
"\t-N \t\tMake \"observe\" responses NON-confirmable. Even if set\n"
"\t \t\tevery fifth response will still be sent as a confirmable\n"
"\t \t\tresponse (RFC 7641 requirement)\n"
"PSK Options (if supported by underlying (D)TLS library)\n"
"\t-h hint\t\tPSK Hint. Default is CoAP\n"
"\t-k key \t\tPre-shared key. This argument requires (D)TLS with PSK\n"
"\t \t\tto be available. This cannot be empty if defined.\n"
"\t \t\tNote that both -c and -k need to be defined\n"
"\t \t\tfor both PSK and PKI to be concurrently supported\n"
"PKI Options (if supported by underlying (D)TLS library)\n"
"\t-c certfile\tPEM file containing both CERTIFICATE and PRIVATE KEY\n"
"\t \t\tThis argument requires (D)TLS with PKI to be available\n"
"\t-n \t\tDisable the requirement for clients to have defined\n"
"\t \t\tclient certificates\n"
"\t-C cafile\tPEM file containing the CA Certificate that was used to\n"
"\t \t\tsign the certfile. If defined, then the client will be\n"
"\t \t\tgiven this CA Certificate during the TLS set up.\n"
"\t \t\tFurthermore, this will trigger the validation of the\n"
"\t \t\tclient certificate. If certfile is self-signed (as\n"
"\t \t\tdefined by '-c certfile'), then you need to have on the\n"
"\t \t\tcommand line the same filename for both the certfile and\n"
"\t \t\tcafile (as in '-c certfile -C certfile') to trigger\n"
"\t \t\tvalidation\n"
"\t-R root_cafile\tPEM file containing the set of trusted root CAs that\n"
"\t \t\tare to be used to validate the client certificate.\n"
"\t \t\tThe '-C cafile' does not have to be in this list and is\n"
"\t \t\t'trusted' for the verification.\n"
"\t \t\tAlternatively, this can point to a directory containing\n"
"\t \t\ta set of CA PEM files\n"
, program, version, coap_string_tls_version(buffer, sizeof(buffer)),
program);
}
static coap_context_t *
get_context(const char *node, const char *port) {
coap_context_t *ctx = NULL;
int s;
struct addrinfo hints;
struct addrinfo *result, *rp;
ctx = coap_new_context(NULL);
if (!ctx) {
return NULL;
}
/* Need PSK set up before we set up (D)TLS endpoints */
fill_keystore(ctx);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_DGRAM; /* Coap uses UDP */
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
s = getaddrinfo(node, port, &hints, &result);
if ( s != 0 ) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
coap_free_context(ctx);
return NULL;
}
/* iterate through results until success */
for (rp = result; rp != NULL; rp = rp->ai_next) {
coap_address_t addr, addrs;
coap_endpoint_t *ep_udp = NULL, *ep_dtls = NULL, *ep_tcp = NULL, *ep_tls = NULL;
if (rp->ai_addrlen <= sizeof(addr.addr)) {
coap_address_init(&addr);
addr.size = rp->ai_addrlen;
memcpy(&addr.addr, rp->ai_addr, rp->ai_addrlen);
addrs = addr;
if (addr.addr.sa.sa_family == AF_INET) {
uint16_t temp = ntohs(addr.addr.sin.sin_port) + 1;
addrs.addr.sin.sin_port = htons(temp);
} else if (addr.addr.sa.sa_family == AF_INET6) {
uint16_t temp = ntohs(addr.addr.sin6.sin6_port) + 1;
addrs.addr.sin6.sin6_port = htons(temp);
} else {
goto finish;
}
ep_udp = coap_new_endpoint(ctx, &addr, COAP_PROTO_UDP);
if (ep_udp) {
if (coap_dtls_is_supported() && (key_defined || cert_file)) {
ep_dtls = coap_new_endpoint(ctx, &addrs, COAP_PROTO_DTLS);
if (!ep_dtls)
coap_log(LOG_CRIT, "cannot create DTLS endpoint\n");
}
} else {
coap_log(LOG_CRIT, "cannot create UDP endpoint\n");
continue;
}
ep_tcp = coap_new_endpoint(ctx, &addr, COAP_PROTO_TCP);
if (ep_tcp) {
if (coap_tls_is_supported() && (key_defined || cert_file)) {
ep_tls = coap_new_endpoint(ctx, &addrs, COAP_PROTO_TLS);
if (!ep_tls)
coap_log(LOG_CRIT, "cannot create TLS endpoint\n");
}
} else {
coap_log(LOG_CRIT, "cannot create TCP endpoint\n");
}
if (ep_udp)
goto finish;
}
}
fprintf(stderr, "no context available for interface '%s'\n", node);
finish:
freeaddrinfo(result);
return ctx;
}
static ssize_t
cmdline_read_key(char *arg, unsigned char *buf, size_t maxlen) {
size_t len = strnlen(arg, maxlen);
if (len) {
memcpy(buf, arg, len);
return len;
}
return -1;
}
int
main(int argc, char **argv) {
coap_context_t *ctx;
char *group = NULL;
coap_tick_t now;
char addr_str[NI_MAXHOST] = "::";
char port_str[NI_MAXSERV] = "5683";
int opt;
coap_log_t log_level = LOG_WARNING;
unsigned wait_ms;
time_t t_last = 0;
int coap_fd;
fd_set m_readfds;
int nfds = 0;
#ifndef _WIN32
struct sigaction sa;
#endif
clock_offset = time(NULL);
while ((opt = getopt(argc, argv, "A:d:c:C:g:h:k:l:nNp:R:v:")) != -1) {
switch (opt) {
case 'A' :
strncpy(addr_str, optarg, NI_MAXHOST-1);
addr_str[NI_MAXHOST - 1] = '\0';
break;
case 'c' :
cert_file = optarg;
break;
case 'C' :
ca_file = optarg;
break;
case 'd' :
support_dynamic = atoi(optarg);
break;
case 'g' :
group = optarg;
break;
case 'h' :
if (!optarg[0]) {
coap_log( LOG_CRIT, "Invalid PSK hint specified\n" );
break;
}
hint = optarg;
break;
case 'k' :
key_length = cmdline_read_key(optarg, key, MAX_KEY);
if (key_length < 0) {
coap_log( LOG_CRIT, "Invalid PSK key specified\n" );
break;
}
key_defined = 1;
break;
case 'l':
if (!coap_debug_set_packet_loss(optarg)) {
usage(argv[0], LIBCOAP_PACKAGE_VERSION);
exit(1);
}
break;
case 'n':
require_peer_cert = 0;
break;
case 'N':
resource_flags = COAP_RESOURCE_FLAGS_NOTIFY_NON;
break;
case 'p' :
strncpy(port_str, optarg, NI_MAXSERV-1);
port_str[NI_MAXSERV - 1] = '\0';
break;
case 'R' :
root_ca_file = optarg;
break;
case 'v' :
log_level = strtol(optarg, NULL, 10);
break;
default:
usage( argv[0], LIBCOAP_PACKAGE_VERSION );
exit( 1 );
}
}
coap_startup();
coap_dtls_set_log_level(log_level);
coap_set_log_level(log_level);
ctx = get_context(addr_str, port_str);
if (!ctx)
return -1;
init_resources(ctx);
/* join multicast group if requested at command line */
if (group)
coap_join_mcast_group(ctx, group);
coap_fd = coap_context_get_coap_fd(ctx);
if (coap_fd != -1) {
/* if coap_fd is -1, then epoll is not supported within libcoap */
FD_ZERO(&m_readfds);
FD_SET(coap_fd, &m_readfds);
nfds = coap_fd + 1;
}
#ifdef _WIN32
signal(SIGINT, handle_sigint);
#else
memset (&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_handler = handle_sigint;
sa.sa_flags = 0;
sigaction (SIGINT, &sa, NULL);
sigaction (SIGTERM, &sa, NULL);
/* So we do not exit on a SIGPIPE */
sa.sa_handler = SIG_IGN;
sigaction (SIGPIPE, &sa, NULL);
#endif
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
while ( !quit ) {
int result;
if (coap_fd != -1) {
fd_set readfds = m_readfds;
struct timeval tv;
tv.tv_sec = wait_ms / 1000;
tv.tv_usec = (wait_ms % 1000) * 1000;
/* Wait until any i/o takes place */
result = select (nfds, &readfds, NULL, NULL, &tv);
if (result == -1) {
if (errno != EAGAIN) {
coap_log(LOG_DEBUG, "select: %s (%d)\n", coap_socket_strerror(), errno);
break;
}
}
if (result > 0) {
if (FD_ISSET(coap_fd, &readfds)) {
result = coap_run_once(ctx, COAP_RUN_NONBLOCK);
}
}
}
else {
/* epoll is not supported within libcoap */
result = coap_run_once( ctx, wait_ms );
}
if ( result < 0 ) {
break;
} else if ( result && (unsigned)result < wait_ms ) {
/* decrement if there is a result wait time returned */
wait_ms -= result;
} else {
/*
* result == 0, or result >= wait_ms
* (wait_ms could have decremented to a small value, below
* the granularity of the timer in coap_run_once() and hence
* result == 0)
*/
time_t t_now = time(NULL);
if (t_last != t_now) {
/* Happens once per second */
t_last = t_now;
if (time_resource) {
coap_resource_notify_observers(time_resource, NULL);
}
}
if (result) {
/* result must have been >= wait_ms, so reset wait_ms */
wait_ms = COAP_RESOURCE_CHECK_TIME * 1000;
}
}
#ifndef WITHOUT_ASYNC
/* check if we have to send asynchronous responses */
coap_ticks( &now );
check_async(ctx, now);
#endif /* WITHOUT_ASYNC */
}
coap_free_context(ctx);
coap_cleanup();
return 0;
}