/* 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 * where node_id is 0, 1, or 2 * * Requires wolfSSL built with: ./configure --enable-dtls --enable-mcast */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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 \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 */