| /** |
| * @file icem.c ICE Media stream |
| * |
| * Copyright (C) 2010 Creytiv.com |
| */ |
| #include <re_types.h> |
| #include <re_fmt.h> |
| #include <re_mem.h> |
| #include <re_mbuf.h> |
| #include <re_list.h> |
| #include <re_tmr.h> |
| #include <re_sa.h> |
| #include <re_stun.h> |
| #include <re_ice.h> |
| #include <re_sys.h> |
| #include <re_trice.h> |
| #include "trice.h" |
| |
| |
| #define DEBUG_MODULE "icem" |
| #define DEBUG_LEVEL 5 |
| #include <re_dbg.h> |
| |
| |
| static const struct trice_conf conf_default = { |
| ICE_NOMINATION_REGULAR, |
| false, |
| false, |
| false, |
| true, |
| false |
| }; |
| |
| |
| static void trice_destructor(void *data) |
| { |
| struct trice *icem = data; |
| |
| mem_deref(icem->checklist); |
| |
| list_flush(&icem->validl); |
| list_flush(&icem->checkl); |
| list_flush(&icem->lcandl); |
| list_flush(&icem->rcandl); |
| list_flush(&icem->reqbufl); |
| |
| list_flush(&icem->connl); |
| |
| mem_deref(icem->rufrag); |
| mem_deref(icem->rpwd); |
| mem_deref(icem->lufrag); |
| mem_deref(icem->lpwd); |
| mem_deref(icem->sw); |
| } |
| |
| |
| /** |
| * Allocate a new ICE Media object |
| * |
| * @param icemp Pointer to allocated ICE Media object |
| * @param conf ICE configuration |
| * @param role Local role |
| * @param lufrag Local username fragment |
| * @param lpwd Local password |
| * @param estabh Candidate pair established handler |
| * @param closeh Close / error handler |
| * @param arg Handler argument |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| |
| int trice_alloc(struct trice **icemp, const struct trice_conf *conf, |
| enum ice_role role, |
| const char *lufrag, const char *lpwd) |
| { |
| struct trice *icem; |
| int err = 0; |
| |
| if (!icemp || !lufrag || !lpwd) |
| return EINVAL; |
| |
| if (str_len(lufrag) < 4 || str_len(lpwd) < 22) { |
| DEBUG_WARNING("alloc: lufrag/lpwd is too short\n"); |
| return EINVAL; |
| } |
| |
| icem = mem_zalloc(sizeof(*icem), trice_destructor); |
| if (!icem) |
| return ENOMEM; |
| |
| icem->conf = conf ? *conf : conf_default; |
| list_init(&icem->reqbufl); |
| list_init(&icem->lcandl); |
| list_init(&icem->rcandl); |
| list_init(&icem->checkl); |
| list_init(&icem->validl); |
| |
| icem->lrole = role; |
| icem->tiebrk = rand_u64(); |
| |
| err |= str_dup(&icem->lufrag, lufrag); |
| err |= str_dup(&icem->lpwd, lpwd); |
| if (err) |
| goto out; |
| |
| out: |
| if (err) |
| mem_deref(icem); |
| else |
| *icemp = icem; |
| |
| return err; |
| } |
| |
| |
| int trice_set_remote_ufrag(struct trice *icem, const char *rufrag) |
| { |
| if (!icem || !rufrag) |
| return EINVAL; |
| |
| icem->rufrag = mem_deref(icem->rufrag); |
| return str_dup(&icem->rufrag, rufrag); |
| } |
| |
| |
| int trice_set_remote_pwd(struct trice *icem, const char *rpwd) |
| { |
| if (!icem || !rpwd) |
| return EINVAL; |
| |
| icem->rpwd = mem_deref(icem->rpwd); |
| |
| return str_dup(&icem->rpwd, rpwd); |
| } |
| |
| |
| int trice_set_software(struct trice *icem, const char *sw) |
| { |
| if (!icem) |
| return EINVAL; |
| |
| icem->sw = mem_deref(icem->sw); |
| |
| if (sw) |
| return str_dup(&icem->sw, sw); |
| |
| return 0; |
| } |
| |
| |
| struct trice_conf *trice_conf(struct trice *icem) |
| { |
| return icem ? &icem->conf : NULL; |
| } |
| |
| |
| /* note: call this ONCE AFTER role has been set */ |
| static void trice_create_candpairs(struct trice *icem) |
| { |
| struct list *lst; |
| struct le *le; |
| bool refresh_checklist = false; |
| int err; |
| |
| lst = &icem->lcandl; |
| for (le = list_head(lst); le; le = le->next) { |
| struct ice_lcand *lcand = le->data; |
| |
| /* pair this local-candidate with all existing |
| * remote-candidates */ |
| err = trice_candpair_with_local(icem, lcand); |
| if (err) { |
| DEBUG_WARNING("trice_candpair_with_local: %m\n", err); |
| } |
| else { |
| refresh_checklist = true; |
| } |
| } |
| |
| lst = &icem->rcandl; |
| for (le = list_head(lst); le; le = le->next) { |
| struct ice_rcand *rcand = le->data; |
| |
| /* pair this remote-candidate with all existing |
| * local-candidates */ |
| err = trice_candpair_with_remote(icem, rcand); |
| if (err) { |
| DEBUG_WARNING("trice_candpair_with_remote: %m\n", err); |
| } |
| else { |
| refresh_checklist = true; |
| } |
| } |
| |
| /* new pair -- refresh the checklist timer */ |
| if (refresh_checklist) |
| trice_checklist_refresh(icem); |
| } |
| |
| |
| /* note: call this AFTER role has been set AND candidate pairs |
| * have been created */ |
| static void trice_reqbuf_process(struct trice *icem) |
| { |
| struct le *le; |
| |
| le = list_head(&icem->reqbufl); |
| while (le) { |
| struct trice_reqbuf *reqbuf = le->data; |
| le = le->next; |
| |
| DEBUG_PRINTF("trice_reqbuf_process: Processing buffered " |
| "request\n"); |
| |
| (void)trice_stund_recv_role_set(icem, reqbuf->lcand, |
| reqbuf->sock, &reqbuf->src, reqbuf->req, |
| reqbuf->presz); |
| |
| mem_deref(reqbuf); |
| } |
| } |
| |
| |
| /** |
| * Set the local role to either CONTROLLING or CONTROLLED. |
| * Note: The role can be set multiple times. |
| * |
| * @param icem ICE Media object |
| * @param role New local role |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int trice_set_role(struct trice *trice, enum ice_role role) |
| { |
| bool refresh; |
| |
| if (!trice) |
| return EINVAL; |
| |
| /* Cannot change the role to unknown */ |
| if (role == ICE_ROLE_UNKNOWN) |
| return EINVAL; |
| |
| if (trice->lrole == role) |
| return 0; |
| |
| /* Cannot switch role manually once it has been set */ |
| if (trice->lrole == ICE_ROLE_UNKNOWN) |
| refresh = false; |
| else |
| refresh = true; |
| |
| trice->lrole = role; |
| |
| /* Create candidate pairs and process pending requests */ |
| if (refresh) { |
| trice_candpair_prio_order(&trice->checkl, |
| role == ICE_ROLE_CONTROLLING); |
| } |
| else { |
| trice_create_candpairs(trice); |
| } |
| |
| trice_reqbuf_process(trice); |
| |
| return 0; |
| } |
| |
| |
| /** |
| * Get the local role |
| * |
| * @param icem ICE Media object |
| * |
| * @return Local role |
| */ |
| enum ice_role trice_local_role(const struct trice *icem) |
| { |
| if (!icem) |
| return ICE_ROLE_UNKNOWN; |
| |
| return icem->lrole; |
| } |
| |
| |
| /** |
| * Print debug information for the ICE Media |
| * |
| * @param pf Print function for debug output |
| * @param icem ICE Media object |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int trice_debug(struct re_printf *pf, const struct trice *icem) |
| { |
| struct le *le; |
| int err = 0; |
| |
| if (!icem) |
| return 0; |
| |
| err |= re_hprintf(pf, "----- ICE Media <%p> -----\n", icem); |
| |
| err |= re_hprintf(pf, " local_role=%s\n", |
| ice_role2name(icem->lrole)); |
| err |= re_hprintf(pf, " local_ufrag=\"%s\" local_pwd=\"%s\"\n", |
| icem->lufrag, icem->lpwd); |
| |
| err |= re_hprintf(pf, " Local Candidates: %H", |
| trice_lcands_debug, &icem->lcandl); |
| err |= re_hprintf(pf, " Remote Candidates: %H", |
| trice_rcands_debug, &icem->rcandl); |
| err |= re_hprintf(pf, " Check list: "); |
| err |= trice_candpairs_debug(pf, icem->conf.ansi, &icem->checkl); |
| |
| err |= re_hprintf(pf, " Valid list: "); |
| err |= trice_candpairs_debug(pf, icem->conf.ansi, &icem->validl); |
| |
| err |= re_hprintf(pf, " Buffered STUN Requests: (%u)\n", |
| list_count(&icem->reqbufl)); |
| |
| if (icem->checklist) |
| err |= trice_checklist_debug(pf, icem->checklist); |
| |
| err |= re_hprintf(pf, " TCP Connections: (%u)\n", |
| list_count(&icem->connl)); |
| |
| for (le = list_head(&icem->connl); le; le = le->next) { |
| struct ice_tcpconn *conn = le->data; |
| |
| err |= re_hprintf(pf, " %H\n", |
| trice_conn_debug, conn); |
| } |
| |
| return err; |
| } |
| |
| |
| /** |
| * Get the list of Local Candidates (struct cand) |
| * |
| * @param icem ICE Media object |
| * |
| * @return List of Local Candidates |
| */ |
| struct list *trice_lcandl(const struct trice *icem) |
| { |
| return icem ? (struct list *)&icem->lcandl : NULL; |
| } |
| |
| |
| /** |
| * Get the list of Remote Candidates (struct cand) |
| * |
| * @param icem ICE Media object |
| * |
| * @return List of Remote Candidates |
| */ |
| struct list *trice_rcandl(const struct trice *icem) |
| { |
| return icem ? (struct list *)&icem->rcandl : NULL; |
| } |
| |
| |
| /** |
| * Get the checklist of Candidate Pairs |
| * |
| * @param icem ICE Media object |
| * |
| * @return Checklist (struct ice_candpair) |
| */ |
| struct list *trice_checkl(const struct trice *icem) |
| { |
| return icem ? (struct list *)&icem->checkl : NULL; |
| } |
| |
| |
| /** |
| * Get the list of valid Candidate Pairs |
| * |
| * @param icem ICE Media object |
| * |
| * @return Validlist (struct ice_candpair) |
| */ |
| struct list *trice_validl(const struct trice *icem) |
| { |
| return icem ? (struct list *)&icem->validl : NULL; |
| } |
| |
| |
| void trice_printf(struct trice *icem, const char *fmt, ...) |
| { |
| va_list ap; |
| |
| if (!icem || !icem->conf.debug) |
| return; |
| |
| va_start(ap, fmt); |
| (void)re_printf("%v", fmt, &ap); |
| va_end(ap); |
| } |
| |
| |
| void trice_tracef(struct trice *icem, int color, const char *fmt, ...) |
| { |
| va_list ap; |
| |
| if (!icem || !icem->conf.trace) |
| return; |
| |
| if (icem->conf.ansi && color) { |
| re_printf("\x1b[%dm", color); |
| } |
| |
| va_start(ap, fmt); |
| (void)re_printf("%v", fmt, &ap); |
| va_end(ap); |
| |
| if (icem->conf.ansi && color) { |
| re_printf("\x1b[;m"); |
| } |
| } |
| |
| |
| void trice_switch_local_role(struct trice *ice) |
| { |
| enum ice_role new_role; |
| |
| if (!ice) |
| return; |
| |
| switch (ice->lrole) { |
| |
| case ICE_ROLE_CONTROLLING: |
| new_role = ICE_ROLE_CONTROLLED; |
| break; |
| |
| case ICE_ROLE_CONTROLLED: |
| new_role = ICE_ROLE_CONTROLLING; |
| break; |
| |
| default: |
| DEBUG_WARNING("trice_switch_local_role: local role unknown\n"); |
| return; |
| } |
| |
| DEBUG_NOTICE("Switch local role from %s to %s\n", |
| ice_role2name(ice->lrole), ice_role2name(new_role)); |
| |
| ice->lrole = new_role; |
| |
| /* recompute pair priorities for all media streams */ |
| trice_candpair_prio_order(&ice->checkl, |
| ice->lrole == ICE_ROLE_CONTROLLING); |
| } |
| |
| |
| /* sock = [ struct udp_sock | struct tcp_conn ] */ |
| bool trice_stun_process(struct trice *icem, struct ice_lcand *lcand, |
| int proto, void *sock, const struct sa *src, |
| struct mbuf *mb) |
| { |
| struct stun_msg *msg = NULL; |
| struct stun_unknown_attr ua; |
| size_t start = mb->pos; |
| (void)proto; |
| |
| if (stun_msg_decode(&msg, mb, &ua)) { |
| return false; /* continue recv-processing */ |
| } |
| |
| if (STUN_METHOD_BINDING == stun_msg_method(msg)) { |
| |
| switch (stun_msg_class(msg)) { |
| |
| case STUN_CLASS_REQUEST: |
| (void)trice_stund_recv(icem, lcand, sock, |
| src, msg, start); |
| break; |
| |
| default: |
| if (icem->checklist) { |
| (void)stun_ctrans_recv(icem->checklist->stun, |
| msg, &ua); |
| } |
| else { |
| DEBUG_NOTICE("STUN resp from %J dropped" |
| " (no checklist)\n", |
| src); |
| } |
| break; |
| } |
| } |
| |
| mem_deref(msg); |
| |
| return true; |
| } |
| |
| |
| static void trice_reqbuf_destructor(void *data) |
| { |
| struct trice_reqbuf *reqbuf = data; |
| |
| list_unlink(&reqbuf->le); |
| |
| mem_deref(reqbuf->req); |
| mem_deref(reqbuf->sock); |
| mem_deref(reqbuf->lcand); |
| } |
| |
| |
| int trice_reqbuf_append(struct trice *icem, struct ice_lcand *lcand, |
| void *sock, const struct sa *src, |
| struct stun_msg *req, size_t presz) |
| { |
| struct trice_reqbuf *reqbuf; |
| |
| if (!icem || !src ||!req) |
| return EINVAL; |
| |
| reqbuf = mem_zalloc(sizeof(*reqbuf), trice_reqbuf_destructor); |
| if (!reqbuf) |
| return ENOMEM; |
| |
| DEBUG_PRINTF("trice_reqbuf_append: Buffering request\n"); |
| reqbuf->lcand = mem_ref(lcand); |
| reqbuf->sock = mem_ref(sock); |
| reqbuf->src = *src; |
| reqbuf->req = mem_ref(req); |
| reqbuf->presz = presz; |
| |
| list_append(&icem->reqbufl, &reqbuf->le, reqbuf); |
| |
| return 0; |
| } |
| |
| int trice_set_port_range(struct trice *trice, |
| uint16_t min_port, uint16_t max_port) |
| { |
| if (!trice) |
| return EINVAL; |
| |
| if (max_port < min_port) |
| return ERANGE; |
| |
| trice->ports.min = min_port; |
| trice->ports.max = max_port; |
| |
| return 0; |
| } |