/* Endpoint and call process handling * * (C) 2019 by Andreas Eversberg * All Rights Reserved * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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, see . */ #include #include #include #include #include #include #include "../libtimer/timer.h" #include "../libdebug/debug.h" #include "endpoint.h" osmo_cc_endpoint_t *osmo_cc_endpoint_list = NULL; static osmo_cc_call_t *call_new(osmo_cc_endpoint_t *ep, uint32_t callref) { osmo_cc_call_t *call, **cp; call = calloc(1, sizeof(*call)); if (!call) { PDEBUG(DCC, DEBUG_ERROR, "No memory for call process instance.\n"); abort(); } PDEBUG(DCC, DEBUG_DEBUG, "Creating new call with callref %u.\n", callref); call->ep = ep; call->callref = callref; /* attach to call process list */ cp = &ep->call_list; while (*cp) cp = &((*cp)->next); *cp = call; /* return new entry */ return call; } static void call_delete(osmo_cc_call_t *call) { osmo_cc_call_t **cp; PDEBUG(DCC, DEBUG_DEBUG, "Destroying call with callref %u.\n", call->callref); /* detach from call process list */ cp = &call->ep->call_list; while (*cp != call) cp = &((*cp)->next); *cp = call->next; /* flush message queue */ while (call->sock_queue) { osmo_cc_msg_t *msg = osmo_cc_msg_list_dequeue(&call->sock_queue, NULL); osmo_cc_free_msg(msg); } /* free remote peer */ free((char *)call->attached_name); free((char *)call->attached_host); free(call); } static const char *state_names[] = { "IDLE", "INIT-OUT", "INIT-IN", "OVERLAP-OUT", "OVERLAP-IN", "PROCEEDING-OUT", "PROCEEDING-IN", "ALERTING-OUT", "ALERTING-IN", "CONNECTING-OUT", "CONNECTING-IN", "ACTIVE", "DISCONNECTING-OUT", "DISCONNECTING-IN", "DISCONNECT-COLLISION", "RELEASING-OUT", "ATTACH-SENT", "ATTACH-OUT", "ATTACH-WAIT", "ATTACH-IN", }; static void new_call_state(osmo_cc_call_t *call, enum osmo_cc_state new_state) { PDEBUG(DCC, DEBUG_DEBUG, "Changing call state with callref %u from %s to %s.\n", call->callref, state_names[call->state], state_names[new_state]); call->state = new_state; } /* helper to forward message to lower layer */ static void forward_to_ll(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { if (call->lower_layer_released) return; if (msg->type == OSMO_CC_MSG_SETUP_REQ || msg->type == OSMO_CC_MSG_SETUP_RSP) { /* screen towards lower layer */ msg = osmo_cc_screen_msg(call->ep, msg, 0, NULL); } osmo_cc_msg_list_enqueue(&call->ep->ll_queue, msg, call->callref); } static void sock_reject_msg(osmo_cc_socket_t *os, uint32_t callref, uint8_t location, uint8_t socket_cause, uint8_t isdn_cause, uint16_t sip_cause) { osmo_cc_msg_t *msg; /* create message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND); /* add cause */ osmo_cc_add_ie_cause(msg, location, isdn_cause, sip_cause, socket_cause); osmo_cc_convert_cause_msg(msg); /* message to socket */ osmo_cc_sock_send_msg(os, callref, msg, NULL, 0); } static void ll_reject_msg(osmo_cc_call_t *call, uint8_t location, uint8_t socket_cause, uint8_t isdn_cause, uint16_t sip_cause) { osmo_cc_msg_t *msg; /* create message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_REQ); /* add cause */ osmo_cc_add_ie_cause(msg, location, isdn_cause, sip_cause, socket_cause); osmo_cc_convert_cause_msg(msg); /* message to lower layer */ forward_to_ll(call, msg); } static int split_address(const char *address, const char **host_p, uint16_t *port_p) { const char *portstring; *host_p = osmo_cc_host_of_address(address); if (!(*host_p)) { PDEBUG(DCC, DEBUG_ERROR, "Host IP in given address '%s' is invalid.\n", address); return -EINVAL; } portstring = osmo_cc_port_of_address(address); if (!portstring) { PDEBUG(DCC, DEBUG_ERROR, "Port number in given address '%s' is not specified or invalid.\n", address); return -EINVAL; } *port_p = atoi(portstring); return 0; } /* helper to forward message to upper layer */ static void forward_to_ul(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { const char *address = NULL, *host = NULL; uint16_t port; int rc; if (call->upper_layer_released) return; if (msg->type == OSMO_CC_MSG_SETUP_IND || msg->type == OSMO_CC_MSG_SETUP_CNF) { /* screen towards upper layer */ msg = osmo_cc_screen_msg(call->ep, msg, 1, &address); } /* no socket: forward message to upper layer */ if (call->ep->ul_msg_cb) { call->ep->ul_msg_cb(call, msg); return; } /* if remote peer is included in the setup message */ if (address && msg->type == OSMO_CC_MSG_SETUP_IND) { rc = split_address(address, &host, &port); if (rc < 0) { PDEBUG(DCC, DEBUG_ERROR, "Given remote peer's address '%s' in setup message is invalid, rejecting call.\n", address); reject: /* reject, due to error */ osmo_cc_free_msg(msg); new_call_state(call, OSMO_CC_STATE_IDLE); ll_reject_msg(call, call->ep->serving_location, 0, OSMO_CC_ISDN_CAUSE_DEST_OOO, 0); call_delete(call); return; } PDEBUG(DCC, DEBUG_DEBUG, "Using host IP '%s' and port '%d' from setup message.\n", host, port); } /* for attach message, use remote peer */ if (msg->type == OSMO_CC_MSG_ATTACH_IND) { host = call->ep->remote_host; port = call->ep->remote_port; PDEBUG(DCC, DEBUG_DEBUG, "Using host IP '%s' and port '%d' from remote address for attach message.\n", host, port); } /* if there is no remote peer in the setup message, use remote peer */ if (!address && msg->type == OSMO_CC_MSG_SETUP_IND && call->ep->remote_host) { host = call->ep->remote_host; port = call->ep->remote_port; PDEBUG(DCC, DEBUG_DEBUG, "Using host IP '%s' and port '%d' from remote address for setup message.\n", host, port); } /* if there is no remote peer set, try to use the interface name */ if (!host && msg->type == OSMO_CC_MSG_SETUP_IND) { char interface[256]; osmo_cc_call_t *att; rc = osmo_cc_get_ie_called_interface(msg, 0, interface, sizeof(interface)); if (rc < 0) interface[0] = '\0'; /* check for incoming attachment */ for (att = call->ep->call_list; att; att = att->next) { if (att->state != OSMO_CC_STATE_ATTACH_IN) continue; /* no interface given, just use the attached peer */ if (!interface[0]) break; /* no interface name given on attached peer, ignore it */ if (!att->attached_name || !att->attached_name[0]) continue; /* interface given, use the attached peer with the same interface name */ if (!strcmp(interface, att->attached_name)) break; } if (!att && !interface[0]) { PDEBUG(DCC, DEBUG_ERROR, "No remote peer attached, rejecting call.\n"); goto reject; } if (!att) { PDEBUG(DCC, DEBUG_ERROR, "No remote peer attached for given interface '%s', rejecting call.\n", interface); goto reject; } host = att->attached_host; port = att->attached_port; PDEBUG(DCC, DEBUG_DEBUG, "Using host IP '%s' and port '%d' from attached peer for setup message.\n", host, port); } /* add local interface name to setup message */ // FIXME: should we do that if there is already an interface name given? if (msg->type == OSMO_CC_MSG_SETUP_IND && call->ep->local_name) osmo_cc_add_ie_calling_interface(msg, call->ep->local_name); /* forward message to socket */ osmo_cc_sock_send_msg(&call->ep->os, call->callref, msg, host, port); } /* send attach indication to socket */ void send_attach_ind(struct timer *timer) { osmo_cc_endpoint_t *ep = (osmo_cc_endpoint_t *)timer->priv; osmo_cc_call_t *call; osmo_cc_msg_t *msg; PDEBUG(DCC, DEBUG_DEBUG, "Trying to attach to remote peer \"%s\".\n", ep->remote_host); /* create new call for attachment */ call = osmo_cc_call_new(ep); /* create attach message */ msg = osmo_cc_new_msg(OSMO_CC_MSG_ATTACH_IND); /* set interface name and address */ osmo_cc_add_ie_calling_interface(msg, ep->local_name); osmo_cc_add_ie_socket_address(msg, ep->local_address); /* message to socket */ forward_to_ul(call, msg); /* set state */ new_call_state(call, OSMO_CC_STATE_ATTACH_SENT); } void attach_rsp(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { PDEBUG(DCC, DEBUG_INFO, "Attached to remote peer \"%s\".\n", call->ep->remote_address); /* set state */ new_call_state(call, OSMO_CC_STATE_ATTACH_OUT); /* drop message */ osmo_cc_free_msg(msg); } void attach_rel(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* (re-)start timer for next attachment */ if (call->state == OSMO_CC_STATE_ATTACH_SENT || call->state == OSMO_CC_STATE_ATTACH_OUT) { timer_start(&call->ep->attach_timer, OSMO_CC_ATTACH_TIMER); PDEBUG(DCC, DEBUG_INFO, "Attachment to remote peer \"%s\" failed, retrying.\n", call->ep->remote_address); } if (call->attached_name) PDEBUG(DCC, DEBUG_INFO, "Peer with remote interface \"%s\" detached from us.\n", call->attached_name); /* change state */ new_call_state(call, OSMO_CC_STATE_IDLE); /* unset interface */ free((char *)call->attached_name); call->attached_name = NULL; free((char *)call->attached_host); call->attached_host = NULL; /* drop message */ osmo_cc_free_msg(msg); /* destroy */ call_delete(call); } void attach_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { char address[256]; char interface[256]; const char *host; uint16_t port; int rc; /* get peer from message */ rc = osmo_cc_get_ie_socket_address(msg, 0, address, sizeof(address)); if (rc < 0) address[0] = '\0'; if (!address[0]) { PDEBUG(DCC, DEBUG_ERROR, "Attachment request from remote peer has no remote address set, rejecting.\n"); rel: /* change to REL_REQ */ msg->type = OSMO_CC_MSG_REL_IND; PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); /* message to socket */ forward_to_ul(call, msg); /* destroy */ call_delete(call); return; } rc = split_address(address, &host, &port); if (rc < 0) { PDEBUG(DCC, DEBUG_ERROR, "Given remote peer's address '%s' in attach message is invalid, rejecting call.\n", address); goto rel; } free((char *)call->attached_host); call->attached_host = strdup(host); call->attached_port = port; rc = osmo_cc_get_ie_calling_interface(msg, 0, interface, sizeof(interface)); if (rc < 0) interface[0] = '\0'; if (interface[0]) { free((char *)call->attached_name); call->attached_name = strdup(interface); } PDEBUG(DCC, DEBUG_INFO, "Remote peer with socket address '%s' and port '%d' and interface '%s' attached to us.\n", call->attached_host, call->attached_port, call->attached_name); /* changing to confirm message */ msg->type = OSMO_CC_MSG_ATTACH_CNF; PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); /* message to socket */ forward_to_ul(call, msg); /* set state */ new_call_state(call, OSMO_CC_STATE_ATTACH_IN); } static void setup_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_INIT_OUT); /* to lower layer */ forward_to_ll(call, msg); } static void setup_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_INIT_IN); /* to upper layer */ forward_to_ul(call, msg); } static void rej_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_IDLE); /* to lower layer */ forward_to_ll(call, msg); /* destroy */ call_delete(call); } static void rej_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_IDLE); /* to upper layer */ forward_to_ul(call, msg); /* destroy */ call_delete(call); } static void setup_ack_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_OVERLAP_IN); /* to lower layer */ forward_to_ll(call, msg); } static void setup_ack_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_OVERLAP_OUT); /* to upper layer */ forward_to_ul(call, msg); } static void proc_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_PROCEEDING_IN); /* to lower layer */ forward_to_ll(call, msg); } static void proc_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_PROCEEDING_OUT); /* to upper layer */ forward_to_ul(call, msg); } static void alert_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_ALERTING_IN); /* to lower layer */ forward_to_ll(call, msg); } static void alert_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_ALERTING_OUT); /* to upper layer */ forward_to_ul(call, msg); } static void setup_rsp(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_CONNECTING_IN); /* to lower layer */ forward_to_ll(call, msg); } static void setup_cnf(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_CONNECTING_OUT); /* to upper layer */ forward_to_ul(call, msg); } static void setup_comp_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_ACTIVE); /* to lower layer */ forward_to_ll(call, msg); } static void setup_comp_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_ACTIVE); /* to upper layer */ forward_to_ul(call, msg); } static void info_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* to lower layer */ forward_to_ll(call, msg); } static void info_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* to upper layer */ forward_to_ul(call, msg); } static void progress_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* to lower layer */ forward_to_ll(call, msg); } static void progress_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* to upper layer */ forward_to_ul(call, msg); } static void notify_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* to lower layer */ forward_to_ll(call, msg); } static void notify_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* to upper layer */ forward_to_ul(call, msg); } static void disc_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_DISCONNECTING_OUT); /* to lower layer */ forward_to_ll(call, msg); } static void disc_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_DISCONNECTING_IN); /* to upper layer */ forward_to_ul(call, msg); } static void rel_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* terminate process, if there is no lower layer anmore */ if (call->lower_layer_released) { /* change state */ new_call_state(call, OSMO_CC_STATE_IDLE); /* drop message */ osmo_cc_free_msg(msg); /* destroy */ call_delete(call); return; } /* change state */ new_call_state(call, OSMO_CC_STATE_RELEASING_OUT); /* to lower layer */ forward_to_ll(call, msg); } static void rel_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_IDLE); /* to upper layer */ forward_to_ul(call, msg); /* destroy */ call_delete(call); } static void rel_cnf(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_IDLE); /* drop message */ osmo_cc_free_msg(msg); /* destroy */ call_delete(call); } static void disc_collision_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* release to lower layer wheen there is no upper layer */ if (call->upper_layer_released) { /* change state */ new_call_state(call, OSMO_CC_STATE_RELEASING_OUT); /* change to REL_REQ */ msg->type = OSMO_CC_MSG_REL_REQ; PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); /* to lower layer */ forward_to_ll(call, msg); return; } /* change state */ new_call_state(call, OSMO_CC_STATE_DISC_COLLISION); /* to upper layer */ forward_to_ul(call, msg); } static void disc_collision_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* release to upper layer wheen there is no lower layer */ if (call->lower_layer_released) { /* change to REL_REQ */ msg->type = OSMO_CC_MSG_REL_IND; PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); /* to upper layer */ forward_to_ul(call, msg); /* destroy */ call_delete(call); return; } /* change state */ new_call_state(call, OSMO_CC_STATE_DISC_COLLISION); /* to lower layer */ forward_to_ll(call, msg); } static void rel_collision(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ if (call->state != OSMO_CC_STATE_IDLE) new_call_state(call, OSMO_CC_STATE_IDLE); /* drop message */ osmo_cc_free_msg(msg); /* destroy */ call_delete(call); } static void rej_ind_disc(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_IDLE); /* change to REL_IND */ msg->type = OSMO_CC_MSG_REL_IND; PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); /* to upper layer */ forward_to_ul(call, msg); /* destroy */ call_delete(call); } static void rej_req_disc(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { /* change state */ new_call_state(call, OSMO_CC_STATE_IDLE); /* change to REL_REQ */ msg->type = OSMO_CC_MSG_REL_REQ; PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); /* to lower layer */ forward_to_ll(call, msg); /* destroy */ call_delete(call); } static void rel_ind_other(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { // FIXME: does this event really happens in this state? // just to be safe we handle it /* if thereis no upper layer, we are done */ if (call->upper_layer_released) { /* drop message */ osmo_cc_free_msg(msg); /* destroy */ call_delete(call); return; } /* change state */ new_call_state(call, OSMO_CC_STATE_DISCONNECTING_IN); /* change to DISC_IND */ msg->type = OSMO_CC_MSG_DISC_IND; PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); call->lower_layer_released = 1; /* to upper layer */ forward_to_ul(call, msg); } static void rel_req_other(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { // FIXME: does this event really happens in this state? // just to be safe we handle it /* if thereis no lower layer, we are done */ if (call->lower_layer_released) { /* drop message */ osmo_cc_free_msg(msg); /* destroy */ call_delete(call); return; } /* change state */ new_call_state(call, OSMO_CC_STATE_DISCONNECTING_OUT); /* change to DISC_REQ */ msg->type = OSMO_CC_MSG_DISC_REQ; PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); call->upper_layer_released = 1; /* to lower layer */ forward_to_ll(call, msg); } #define SBIT(a) (1 << a) #define ALL_STATES (~0) static struct statemachine { uint32_t states; int type; void (*action)(osmo_cc_call_t *call, osmo_cc_msg_t *msg); } statemachine_list[] = { /* attachment states */ {SBIT(OSMO_CC_STATE_ATTACH_SENT), OSMO_CC_MSG_ATTACH_RSP, attach_rsp}, {SBIT(OSMO_CC_STATE_ATTACH_OUT) | SBIT(OSMO_CC_STATE_ATTACH_SENT), OSMO_CC_MSG_REL_REQ, attach_rel}, {SBIT(OSMO_CC_STATE_IDLE), OSMO_CC_MSG_ATTACH_REQ, attach_req}, {SBIT(OSMO_CC_STATE_ATTACH_IN), OSMO_CC_MSG_REL_REQ, attach_rel}, /* call setup toward lower layer protocol */ {SBIT(OSMO_CC_STATE_IDLE), OSMO_CC_MSG_SETUP_REQ, setup_req}, {SBIT(OSMO_CC_STATE_INIT_OUT), OSMO_CC_MSG_SETUP_ACK_IND, setup_ack_ind}, {SBIT(OSMO_CC_STATE_INIT_OUT) | SBIT(OSMO_CC_STATE_OVERLAP_OUT), OSMO_CC_MSG_PROC_IND, proc_ind}, {SBIT(OSMO_CC_STATE_INIT_OUT) | SBIT(OSMO_CC_STATE_OVERLAP_OUT) | SBIT(OSMO_CC_STATE_PROCEEDING_OUT), OSMO_CC_MSG_ALERT_IND, alert_ind}, {SBIT(OSMO_CC_STATE_INIT_OUT) | SBIT(OSMO_CC_STATE_OVERLAP_OUT) | SBIT(OSMO_CC_STATE_PROCEEDING_OUT) | SBIT(OSMO_CC_STATE_ALERTING_OUT), OSMO_CC_MSG_SETUP_CNF, setup_cnf}, {SBIT(OSMO_CC_STATE_OVERLAP_OUT) | SBIT(OSMO_CC_STATE_PROCEEDING_OUT) | SBIT(OSMO_CC_STATE_ALERTING_OUT), OSMO_CC_MSG_PROGRESS_IND, progress_ind}, {SBIT(OSMO_CC_STATE_OVERLAP_OUT), OSMO_CC_MSG_INFO_REQ, info_req}, {SBIT(OSMO_CC_STATE_PROCEEDING_OUT) | SBIT(OSMO_CC_STATE_ALERTING_OUT), OSMO_CC_MSG_NOTIFY_IND, notify_ind}, {SBIT(OSMO_CC_STATE_CONNECTING_OUT), OSMO_CC_MSG_SETUP_COMP_REQ, setup_comp_req}, /* call setup from lower layer protocol */ {SBIT(OSMO_CC_STATE_IDLE), OSMO_CC_MSG_SETUP_IND, setup_ind}, {SBIT(OSMO_CC_STATE_INIT_IN), OSMO_CC_MSG_SETUP_ACK_REQ, setup_ack_req}, {SBIT(OSMO_CC_STATE_INIT_IN) | SBIT(OSMO_CC_STATE_OVERLAP_IN), OSMO_CC_MSG_PROC_REQ, proc_req}, {SBIT(OSMO_CC_STATE_INIT_IN) | SBIT(OSMO_CC_STATE_OVERLAP_IN) | SBIT(OSMO_CC_STATE_PROCEEDING_IN), OSMO_CC_MSG_ALERT_REQ, alert_req}, {SBIT(OSMO_CC_STATE_INIT_IN) | SBIT(OSMO_CC_STATE_OVERLAP_IN) | SBIT(OSMO_CC_STATE_PROCEEDING_IN) | SBIT(OSMO_CC_STATE_ALERTING_IN), OSMO_CC_MSG_SETUP_RSP, setup_rsp}, {SBIT(OSMO_CC_STATE_OVERLAP_IN) | SBIT(OSMO_CC_STATE_PROCEEDING_IN) | SBIT(OSMO_CC_STATE_ALERTING_IN), OSMO_CC_MSG_PROGRESS_REQ, progress_req}, {SBIT(OSMO_CC_STATE_OVERLAP_IN), OSMO_CC_MSG_INFO_IND, info_ind}, {SBIT(OSMO_CC_STATE_PROCEEDING_IN) | SBIT(OSMO_CC_STATE_ALERTING_IN), OSMO_CC_MSG_NOTIFY_REQ, notify_req}, {SBIT(OSMO_CC_STATE_CONNECTING_IN), OSMO_CC_MSG_SETUP_COMP_IND, setup_comp_ind}, /* active state */ {SBIT(OSMO_CC_STATE_ACTIVE), OSMO_CC_MSG_NOTIFY_IND, notify_ind}, {SBIT(OSMO_CC_STATE_ACTIVE), OSMO_CC_MSG_NOTIFY_REQ, notify_req}, {SBIT(OSMO_CC_STATE_ACTIVE), OSMO_CC_MSG_INFO_IND, info_ind}, {SBIT(OSMO_CC_STATE_ACTIVE), OSMO_CC_MSG_INFO_REQ, info_req}, /* call release */ {SBIT(OSMO_CC_STATE_INIT_OUT) | SBIT(OSMO_CC_STATE_INIT_IN) | SBIT(OSMO_CC_STATE_OVERLAP_OUT) | SBIT(OSMO_CC_STATE_OVERLAP_IN) | SBIT(OSMO_CC_STATE_PROCEEDING_OUT) | SBIT(OSMO_CC_STATE_PROCEEDING_IN) | SBIT(OSMO_CC_STATE_ALERTING_OUT) | SBIT(OSMO_CC_STATE_ALERTING_IN) | SBIT(OSMO_CC_STATE_CONNECTING_OUT) | SBIT(OSMO_CC_STATE_CONNECTING_IN) | SBIT(OSMO_CC_STATE_ACTIVE), OSMO_CC_MSG_DISC_REQ, disc_req}, {SBIT(OSMO_CC_STATE_INIT_OUT) | SBIT(OSMO_CC_STATE_INIT_IN) | SBIT(OSMO_CC_STATE_OVERLAP_OUT) | SBIT(OSMO_CC_STATE_OVERLAP_IN) | SBIT(OSMO_CC_STATE_PROCEEDING_OUT) | SBIT(OSMO_CC_STATE_PROCEEDING_IN) | SBIT(OSMO_CC_STATE_ALERTING_OUT) | SBIT(OSMO_CC_STATE_ALERTING_IN) | SBIT(OSMO_CC_STATE_CONNECTING_OUT) | SBIT(OSMO_CC_STATE_CONNECTING_IN) | SBIT(OSMO_CC_STATE_ACTIVE), OSMO_CC_MSG_DISC_IND, disc_ind}, {SBIT(OSMO_CC_STATE_INIT_OUT), OSMO_CC_MSG_REJ_IND, rej_ind}, {SBIT(OSMO_CC_STATE_INIT_IN), OSMO_CC_MSG_REJ_REQ, rej_req}, {SBIT(OSMO_CC_STATE_DISCONNECTING_OUT), OSMO_CC_MSG_REL_IND, rel_ind}, {SBIT(OSMO_CC_STATE_DISCONNECTING_IN), OSMO_CC_MSG_REL_REQ, rel_req}, {SBIT(OSMO_CC_STATE_RELEASING_OUT), OSMO_CC_MSG_REL_CNF, rel_cnf}, /* race condition where disconnect is received after disconnecting (disconnect collision) */ {SBIT(OSMO_CC_STATE_DISCONNECTING_OUT), OSMO_CC_MSG_DISC_IND, disc_collision_ind}, {SBIT(OSMO_CC_STATE_DISCONNECTING_IN), OSMO_CC_MSG_DISC_REQ, disc_collision_req}, {SBIT(OSMO_CC_STATE_DISC_COLLISION), OSMO_CC_MSG_REL_IND, rel_ind}, {SBIT(OSMO_CC_STATE_DISC_COLLISION), OSMO_CC_MSG_REL_REQ, rel_req}, /* race condition where release is received after releasing (release collision) */ {SBIT(OSMO_CC_STATE_RELEASING_OUT), OSMO_CC_MSG_REL_IND, rel_collision}, {SBIT(OSMO_CC_STATE_IDLE), OSMO_CC_MSG_REL_REQ, rel_collision}, /* race condition where reject is received after disconnecting */ {SBIT(OSMO_CC_STATE_DISCONNECTING_OUT), OSMO_CC_MSG_REJ_IND, rej_ind_disc}, {SBIT(OSMO_CC_STATE_DISCONNECTING_IN), OSMO_CC_MSG_REJ_REQ, rej_req_disc}, /* turn release into disconnect, so release is possible in any state */ {ALL_STATES, OSMO_CC_MSG_REL_IND, rel_ind_other}, {ALL_STATES, OSMO_CC_MSG_REL_REQ, rel_req_other}, }; #define STATEMACHINE_LEN \ (sizeof(statemachine_list) / sizeof(struct statemachine)) static void handle_msg(osmo_cc_call_t *call, osmo_cc_msg_t *msg) { int i; /* Find function for current state and message */ for (i = 0; i < (int)STATEMACHINE_LEN; i++) if ((msg->type == statemachine_list[i].type) && ((1 << call->state) & statemachine_list[i].states)) break; if (i == STATEMACHINE_LEN) { PDEBUG(DCC, DEBUG_INFO, "Message %s unhandled at state %s (callref %d)\n", osmo_cc_msg_name(msg->type), state_names[call->state], call->callref); osmo_cc_free_msg(msg); return; } PDEBUG(DCC, DEBUG_INFO, "Handle message %s at state %s (callref %d)\n", osmo_cc_msg_name(msg->type), state_names[call->state], call->callref); statemachine_list[i].action(call, msg); } static int handle_call(osmo_cc_call_t *call) { /* may handle only one message, since call may be destroyed when handling */ if (call->sock_queue) { osmo_cc_msg_t *msg = osmo_cc_msg_list_dequeue(&call->sock_queue, NULL); handle_msg(call, msg); return 1; } return 0; } static int osmo_cc_handle_endpoint(osmo_cc_endpoint_t *ep) { int work = 0; uint32_t callref; osmo_cc_call_t *call; /* may handle only one message, since call may be destroyed when handling */ if (ep->ll_queue) { osmo_cc_msg_t *msg = osmo_cc_msg_list_dequeue(&ep->ll_queue, &callref); ep->ll_msg_cb(ep, callref, msg); work |= 1; } /* handle only one call, because it might have been removed */ for (call = ep->call_list; call; call = call->next) { work |= handle_call(call); if (work) break; } return work; } /* main handler * note that it must be called in a loop (with ohter handlers) until no work was done */ int osmo_cc_handle(void) { int work = 0; osmo_cc_endpoint_t *ep; for (ep = osmo_cc_endpoint_list; ep; ep = ep->next) { work |= osmo_cc_handle_endpoint(ep); work |= osmo_cc_handle_socket(&ep->os); } return work; } osmo_cc_call_t *osmo_cc_call_by_callref(osmo_cc_endpoint_t *ep, uint32_t callref) { osmo_cc_call_t *call; if (!callref) return NULL; for (call = ep->call_list; call; call = call->next) { if (call->callref == callref) { return call; } } return NULL; } void osmo_cc_ll_msg(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg) { osmo_cc_call_t *call; if (!(msg->type & 1)) { PDEBUG(DCC, DEBUG_ERROR, "Received message from lower layer that is not an _IND nor _CNF, please fix!\n"); osmo_cc_free_msg(msg); return; } call = osmo_cc_call_by_callref(ep, callref); if (call) { /* complete cause */ osmo_cc_convert_cause_msg(msg); handle_msg(call, msg); return; } /* if no ref exists */ } /* message from upper layer (socket) */ void osmo_cc_ul_msg(void *priv, uint32_t callref, osmo_cc_msg_t *msg) { osmo_cc_endpoint_t *ep = priv; osmo_cc_call_t *call; if ((msg->type & 1)) { PDEBUG(DCC, DEBUG_ERROR, "Received message from socket that is not an _REQ nor _RSP, please fix!\n"); osmo_cc_free_msg(msg); return; } call = osmo_cc_call_by_callref(ep, callref); if (call) { /* if we are not in INIT-IN state, we change a CC-REJ-REQ into CC-REL_REQ. * this happens, if the socket fails. */ if (call->state != OSMO_CC_STATE_INIT_IN && msg->type == OSMO_CC_MSG_REJ_REQ) msg->type = OSMO_CC_MSG_REL_REQ; osmo_cc_msg_list_enqueue(&call->sock_queue, msg, call->callref); return; } /* if no ref exists */ /* reject and release are ignored */ if (msg->type == OSMO_CC_MSG_REJ_REQ || msg->type == OSMO_CC_MSG_REL_REQ) { osmo_cc_free_msg(msg); return; } /* reject if not a setup/attach or release message */ if (msg->type != OSMO_CC_MSG_SETUP_REQ && msg->type != OSMO_CC_MSG_ATTACH_REQ) { sock_reject_msg(&ep->os, callref, ep->serving_location, 0, OSMO_CC_ISDN_CAUSE_INVAL_CALLREF, 0); osmo_cc_free_msg(msg); return; } /* create call instance with one socket reference */ call = call_new(ep, callref); osmo_cc_msg_list_enqueue(&call->sock_queue, msg, call->callref); } static void osmo_cc_help_address(void) { printf("Address options:\n\n"); printf("local :\n"); printf("local []:\n"); printf("remote :\n"); printf("remote []:\n\n"); printf("These options can be used to define local and remote IP and port for the socket\n"); printf("interface. Note that IPv6 adresses must be enclosed by '[' and ']'.\n\n"); printf("If no local address was given, the IPv4 loopback IP and port %d is used. If\n", OSMO_CC_DEFAULT_PORT); printf("this port is already in use, the first free higher port is used.\n\n"); printf("If no remote address is given, the local IP is used. If the local port is %d,\n", OSMO_CC_DEFAULT_PORT); printf("the remote port will be %d. If not, the remote port will be %d. This way it is\n", OSMO_CC_DEFAULT_PORT + 1, OSMO_CC_DEFAULT_PORT); printf("possible to link two interfaces without any IP configuration required.\n\n"); } static int osmo_cc_set_address(osmo_cc_endpoint_t *ep, const char *text) { const char **address_p, **host_p; uint16_t *port_p; int rc; if (!strncasecmp(text, "local", 5)) { text += 5; /* remove spaces after keyword */ while (*text) { if (*text > 32) break; text++; } address_p = &ep->local_address; host_p = &ep->local_host; port_p = &ep->local_port; } else if (!strncasecmp(text, "remote", 6)) { text += 6; /* remove spaces after keyword */ while (*text) { if (*text > 32) break; text++; } if (!strcasecmp(text, "auto")) { PDEBUG(DCC, DEBUG_DEBUG, "setting automatic remote peer selection\n"); ep->remote_auto = 1; return 0; } ep->remote_auto = 0; address_p = &ep->remote_address; host_p = &ep->remote_host; port_p = &ep->remote_port; } else { PDEBUG(DCC, DEBUG_ERROR, "Invalid local or remote address definition '%s'\n", text); return -EINVAL; } if (*address_p) { free((char *)*address_p); *address_p = NULL; } if (*host_p) { free((char *)*host_p); *host_p = NULL; } rc = split_address(text, host_p, port_p); if (rc < 0) { /* unset, so that this is not treated with free() */ *host_p = NULL; return rc; } *address_p = strdup(text); *host_p = strdup(*host_p); return 0; } static void osmo_cc_help_rtp(void) { printf("RTP options:\n\n"); printf("rtp-peer \n"); printf("rtp-peer \n"); printf("rtp-ports \n\n"); printf("These options can be used to alter the local IP and port range for RTP traffic.\n"); printf("By default the local IPv4 loopback address is used. To connect interfaces\n"); printf("between machines, local machine's IP must be given.\n\n"); } static int osmo_cc_set_rtp(const char *text) { int peer = 0, ports = 0; if (!strncasecmp(text, "rtp-peer", 8)) { text += 8; peer = 1; } else if (!strncasecmp(text, "rtp-ports", 9)) { text += 9; ports = 1; } else { PDEBUG(DCC, DEBUG_ERROR, "Invalid RTP definition '%s'\n", text); return -EINVAL; } /* remove spaces after keyword */ while (*text) { if (*text > 32) break; text++; } if (peer) { enum osmo_cc_session_addrtype addrtype; addrtype = osmo_cc_address_type(text); if (addrtype == osmo_cc_session_addrtype_unknown) { PDEBUG(DCC, DEBUG_ERROR, "Given RTP address '%s' is invalid.\n", text); return -EINVAL; } osmo_cc_set_local_peer(osmo_cc_session_nettype_inet, addrtype, text); return 0; } if (ports) { int from = 0, to = 0; /* from port */ while (*text > ' ') { if (*text < '0' || *text > '9') { PDEBUG(DCC, DEBUG_ERROR, "Given 'from' port in '%s' is invalid.\n", text); return -EINVAL; } from = from * 10 + *text - '0'; } /* remove spaces after keyword */ while (*text) { if (*text > 32) break; text++; } /* to port */ while (*text > ' ') { if (*text < '0' || *text > '9') { PDEBUG(DCC, DEBUG_ERROR, "Given 'to' port in '%s' is invalid.\n", text); return -EINVAL; } from = from * 10 + *text - '0'; } osmo_cc_set_rtp_ports(from, to); return 0; } return -EINVAL; } void osmo_cc_help(void) { osmo_cc_help_screen(); osmo_cc_help_address(); osmo_cc_help_rtp(); } /* create a new endpoint instance */ int osmo_cc_new(osmo_cc_endpoint_t *ep, const char *version, const char *name, uint8_t serving_location, void (*ll_msg_cb)(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg), void (*ul_msg_cb)(osmo_cc_call_t *call, osmo_cc_msg_t *msg), void *priv, int argc, const char *argv[]) { osmo_cc_endpoint_t **epp; int rc; int i; PDEBUG(DCC, DEBUG_DEBUG, "Creating new endpoint instance.\n"); if (!!strcmp(version, OSMO_CC_VERSION)) { PDEBUG(DCC, DEBUG_ERROR, "Application was compiled for different Osmo-CC version.\n"); return OSMO_CC_RC_VERSION_MISMATCH; } memset(ep, 0, sizeof(*ep)); /* attach to list */ epp = &osmo_cc_endpoint_list; while (*epp) epp = &((*epp)->next); *epp = ep; if (name) ep->local_name = strdup(name); ep->ll_msg_cb = ll_msg_cb; ep->ul_msg_cb = ul_msg_cb; ep->serving_location = serving_location; ep->priv = priv; /* apply args */ for (i = 0; i < argc; i++) { if (!strncasecmp(argv[i], "local", 5)) { rc = osmo_cc_set_address(ep, argv[i]); if (rc < 0) { return rc; } } else if (!strncasecmp(argv[i], "remote", 6)) { rc = osmo_cc_set_address(ep, argv[i]); if (rc < 0) { return rc; } } else if (!strncasecmp(argv[i], "rtp", 3)) { rc = osmo_cc_set_rtp(argv[i]); if (rc < 0) { return rc; } } else if (!strncasecmp(argv[i], "screen", 6)) { rc = osmo_cc_add_screen(ep, argv[i]); if (rc < 0) { return rc; } } else { PDEBUG(DCC, DEBUG_ERROR, "Unknown osmo-cc argument \"%s\"\n", argv[i]); return -EINVAL; } } /* open socket */ if (!ul_msg_cb) { char address[256]; const char *host; uint16_t port; enum osmo_cc_session_addrtype addrtype; host = ep->local_host; port = ep->local_port; if (!host) { host = "127.0.0.1"; PDEBUG(DCC, DEBUG_DEBUG, "No local peer set, using default \"%s\"\n", host); } rc = osmo_cc_open_socket(&ep->os, host, port, ep, osmo_cc_ul_msg, serving_location); if (rc < 0) { return rc; } port = rc; if (!ep->local_host) { ep->local_host = strdup(host); /* create address string */ addrtype = osmo_cc_address_type(host); if (addrtype == osmo_cc_session_addrtype_ipv6) sprintf(address, "[%s]:%d", host, port); else sprintf(address, "%s:%d", host, port); ep->local_address = strdup(address); } ep->local_port = port; /* auto configure */ if (ep->remote_auto) { free((char *)ep->remote_host); ep->remote_host = strdup(ep->local_host); PDEBUG(DCC, DEBUG_DEBUG, "Remote peer set to auto, using local peer's host \"%s\" for remote peer.\n", ep->remote_host); if (rc == OSMO_CC_DEFAULT_PORT) ep->remote_port = OSMO_CC_DEFAULT_PORT + 1; else ep->remote_port = OSMO_CC_DEFAULT_PORT; PDEBUG(DCC, DEBUG_DEBUG, " -> Using remote port %d.\n", ep->remote_port); /* create address string */ free((char *)ep->remote_address); addrtype = osmo_cc_address_type(ep->remote_host); if (addrtype == osmo_cc_session_addrtype_ipv6) sprintf(address, "[%s]:%d", ep->remote_host, ep->remote_port); else sprintf(address, "%s:%d", ep->remote_host, ep->remote_port); ep->remote_address = strdup(address); } /* attach to remote host */ timer_init(&ep->attach_timer, send_attach_ind, ep); if (ep->remote_host) { send_attach_ind(&ep->attach_timer); } } return 0; } /* destroy an endpoint instance */ void osmo_cc_delete(osmo_cc_endpoint_t *ep) { osmo_cc_endpoint_t **epp; PDEBUG(DCC, DEBUG_DEBUG, "Destroying endpoint instance.\n"); /* detach from list >*/ epp = &osmo_cc_endpoint_list; while (*epp && *epp != ep) epp = &((*epp)->next); if (*epp) *epp = ep->next; /* remove timer */ timer_exit(&ep->attach_timer); /* flush screen lists */ osmo_cc_flush_screen(ep->screen_calling_in); osmo_cc_flush_screen(ep->screen_called_in); osmo_cc_flush_screen(ep->screen_calling_out); osmo_cc_flush_screen(ep->screen_called_out); /* free local and remote peer */ free((char *)ep->local_name); free((char *)ep->local_address); free((char *)ep->local_host); free((char *)ep->remote_address); free((char *)ep->remote_host); /* destroying all child callesses (calls) */ while(ep->call_list) call_delete(ep->call_list); /* flush message queue */ while(ep->ll_queue) { osmo_cc_msg_t *msg = osmo_cc_msg_list_dequeue(&ep->ll_queue, NULL); osmo_cc_free_msg(msg); } /* remove socket */ osmo_cc_close_socket(&ep->os); memset(ep, 0, sizeof(*ep)); } /* create new call instance */ osmo_cc_call_t *osmo_cc_call_new(osmo_cc_endpoint_t *ep) { return call_new(ep, osmo_cc_new_callref()); } /* destroy call instance */ void osmo_cc_call_delete(osmo_cc_call_t *call) { call_delete(call); } /* check valid IP and return address type (protocol) */ enum osmo_cc_session_addrtype osmo_cc_address_type(const char *address) { struct sockaddr_storage sa; int rc; rc = inet_pton(AF_INET, address, &sa); if (rc > 0) return osmo_cc_session_addrtype_ipv4; rc = inet_pton(AF_INET6, address, &sa); if (rc > 0) return osmo_cc_session_addrtype_ipv6; return osmo_cc_session_addrtype_unknown; } /* get host from address */ const char *osmo_cc_host_of_address(const char *address) { static char host[256]; char *p; if (strlen(address) >= sizeof(host)) { PDEBUG(DCC, DEBUG_ERROR, "String way too long!\n"); return NULL; } if (address[0] == '[' && (p = strchr(address, ']'))) { memcpy(host, address + 1, p - address - 1); host[p - address - 1] = '\0'; return host; } strcpy(host, address); if ((p = strchr(host, ':'))) *p = '\0'; return host; } /* get port from address */ const char *osmo_cc_port_of_address(const char *address) { const char *p; int i; if (address[0] == '[' && (p = strchr(address, ']'))) address = p + 1; if (!(p = strchr(address, ':'))) return NULL; p++; /* check for zero */ if (p[0] == '0') return NULL; /* check for digits */ for (i = 0; i < (int)strlen(p); i++) { if (p[i] < '0' || p[i] > '9') return NULL; } /* check for magnitude */ if (atoi(p) > 65535) return NULL; return p; }