wolfssl-examples/dtls-mcast/mcast-peer.c

535 lines
16 KiB
C

/* mcast-peer.c
*
* Copyright (C) 2006-2025 wolfSSL Inc.
*
* This file is part of wolfSSL. (formerly known as CyaSSL)
*
* wolfSSL 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 of the License, or
* (at your option) any later version.
*
* wolfSSL 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*=============================================================================
*
* Example of DTLS 1.2 multicast with PSK using wolfSSL.
* Three peers can communicate with each other over multicast.
*
* Usage: ./mcast-peer <node_id>
* where node_id is 0, 1, or 2
*
* Requires wolfSSL built with: ./configure --enable-dtls --enable-mcast
*/
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#include <wolfssl/error-ssl.h>
#include <wolfssl/wolfcrypt/random.h>
#include <wolfssl/wolfcrypt/pwdbased.h>
#include <wolfssl/wolfcrypt/memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <time.h>
/* Multicast group settings */
#define MCAST_GROUP "239.255.0.1"
#define MCAST_PORT 12345
#define MSGLEN 256
/* Number of peers in the multicast group */
#define NUM_PEERS 3
/* Pre-shared secret components (must be same for all peers) */
#define PMS_SIZE 48
#define RANDOM_SIZE 32
/* Epoch for the multicast session */
#define MCAST_EPOCH 1
#if defined(WOLFSSL_DTLS) && defined(WOLFSSL_MULTICAST) && \
defined(HAVE_PBKDF2) && !defined(NO_PWDBASED)
/* Global flag for clean shutdown */
static volatile sig_atomic_t running = 1;
static void sig_handler(int sig)
{
(void)sig;
running = 0;
}
/*
* Setup a multicast UDP socket for sending to the group
*/
static int setup_tx_socket(struct sockaddr_in* txAddr)
{
int sd;
unsigned char ttl = 1;
int on = 1;
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
perror("socket (tx)");
return -1;
}
/* Set TTL for multicast */
if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) {
perror("setsockopt IP_MULTICAST_TTL");
close(sd);
return -1;
}
/* Enable loopback so other processes on same host can receive */
if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &on, sizeof(on)) < 0) {
perror("setsockopt IP_MULTICAST_LOOP");
close(sd);
return -1;
}
/* Setup destination address */
memset(txAddr, 0, sizeof(*txAddr));
txAddr->sin_family = AF_INET;
txAddr->sin_addr.s_addr = inet_addr(MCAST_GROUP);
txAddr->sin_port = htons(MCAST_PORT);
/* Connect to multicast address for sending */
if (connect(sd, (struct sockaddr*)txAddr, sizeof(*txAddr)) < 0) {
perror("connect (tx)");
close(sd);
return -1;
}
return sd;
}
/*
* Setup a multicast UDP socket for receiving from the group
*/
static int setup_rx_socket(void)
{
int sd;
int on = 1;
struct sockaddr_in addr;
struct ip_mreq mreq;
sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
perror("socket (rx)");
return -1;
}
/* Allow multiple sockets to use the same port */
if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
perror("setsockopt SO_REUSEADDR");
close(sd);
return -1;
}
#ifdef SO_REUSEPORT
if (setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) {
perror("setsockopt SO_REUSEPORT");
close(sd);
return -1;
}
#endif
/* Bind to multicast port */
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(MCAST_PORT);
if (bind(sd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind (rx)");
close(sd);
return -1;
}
/* Join the multicast group */
mreq.imr_multiaddr.s_addr = inet_addr(MCAST_GROUP);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
perror("setsockopt IP_ADD_MEMBERSHIP");
close(sd);
return -1;
}
/* Set non-blocking for receive */
{
int flags = fcntl(sd, F_GETFL, 0);
if (flags < 0 || fcntl(sd, F_SETFL, flags | O_NONBLOCK) < 0) {
perror("fcntl");
close(sd);
return -1;
}
}
return sd;
}
int main(int argc, char** argv)
{
int ret;
int myId;
int txSd = -1;
int rxSd = -1;
WOLFSSL_CTX* ctx = NULL;
WOLFSSL* sslTx = NULL;
WOLFSSL* sslRx = NULL;
struct sockaddr_in txAddr;
int i;
/* Pre-shared secret components - same for all peers */
unsigned char pms[PMS_SIZE];
unsigned char clientRandom[RANDOM_SIZE];
unsigned char serverRandom[RANDOM_SIZE];
/* WDM-NULL-SHA256 cipher suite bytes */
unsigned char suite[2] = { 0x00, 0xFE };
time_t lastSend = 0;
int msgCount = 0;
/* Parse arguments */
if (argc != 2) {
fprintf(stderr, "Usage: %s <node_id>\n", argv[0]);
fprintf(stderr, " node_id: 0, 1, or 2\n");
return 1;
}
myId = atoi(argv[1]);
if (myId < 0 || myId >= NUM_PEERS) {
fprintf(stderr, "Error: node_id must be 0, 1, or 2\n");
return 1;
}
/* Setup signal handler */
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
printf("=== DTLS Multicast Peer - Node %d ===\n", myId);
/* Enable debug output if built with --enable-debug */
#ifdef DEBUG_WOLFSSL
wolfSSL_Debugging_ON();
#endif
/* Initialize the pre-shared secrets (same for all peers) */
{
const char* envSecret = getenv("DTLS_MCAST_SECRET");
char genSecret[2 * PMS_SIZE + 1];
const char* secret;
size_t len;
if (envSecret != NULL) {
secret = envSecret;
}
else {
byte secretBytes[PMS_SIZE];
WC_RNG rng;
printf("WARNING: DTLS_MCAST_SECRET not set. Generating a random "
"multicast secret for this run.\n"
" Export the printed value as DTLS_MCAST_SECRET "
"on ALL peers so they can share the same secret.\n");
ret = wc_InitRng(&rng);
if (ret != 0) {
fprintf(stderr, "Error: wc_InitRng failed: %d\n", ret);
return 1;
}
ret = wc_RNG_GenerateBlock(&rng, secretBytes, sizeof(secretBytes));
wc_FreeRng(&rng);
if (ret != 0) {
fprintf(stderr, "Error: Failed to generate random multicast "
"secret: %d\n", ret);
wc_ForceZero(secretBytes, sizeof(secretBytes));
return 1;
}
for (i = 0; i < (int)sizeof(secretBytes); i++)
snprintf(&genSecret[i * 2], 3, "%02x", secretBytes[i]);
wc_ForceZero(secretBytes, sizeof(secretBytes));
printf("Generated multicast secret (hex): %s\n"
"Export this value as DTLS_MCAST_SECRET on ALL peers.\n",
genSecret);
secret = genSecret;
}
len = strlen(secret);
/* PBKDF2 guards against a weak DTLS_MCAST_SECRET; distinct salts
* keep pms/clientRandom/serverRandom independent yet peer-shared. */
ret = wc_PBKDF2(pms, (byte*)secret, (int)len,
(byte*)"mcast-pms", 9, 4096, sizeof(pms), WC_SHA256);
if (ret == 0)
ret = wc_PBKDF2(clientRandom, (byte*)secret, (int)len,
(byte*)"mcast-client-random", 19, 4096,
sizeof(clientRandom), WC_SHA256);
if (ret == 0)
ret = wc_PBKDF2(serverRandom, (byte*)secret, (int)len,
(byte*)"mcast-server-random", 19, 4096,
sizeof(serverRandom), WC_SHA256);
wc_ForceZero(genSecret, sizeof(genSecret));
if (ret != 0) {
fprintf(stderr, "Error: Failed to derive multicast secret: %d\n",
ret);
wc_ForceZero(pms, sizeof(pms));
wc_ForceZero(clientRandom, sizeof(clientRandom));
wc_ForceZero(serverRandom, sizeof(serverRandom));
return 1;
}
}
/* Initialize wolfSSL */
ret = wolfSSL_Init();
if (ret != WOLFSSL_SUCCESS) {
fprintf(stderr, "Error: wolfSSL_Init failed: %d\n", ret);
wc_ForceZero(pms, sizeof(pms));
wc_ForceZero(clientRandom, sizeof(clientRandom));
wc_ForceZero(serverRandom, sizeof(serverRandom));
return 1;
}
/* Create DTLS 1.2 context */
ctx = wolfSSL_CTX_new(wolfDTLSv1_2_client_method());
if (ctx == NULL) {
fprintf(stderr, "Error: wolfSSL_CTX_new failed\n");
goto cleanup;
}
/* Set multicast member ID */
ret = wolfSSL_CTX_mcast_set_member_id(ctx, (unsigned short)myId);
if (ret != WOLFSSL_SUCCESS) {
fprintf(stderr, "Error: wolfSSL_CTX_mcast_set_member_id failed: %d\n", ret);
goto cleanup;
}
/* Set multicast cipher suite (NULL cipher with SHA256 for integrity) */
ret = wolfSSL_CTX_set_cipher_list(ctx, "WDM-NULL-SHA256");
if (ret != WOLFSSL_SUCCESS) {
fprintf(stderr, "Error: wolfSSL_CTX_set_cipher_list failed: %d\n", ret);
fprintf(stderr, "Make sure wolfSSL is built with --enable-mcast\n");
goto cleanup;
}
/* Setup sockets */
txSd = setup_tx_socket(&txAddr);
if (txSd < 0) {
goto cleanup;
}
rxSd = setup_rx_socket();
if (rxSd < 0) {
goto cleanup;
}
printf("Node %d: Sockets ready, joined multicast group %s:%d\n",
myId, MCAST_GROUP, MCAST_PORT);
/* Create SSL object for transmitting */
sslTx = wolfSSL_new(ctx);
if (sslTx == NULL) {
fprintf(stderr, "Error: wolfSSL_new (tx) failed\n");
goto cleanup;
}
/* Create SSL object for receiving */
sslRx = wolfSSL_new(ctx);
if (sslRx == NULL) {
fprintf(stderr, "Error: wolfSSL_new (rx) failed\n");
goto cleanup;
}
/* Set socket file descriptors */
wolfSSL_set_fd(sslTx, txSd);
wolfSSL_set_fd(sslRx, rxSd);
/* Set using nonblock for receive */
wolfSSL_dtls_set_using_nonblock(sslRx, 1);
/* Add peers for receive tracking BEFORE setting secret.
* IMPORTANT: wolfSSL_mcast_peer_add() zeros the peer entry, including
* nextEpoch. wolfSSL_set_secret() sets nextEpoch for all entries.
* So peers must be added first, then secret set afterward. */
for (i = 0; i < NUM_PEERS; i++) {
if (i != myId) {
ret = wolfSSL_mcast_peer_add(sslRx, (unsigned short)i, 0);
if (ret != WOLFSSL_SUCCESS) {
fprintf(stderr, "Error: wolfSSL_mcast_peer_add(%d) failed: %d\n",
i, ret);
goto cleanup;
}
printf("Node %d: Added peer %d to receive tracking\n", myId, i);
}
}
/* Set the pre-shared secret for both SSL objects.
* This must be called AFTER adding peers so their nextEpoch gets set. */
ret = wolfSSL_set_secret(sslTx, MCAST_EPOCH, pms, sizeof(pms),
clientRandom, serverRandom, suite);
if (ret != WOLFSSL_SUCCESS) {
fprintf(stderr, "Error: wolfSSL_set_secret (tx) failed: %d\n", ret);
goto cleanup;
}
ret = wolfSSL_set_secret(sslRx, MCAST_EPOCH, pms, sizeof(pms),
clientRandom, serverRandom, suite);
if (ret != WOLFSSL_SUCCESS) {
fprintf(stderr, "Error: wolfSSL_set_secret (rx) failed: %d\n", ret);
goto cleanup;
}
/* Flush any stale packets from previous runs */
{
char discard[MSGLEN];
struct timeval tv_flush = {0, 0};
fd_set fds;
while (1) {
FD_ZERO(&fds);
FD_SET(rxSd, &fds);
if (select(rxSd + 1, &fds, NULL, NULL, &tv_flush) <= 0)
break;
if (recv(rxSd, discard, sizeof(discard), 0) <= 0)
break;
}
}
printf("Node %d: Ready. Press Ctrl+C to exit.\n", myId);
printf("Node %d: Sending messages every 3 seconds...\n\n", myId);
/* Main loop */
while (running) {
fd_set readfds;
struct timeval tv;
time_t now;
FD_ZERO(&readfds);
FD_SET(rxSd, &readfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
ret = select(rxSd + 1, &readfds, NULL, NULL, &tv);
if (ret < 0) {
if (errno == EINTR) continue;
perror("select");
break;
}
/* Check for incoming messages */
if (ret > 0 && FD_ISSET(rxSd, &readfds)) {
unsigned char buf[MSGLEN];
unsigned short peerId = 0;
int recvLen;
recvLen = wolfSSL_mcast_read(sslRx, &peerId, buf, sizeof(buf) - 1);
if (recvLen > 0) {
/* Ignore our own messages (loopback) */
if (peerId != (unsigned short)myId) {
buf[recvLen] = '\0';
printf("Node %d: Received from peer %d: \"%s\"\n",
myId, peerId, buf);
}
}
else if (recvLen < 0) {
int err = wolfSSL_get_error(sslRx, recvLen);
if (err != WOLFSSL_ERROR_WANT_READ) {
/* Ignore decryption errors from our own packets */
if (err != DECRYPT_ERROR && err != VERIFY_MAC_ERROR) {
char errStr[80];
wolfSSL_ERR_error_string(err, errStr);
fprintf(stderr, "Node %d: Read error: %s (%d)\n",
myId, errStr, err);
}
}
}
}
/* Send a message every 3 seconds */
now = time(NULL);
if (now - lastSend >= 3) {
char msg[MSGLEN];
int writeLen;
snprintf(msg, sizeof(msg), "Hello from node %d, message #%d",
myId, ++msgCount);
writeLen = wolfSSL_write(sslTx, msg, (int)strlen(msg));
if (writeLen > 0) {
printf("Node %d: Sent: \"%s\"\n", myId, msg);
}
else {
int err = wolfSSL_get_error(sslTx, writeLen);
char errStr[80];
wolfSSL_ERR_error_string(err, errStr);
fprintf(stderr, "Node %d: Write error: %s (%d)\n",
myId, errStr, err);
}
lastSend = now;
}
}
printf("\nNode %d: Shutting down...\n", myId);
cleanup:
wc_ForceZero(pms, sizeof(pms));
wc_ForceZero(clientRandom, sizeof(clientRandom));
wc_ForceZero(serverRandom, sizeof(serverRandom));
if (sslTx != NULL) {
wolfSSL_free(sslTx);
}
if (sslRx != NULL) {
wolfSSL_free(sslRx);
}
if (ctx != NULL) {
wolfSSL_CTX_free(ctx);
}
if (rxSd >= 0) {
/* Leave multicast group */
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(MCAST_GROUP);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(rxSd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
close(rxSd);
}
if (txSd >= 0) {
close(txSd);
}
wolfSSL_Cleanup();
printf("Node %d: Goodbye!\n", myId);
return 0;
}
#else
int main()
{
fprintf(stderr, "Please configure the wolfssl library with "
"--enable-dtls --enable-mcast --enable-pwdbased.\n");
return EXIT_FAILURE;
}
#endif /* WOLFSSL_DTLS && WOLFSSL_MULTICAST && HAVE_PBKDF2 && !NO_PWDBASED */