From 6d88c555019f32509f303e23dcfbba824fecd2ee Mon Sep 17 00:00:00 2001 From: Chris Hiszpanski Date: Thu, 18 Apr 2019 00:55:30 -0700 Subject: Initial public commit. mDNS and SDP are functional. Otherwise, library is still very much a work in progress. All tests pass. --- src/urtc.c | 441 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 src/urtc.c (limited to 'src/urtc.c') diff --git a/src/urtc.c b/src/urtc.c new file mode 100644 index 0000000..892b632 --- /dev/null +++ b/src/urtc.c @@ -0,0 +1,441 @@ +/** + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include // assert +#include // errno +#include // poll +#include // pthread_create +#include // true +#include // fprintf +#include // calloc, free +#include // strerror +#include // close + +#include // inet_ntoa +#include // IPPROTO_UDP +#include // socket +#include + +#include "err.h" +#include "log.h" +#include "mdns.h" // mdns_subscribe, mdns_unsubscribe +#include "prng.h" // prng_init +#include "sdp.h" +#include "urtc.h" +#include "uuid.h" // uuid_create_str + +#define RX_BUF_CAP 2048 // receive buffer capacity + +#define POLL_TIMEOUT_MS 50 // poll() timeout (in milliseconds) + +enum signaling_state { + SIGNALING_STATE_STABLE = 0, + SIGNALING_STATE_HAVE_LOCAL_OFFER, + SIGNALING_STATE_HAVE_REMOTE_OFFER, + SIGNALING_STATE_HAVE_LOCAL_PRANSWER, + SIGNALING_STATE_HAVE_REMOTE_PRANSWER, +}; + +enum rtc_state { + STATE_NEW = 0, + + STATE_ICE_NEW, + STATE_ICE_GATHERING, + STATE_ICE_COMPLETE, + + STATE_CONNECTING, + + STATE_CONNECTED, + + STATE_DISCONNECTED, + + STATE_FAILED, + + STATE_CLOSED, + + NUM_STATES // must be last +}; + +enum rtc_event { + EVENT_SOCKET = 0, + EVENT_TIMER, + EVENT_MDNS, + NUM_EVENTS, // must be last +}; + +struct peerconn { + // socket file descriptor + int sockfd; + + // execution thread + pthread_t thread; + + // poll file descriptors + struct pollfd fds[NUM_EVENTS]; + + // callbacks + urtc_on_ice_candidate *on_ice_candidate; + urtc_force_idr *force_idr; + + // stun servers + const char **stun; + + // state machine + enum signaling_state ss; + + // local and remote descriptions + struct sdp ldesc, rdesc; + + // mDNS related state + struct { + char hostname[UUID_STR_LEN]; // .local hostname + int sockfd; // port 5353 listener + } mdns; +}; + + +typedef int (*event_handler)(struct peerconn *pc); + +const enum rtc_state state_table[NUM_STATES][NUM_EVENTS] = { + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW } +}; + +/** + * Handle incoming STUN packet + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int stun_handler( + struct peerconn *pc, + const uint8_t *pkt, + size_t n +) { + return 0; +} + +/** + * Handle incoming DTLS packet + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int dtls_handler( + struct peerconn *pc, + const uint8_t *pkt, + size_t n +) { + return 0; +} + +/** + * Handle incoming SRTP (or SRTCP) packet + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int rtp_handler( + struct peerconn *pc, + const uint8_t *pkt, + size_t n +) { + return 0; +} + +/** + * Handle incoming packet + * + * Packet may be a DTLS, SRTP, SRTCP, or STUN packet. Other packet types + * are discarded. + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int socket_event_handler(struct peerconn *pc) { + uint8_t buffer[RX_BUF_CAP]; + struct sockaddr_in ra; + socklen_t ralen; + ssize_t n; + + // read packet + ralen = sizeof(ra); + n = recvfrom( + pc->sockfd, + buffer, + sizeof(buffer), + 0, + (struct sockaddr *)(&ra), + &ralen + ); + + // rtp + if ((127 < buffer[0]) && (buffer[0] < 192)) { + log(INFO, "[rtp] %s", inet_ntoa(ra.sin_addr)); + rtp_handler(pc, buffer, n); + } else + // dtls + if ((19 < buffer[0]) && (buffer[0] < 64)) { + log(INFO, "[dtls] %s", inet_ntoa(ra.sin_addr)); + dtls_handler(pc, buffer, n); + } else + // stun + if (buffer[0] < 2) { + log(INFO, "[stun] %s", inet_ntoa(ra.sin_addr)); + stun_handler(pc, buffer, n); + } + + return 0; +} + +/** + * Handle timer event + * + * May need to: + * - resend STUN packet + * - resend DTLS packet + * - expire ICE candidate? + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int timer_event_handler(struct peerconn *pc) { + return 0; +} + +/** + * Handle incoming mDNS query + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int mdns_handler(struct peerconn *pc) { + uint8_t buffer[RX_BUF_CAP]; + ssize_t n; + + // read packet + if (n = recvfrom( + pc->mdns.sockfd, + buffer, + sizeof(buffer), + 0, + NULL, + NULL + ), -1 == n) { + log(ERROR, "%s", strerror(errno)); + goto _fail_recvfrom; + } + + // if valid query for peer connection's ephemeral hostname... + int type = mdns_validate_query(buffer, n, pc->mdns.hostname); + if (type > 0) { + if (type | TYPE_A) { + log(DEBUG, "[mdns] received A query"); + // ...respond to query with interface addresses + mdns_send_response(pc->mdns.sockfd, pc->mdns.hostname); + } + if (type | TYPE_AAAA) { + log(DEBUG, "[mdns] received AAAA query"); + } + } + + return 0; + +_fail_recvfrom: + return -URTC_ERR; +} + +const event_handler action_table[NUM_STATES][NUM_EVENTS] = { + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL } +}; + +/** + * Runloop finite state machine (mealy) for handling timer and receive events + */ + +// send stun request +// wait for stun reply +// timeout -> resend request + +// called dir caller +// ------ --- ------ +// setRemoteDescription(offer) <-- offer = createOffer() +// setLocalDescription(offer) +// answer = createAnswer() --> setRemoteDescription(answer) +// setLocalDescription(answer) +// +// cand = onIceCandidate --> addIceCandidate(cand) +// addIceCandidate(cand) <-- cand = onIceCandidate +// ... +// +// (connection established) +// +// (dtls handshake) +// +// srtp --> +// <-- rtcp, nack + +/** + * Execution thread per peer connection + * + * \param arg Peer connection + * + * \return Unused. + */ +static void * runloop(void *arg) { + struct peerconn *pc; + enum rtc_event event; + int n; + + pc = (struct peerconn *)arg; + assert(pc); + +_loop: + // block indefinitely (-1 timeout) until i/o event + n = poll(pc->fds, NUM_EVENTS, -1); + assert(n > 0); + + // which event(s) occurred? + if (pc->fds[EVENT_SOCKET].revents & POLLIN) { + event = EVENT_SOCKET; + } else + if (pc->fds[EVENT_MDNS].revents & POLLIN) { + mdns_handler(pc); + } else + if (pc->fds[EVENT_TIMER].revents & POLLIN) { + event = EVENT_TIMER; + } + + goto _loop; + + + return NULL; +}; + +urtc_peerconn_t * urtc_peerconn_create(const char *stun[]) { + // seend pseudorandom number generator + prng_init(); + + // allocate peer connection + struct peerconn *pc = (struct peerconn *)calloc(1, sizeof(struct peerconn)); + if (!pc) return pc; + + // copy pointer to stun servers + pc->stun = stun; + + // open udp socket (for all dtls/srtp/srtcp/stun/turn communication) + pc->sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (-1 == pc->sockfd) goto _fail_socket; + pc->fds[EVENT_SOCKET] = (struct pollfd){ pc->sockfd, POLLIN }; + + // generate a unique local mDNS hostname + uuid_create_str(pc->mdns.hostname); + log(INFO, "mDNS hostname is %s.local", pc->mdns.hostname); + + // open multicast udp socket for replying to mDNS queries + pc->mdns.sockfd = mdns_subscribe(); + if (-1 == pc->mdns.sockfd) goto _fail_mdns_subscribe; + pc->fds[EVENT_MDNS] = (struct pollfd){ + pc->mdns.sockfd, + POLLIN + }; + + // start new thread per peer connection + if (0 != pthread_create( + &(pc->thread), + NULL, + runloop, + pc + )) goto _fail_pthread_create; + + return pc; + +_fail_pthread_create: + mdns_unsubscribe(pc->mdns.sockfd); +_fail_mdns_subscribe: + close(pc->sockfd); +_fail_socket: + free(pc); + + return NULL; +} + +int urtc_set_on_ice_candidate(struct peerconn *pc, urtc_on_ice_candidate *cb) { + if (!pc) return -1; + if (!cb) return -1; + pc->on_ice_candidate = cb; + + return -URTC_ERR_NOT_IMPLEMENTED; +} + +int urtc_add_ice_candidate(struct peerconn *pc, const char *cand) { + return -URTC_ERR_NOT_IMPLEMENTED; +} + +int urtc_create_answer(struct peerconn *pc, char **answer) { + return -URTC_ERR_NOT_IMPLEMENTED; +} + +int urtc_create_offer(struct peerconn *pc, char **offer) { + return -URTC_ERR_NOT_IMPLEMENTED; +} + +int urtc_set_remote_description(struct peerconn *pc, const char *desc) { + return -URTC_ERR_NOT_IMPLEMENTED; +} + +int urtc_set_local_description(struct peerconn *pc, const char *desc) { + return -URTC_ERR_NOT_IMPLEMENTED; +} + +void urtc_peerconn_destroy(struct peerconn *pc) { + if (pc) { + pthread_cancel(pc->thread); + pthread_join(pc->thread, NULL); + mdns_unsubscribe(pc->mdns.sockfd); + shutdown(pc->sockfd, SHUT_RDWR); + close(pc->sockfd); + free(pc); + } +} -- cgit v1.2.3