diff options
Diffstat (limited to 'package/tapi_sip')
-rw-r--r-- | package/tapi_sip/Makefile | 41 | ||||
-rw-r--r-- | package/tapi_sip/files/telephony.conf | 28 | ||||
-rwxr-xr-x | package/tapi_sip/files/telephony.init | 50 | ||||
-rw-r--r-- | package/tapi_sip/src/Makefile | 21 | ||||
-rw-r--r-- | package/tapi_sip/src/agent.h | 41 | ||||
-rw-r--r-- | package/tapi_sip/src/contact.c | 162 | ||||
-rw-r--r-- | package/tapi_sip/src/contact.h | 33 | ||||
-rw-r--r-- | package/tapi_sip/src/dialdetector.c | 148 | ||||
-rw-r--r-- | package/tapi_sip/src/dialdetector.h | 49 | ||||
-rw-r--r-- | package/tapi_sip/src/list.h | 601 | ||||
-rw-r--r-- | package/tapi_sip/src/session.c | 84 | ||||
-rw-r--r-- | package/tapi_sip/src/session.h | 14 | ||||
-rw-r--r-- | package/tapi_sip/src/sip_agent.h | 27 | ||||
-rw-r--r-- | package/tapi_sip/src/sip_client.c | 800 | ||||
-rw-r--r-- | package/tapi_sip/src/sip_client.h | 61 | ||||
-rw-r--r-- | package/tapi_sip/src/stun.c | 243 | ||||
-rw-r--r-- | package/tapi_sip/src/stun.h | 12 | ||||
-rw-r--r-- | package/tapi_sip/src/tapi_agent.c | 105 | ||||
-rw-r--r-- | package/tapi_sip/src/tapi_agent.h | 37 | ||||
-rw-r--r-- | package/tapi_sip/src/tapi_sip.c | 179 |
20 files changed, 2736 insertions, 0 deletions
diff --git a/package/tapi_sip/Makefile b/package/tapi_sip/Makefile new file mode 100644 index 000000000..7a3d6766a --- /dev/null +++ b/package/tapi_sip/Makefile @@ -0,0 +1,41 @@ +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=tapi_sip +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/tapi_sip + SECTION:=utils + CATEGORY:=Utilities + TITLE:=tapi_sip + DEPENDS:=+libuci +libtapi +pjsip +kmod-lqtapi +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Build/Compile + CFLAGS="$(TARGET_CPPFLAGS) $(TARGET_CFLAGS)" \ + LDFLAGS="$(TARGET_LDFLAGS)" \ + $(MAKE) -C $(PKG_BUILD_DIR) \ + $(TARGET_CONFIGURE_OPTS) +endef + +define Package/tapi_sip/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/tapi-sip $(1)/usr/bin/ + + $(INSTALL_DIR) $(1)/etc/config $(1)/etc/init.d + $(INSTALL_DATA) ./files/telephony.conf $(1)/etc/config/telephony + $(INSTALL_BIN) ./files/telephony.init $(1)/etc/init.d/telephony +endef + +$(eval $(call BuildPackage,tapi_sip)) diff --git a/package/tapi_sip/files/telephony.conf b/package/tapi_sip/files/telephony.conf new file mode 100644 index 000000000..6d87f45a8 --- /dev/null +++ b/package/tapi_sip/files/telephony.conf @@ -0,0 +1,28 @@ +config 'config' 'config' +# option 'fw_url' 'http://192.168.1.100/danube_firmware.bin' + option 'fw_file' 'danube_firmware.bin' + option 'netdev' 'pppoe-wan' + option 'disable' '1' + +config 'account' 'account' + option 'realm' 'example.com' + option 'username' 'user' + option 'password' 'password' + option 'stun_host' 'stun.example.com' + option 'stun_port' '3478' + option 'sip_port' '5600' + +config 'contact' + option 'name' 'sip example' + option 'identifier' 'sip:user@example.net' + option 'number' '123' + +config 'contact' + option 'name' 'local1' + option 'identifier' 'tel:1' + option 'number' '01' + +config 'contact' + option 'name' 'local2' + option 'identifier' 'tel:2' + option 'number' '02' diff --git a/package/tapi_sip/files/telephony.init b/package/tapi_sip/files/telephony.init new file mode 100755 index 000000000..082024c23 --- /dev/null +++ b/package/tapi_sip/files/telephony.init @@ -0,0 +1,50 @@ +#!/bin/sh /etc/rc.common +START=80 + +download_fw() +{ + config_load telephony + config_get fw_url config fw_url + config_get fw_file config fw_file + wget $fw_url -O /tmp/$fw_file || { + echo "failed to load $fw_url" + exit 1 + } +} + +load_module() +{ + M=`lsmod | grep vmmc` + [ -z "$M" ] || return + config_load telephony + config_get fw_file config fw_file + [ -z "fw_file" ] && exit 1 + F=/lib/firmware/$fw_file + [ ! -f "$F" -a ! -L "$F" ] && { + echo "missing firmware file" + exit 1 + } + [ -L "$F" -a -f /tmp/$fw_file ] && F=/tmp/$fw_file + insmod vmmc + sleep 3 +} + +stop() +{ + killall tapi-sip 2>/dev/null +} + +start() +{ + stop + config_load telephony + config_get fw_url config fw_url + config_get fw_file config fw_file + config_get netdev config netdev + config_get disable config disable + [ "$disable" != "1" ] && { + [ ! -z "$fw_url" -a ! -f "/tmp/$fw_file" ] && download_fw + load_module + /usr/bin/tapi-sip $netdev & + } +} diff --git a/package/tapi_sip/src/Makefile b/package/tapi_sip/src/Makefile new file mode 100644 index 000000000..56013700f --- /dev/null +++ b/package/tapi_sip/src/Makefile @@ -0,0 +1,21 @@ +ifndef CFLAGS +CFLAGS = -O2 -g -I ../src +endif + +CFLAGS += -Werror -Wall -std=gnu99 + +PJ_CFLAGS ?= `pkg-config libpjproject --cflags` +PJ_LDFLAGS ?= `pkg-config libpjproject --libs` + +CFLAGS += $(PJ_CFLAGS) +LDFLAGS += $(PJ_LDFLAGS) + +FPIC=-fPIC + +all: tapi-sip + +%.o: %.c + $(CC) -c -o $@ $^ $(CFLAGS) + +tapi-sip: contact.o session.o tapi_agent.o tapi_sip.o sip_client.o stun.o dialdetector.o + $(CC) -o $@ $^ -ltapi -luci $(LDFLAGS) diff --git a/package/tapi_sip/src/agent.h b/package/tapi_sip/src/agent.h new file mode 100644 index 000000000..795994178 --- /dev/null +++ b/package/tapi_sip/src/agent.h @@ -0,0 +1,41 @@ +#ifndef __AGENT_H__ +#define __AGENT_H__ + +#include "agent.h" + +struct session; +struct agent; + +struct agent_ops { + int (*invite)(struct agent *, struct session *); + int (*accept)(struct agent *, struct session *); + int (*hangup)(struct agent *, struct session *); + + int (*get_endpoint)(struct agent *, struct session *); +}; + +struct agent { + const struct agent_ops *ops; +}; + +static inline int agent_invite(struct agent *agent, struct session *session) +{ + return agent->ops->invite(agent, session); +} + +static inline int agent_accept(struct agent *agent, struct session *session) +{ + return agent->ops->accept(agent, session); +} + +static inline int agent_hangup(struct agent *agent, struct session *session) +{ + return agent->ops->hangup(agent, session); +} + +static inline int agent_get_endpoint(struct agent *agent, struct session *session) +{ + return agent->ops->get_endpoint(agent, session); +} + +#endif diff --git a/package/tapi_sip/src/contact.c b/package/tapi_sip/src/contact.c new file mode 100644 index 000000000..eeb38b418 --- /dev/null +++ b/package/tapi_sip/src/contact.c @@ -0,0 +1,162 @@ +#include <malloc.h> +#include <string.h> + +#include <uci.h> +#include <ucimap.h> + +#include "list.h" +#include "contact.h" + +static struct uci_context *ctx; +static struct uci_package *pkg; +static struct list_head contact_list; +static struct list_head account_list; + +static int contact_init(struct uci_map *map, void *section, + struct uci_section *s) +{ + struct contact *p = section; + p->name = strdup(s->e.name); + return 0; +} + +static int contact_add(struct uci_map *map, void *section) +{ + struct contact *c = section; + printf("add contact: %s\n", c->name); + list_add_tail(&c->head, &contact_list); + return 0; +} + +static struct uci_optmap contact_uci_map[] = { + { + UCIMAP_OPTION(struct contact, identifier), + .type = UCIMAP_STRING, + .name = "identifier", + }, + { + UCIMAP_OPTION(struct contact, number), + .type = UCIMAP_STRING, + .name = "number", + }, +}; + +static struct uci_sectionmap contact_sectionmap = { + UCIMAP_SECTION(struct contact, map), + .type = "contact", + .init = contact_init, + .add = contact_add, + .options = contact_uci_map, + .n_options = ARRAY_SIZE(contact_uci_map), + .options_size = sizeof(struct uci_optmap), +}; + +static int account_init(struct uci_map *map, void *section, + struct uci_section *s) +{ + struct account *a = section; + a->name = strdup(s->e.name); + return 0; +} + +static int account_add(struct uci_map *map, void *section) +{ + struct account *a = section; + list_add_tail(&a->head, &account_list); + return 0; +} + +static struct uci_optmap account_uci_map[] = { + { + UCIMAP_OPTION(struct account, realm), + .type = UCIMAP_STRING, + .name = "realm", + }, + { + UCIMAP_OPTION(struct account, username), + .type = UCIMAP_STRING, + .name = "username", + }, + { + UCIMAP_OPTION(struct account, sip_port), + .type = UCIMAP_INT, + .name = "sip_port", + }, + { + UCIMAP_OPTION(struct account, password), + .type = UCIMAP_STRING, + .name = "password", + }, + { + UCIMAP_OPTION(struct account, stun_host), + .type = UCIMAP_STRING, + .name = "stun_host", + }, + { + UCIMAP_OPTION(struct account, stun_port), + .type = UCIMAP_INT, + .name = "stun_port", + }, +}; + +static struct uci_sectionmap account_sectionmap = { + UCIMAP_SECTION(struct account, map), + .type = "account", + .init = account_init, + .add = account_add, + .options = account_uci_map, + .n_options = ARRAY_SIZE(account_uci_map), + .options_size = sizeof(struct uci_optmap), +}; + +static struct uci_sectionmap *network_smap[] = { + &contact_sectionmap, + &account_sectionmap, +}; + +static struct uci_map contact_map = { + .sections = network_smap, + .n_sections = ARRAY_SIZE(network_smap), +}; + +int contacts_init(void) +{ + int ret; + + INIT_LIST_HEAD(&contact_list); + INIT_LIST_HEAD(&account_list); + ctx = uci_alloc_context(); + + ucimap_init(&contact_map); + ret = uci_load(ctx, "telephony", &pkg); + if (ret) + return ret; + + ucimap_parse(&contact_map, pkg); + + return 0; +} + +void contacts_free(void) +{ +} + +struct contact *contact_get(const char *number) +{ + struct contact *contact; + list_for_each_entry(contact, &contact_list, head) + { + if (strcmp(contact->number, number) == 0) + return contact; + } + + return NULL; +} + +struct account *get_account(void) +{ + if (list_empty(&account_list)) + return NULL; + + return list_first_entry(&account_list, struct account, head); +} diff --git a/package/tapi_sip/src/contact.h b/package/tapi_sip/src/contact.h new file mode 100644 index 000000000..1fb0037bf --- /dev/null +++ b/package/tapi_sip/src/contact.h @@ -0,0 +1,33 @@ +#ifndef __CONTACT_H__ +#define __CONTACT_H__ + +#include <ucimap.h> + +struct account { + struct ucimap_section_data map; + const char *name; + char *realm; + char *username; + char *password; + int sip_port; + char *stun_host; + int stun_port; + struct list_head head; +}; + +struct contact { + struct ucimap_section_data map; + const char *name; + const char *identifier; + const char *number; + + struct list_head head; +}; + +int contacts_init(void); +void contacts_free(void); +struct contact *contact_get(const char *number); + +struct account *get_account(void); + +#endif diff --git a/package/tapi_sip/src/dialdetector.c b/package/tapi_sip/src/dialdetector.c new file mode 100644 index 000000000..277b0296c --- /dev/null +++ b/package/tapi_sip/src/dialdetector.c @@ -0,0 +1,148 @@ +#include <linux/input.h> +#include <sys/epoll.h> +#include <stdint.h> +#include <stdbool.h> + + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <events.h> +#include "timerfd.h" + +#include "tapi-port.h" +#include "dialdetector.h" + +static const struct itimerspec dialdetector_timeout = { + .it_value.tv_sec = 3, +}; + +static const struct itimerspec dialdetector_impulse_timeout = { + .it_value.tv_nsec = 200000000, +}; + +static void dialdetector_note_digit(struct dialdetector *d, unsigned char digit) +{ + printf("note digit: %d\n", d->num_digits); + + d->digits[d->num_digits] = digit; + ++d->num_digits; + + timerfd_settime(d->timer_fd, 0, &dialdetector_timeout, NULL); + d->dial_state = DIALDETECTOR_DIAL_WAIT_TIMEOUT; +} + +static void dialdetector_reset(struct dialdetector *d) +{ + d->num_digits = 0; + d->impulses = 0; + d->dial_state = DIALDETECTOR_DIAL_WAIT; + d->port_state = DIALDETECTOR_PORT_INACTIVE; +} + +static bool dialdetector_timeout_event(int events, void *data) +{ + char num[20]; + struct dialdetector *dialdetector = data; + int i; + uint64_t tmp; + + read(dialdetector->timer_fd, &tmp, sizeof(tmp)); + + for (i = 0; i < dialdetector->num_digits; ++i) { + num[i] = '0' + dialdetector->digits[i]; + } + num[i] = '\0'; + + dialdetector->dial_callback(dialdetector->port, dialdetector->num_digits, + dialdetector->digits); + + dialdetector_reset(dialdetector); + + return true; +} + +static bool dialdetector_impulse_timeout_cb(int events, void *data) +{ + struct dialdetector *d = data; + uint64_t tmp; + + read(d->impulse_timer_fd, &tmp, sizeof(tmp)); + + if (d->port_state == DIALDETECTOR_PORT_ACTIVE_DOWN) { + d->port_state = DIALDETECTOR_PORT_INACTIVE; + } else { + printf("impulse: %d\n", d->impulses); + if (d->impulses > 0) + dialdetector_note_digit(d, d->impulses < 10 ? d->impulses : 0); + d->impulses = 0; + } + + return true; +} + +static void dialdetector_port_event(struct tapi_port *port, + struct tapi_event *event, void *data) +{ + struct dialdetector *d = data; + + printf("port event: %d %d\n", d->port_state, event->hook.on); + + switch (d->port_state) { + case DIALDETECTOR_PORT_INACTIVE: + if (event->type == TAPI_EVENT_TYPE_HOOK && event->hook.on == false) + d->port_state = DIALDETECTOR_PORT_ACTIVE; + break; + case DIALDETECTOR_PORT_ACTIVE: + switch (event->type) { + case TAPI_EVENT_TYPE_HOOK: + if (event->hook.on == true) { + d->port_state = DIALDETECTOR_PORT_ACTIVE_DOWN; + timerfd_settime(d->impulse_timer_fd, 0, &dialdetector_impulse_timeout, NULL); + } + break; + case TAPI_EVENT_TYPE_DTMF: + dialdetector_note_digit(d, event->dtmf.code); + break; + } + break; + case DIALDETECTOR_PORT_ACTIVE_DOWN: + if (event->type == TAPI_EVENT_TYPE_HOOK && event->hook.on == false) { + timerfd_settime(d->timer_fd, 0, &dialdetector_timeout, NULL); + ++d->impulses; + d->port_state = DIALDETECTOR_PORT_ACTIVE; + } + break; + } +} + +struct dialdetector *dialdetector_alloc(struct tapi_port *port) +{ + struct dialdetector *dialdetector; + dialdetector = malloc(sizeof(*dialdetector)); + + dialdetector->timer_fd = timerfd_create(CLOCK_MONOTONIC, 0); + dialdetector->impulse_timer_fd = timerfd_create(CLOCK_MONOTONIC, 0); + dialdetector->port = port; + dialdetector->num_digits = 0; + dialdetector->impulses = 0; + dialdetector->dial_state = DIALDETECTOR_DIAL_WAIT; + dialdetector->port_state = DIALDETECTOR_PORT_INACTIVE; + + dialdetector->timeout_cb.callback = dialdetector_timeout_event; + dialdetector->timeout_cb.data = dialdetector; + dialdetector->impulse_cb.callback = dialdetector_impulse_timeout_cb; + dialdetector->impulse_cb.data = dialdetector; + + dialdetector->port_listener.callback = dialdetector_port_event; + dialdetector->port_listener.data = dialdetector; + + tapi_port_register_event(port, &dialdetector->port_listener); + + event_register(dialdetector->impulse_timer_fd, EPOLLIN, + &dialdetector->impulse_cb); + event_register(dialdetector->timer_fd, EPOLLIN, &dialdetector->timeout_cb); + + return dialdetector; +} diff --git a/package/tapi_sip/src/dialdetector.h b/package/tapi_sip/src/dialdetector.h new file mode 100644 index 000000000..c3737c36e --- /dev/null +++ b/package/tapi_sip/src/dialdetector.h @@ -0,0 +1,49 @@ +#include <linux/input.h> +#include <sys/epoll.h> +#include <stdint.h> +#include <stdbool.h> + + +#include <stdlib.h> +#include <stdio.h> + +#include "events.h" +#include "timerfd.h" + +#include "tapi-port.h" + +enum dialdetector_dial_state { + DIALDETECTOR_DIAL_WAIT = 1, + DIALDETECTOR_DIAL_WAIT_TIMEOUT = 2, +}; + +enum dialdetector_port_state { + DIALDETECTOR_PORT_INACTIVE = 0, + DIALDETECTOR_PORT_ACTIVE = 1, + DIALDETECTOR_PORT_ACTIVE_DOWN = 2, +}; + +struct dialdetector { + enum dialdetector_dial_state dial_state; + enum dialdetector_port_state port_state; + + struct tapi_port *port; + int timer_fd; + int impulse_timer_fd; + + struct event_callback timeout_cb; + struct event_callback impulse_cb; + struct tapi_port_event_listener port_listener; + + size_t num_digits; + unsigned char digits[20]; + + unsigned int impulses; + + void (*dial_callback)(struct tapi_port *port, size_t num_digits, const unsigned char *digits); +}; + + +struct tapi_port; + +struct dialdetector *dialdetector_alloc(struct tapi_port *port); diff --git a/package/tapi_sip/src/list.h b/package/tapi_sip/src/list.h new file mode 100644 index 000000000..2959a061d --- /dev/null +++ b/package/tapi_sip/src/list.h @@ -0,0 +1,601 @@ +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +#include <stddef.h> +/** + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#ifndef container_of +#define container_of(ptr, type, member) ( \ + (type *)( (char *)ptr - offsetof(type,member) )) +#endif + + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +static inline void INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty() on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = NULL; + entry->prev = NULL; +} + +/** + * list_replace - replace old entry by new one + * @old : the element to be replaced + * @new : the new element to insert + * + * If @old was empty, it will be overwritten. + */ +static inline void list_replace(struct list_head *old, + struct list_head *new) +{ + new->next = old->next; + new->next->prev = new; + new->prev = old->prev; + new->prev->next = new; +} + +static inline void list_replace_init(struct list_head *old, + struct list_head *new) +{ + list_replace(old, new); + INIT_LIST_HEAD(old); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * list_is_last - tests whether @list is the last entry in list @head + * @list: the entry to test + * @head: the head of the list + */ +static inline int list_is_last(const struct list_head *list, + const struct list_head *head) +{ + return list->next == head; +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_empty_careful - tests whether a list is empty and not being modified + * @head: the list to test + * + * Description: + * tests whether a list is empty _and_ checks that no other CPU might be + * in the process of modifying either member (next or prev) + * + * NOTE: using list_empty_careful() without synchronization + * can only be safe if the only activity that can happen + * to the list entry is list_del_init(). Eg. it cannot be used + * if another CPU could re-list_add() it. + */ +static inline int list_empty_careful(const struct list_head *head) +{ + struct list_head *next = head->next; + return (next == head) && (next == head->prev); +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_first_entry - get the first element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); \ + pos = pos->next) + +/** + * __list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + * + * This variant differs from list_for_each() in that it's the + * simplest possible list iteration code, no prefetching is done. + * Use this for code that knows the list to be very short (empty + * or 1 entry) most of the time. + */ +#define __list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); \ + pos = pos->prev) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_prev_safe(pos, n, head) \ + for (pos = (head)->prev, n = pos->prev; \ + pos != (head); \ + pos = n, n = pos->prev) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue() + * @pos: the type * to use as a start point + * @head: the head of the list + * @member: the name of the list_struct within the struct. + * + * Prepares a pos entry for use as a start point in list_for_each_entry_continue(). + */ +#define list_prepare_entry(pos, head, member) \ + ((pos) ? : list_entry(head, typeof(*pos), member)) + +/** + * list_for_each_entry_continue - continue iteration over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Continue to iterate over list of given type, continuing after + * the current position. + */ +#define list_for_each_entry_continue(pos, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_continue_reverse - iterate backwards from the given point + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Start to iterate over list of given type backwards, continuing after + * the current position. + */ +#define list_for_each_entry_continue_reverse(pos, head, member) \ + for (pos = list_entry(pos->member.prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_for_each_entry_from - iterate over list of given type from the current point + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type, continuing from current position. + */ +#define list_for_each_entry_from(pos, head, member) \ + for (; &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_continue + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type, continuing after current point, + * safe against removal of list entry. + */ +#define list_for_each_entry_safe_continue(pos, n, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_from + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type from current point, safe against + * removal of list entry. + */ +#define list_for_each_entry_safe_from(pos, n, head, member) \ + for (n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_reverse + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate backwards over list of given type, safe against removal + * of list entry. + */ +#define list_for_each_entry_safe_reverse(pos, n, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member), \ + n = list_entry(pos->member.prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.prev, typeof(*n), member)) + +/* + * Double linked lists with a single pointer list head. + * Mostly useful for hash tables where the two pointer list head is + * too wasteful. + * You lose the ability to access the tail in O(1). + */ + +struct hlist_head { + struct hlist_node *first; +}; + +struct hlist_node { + struct hlist_node *next, **pprev; +}; + +#define HLIST_HEAD_INIT { .first = NULL } +#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } +#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) +static inline void INIT_HLIST_NODE(struct hlist_node *h) +{ + h->next = NULL; + h->pprev = NULL; +} + +static inline int hlist_unhashed(const struct hlist_node *h) +{ + return !h->pprev; +} + +static inline int hlist_empty(const struct hlist_head *h) +{ + return !h->first; +} + +static inline void __hlist_del(struct hlist_node *n) +{ + struct hlist_node *next = n->next; + struct hlist_node **pprev = n->pprev; + *pprev = next; + if (next) + next->pprev = pprev; +} + +static inline void hlist_del(struct hlist_node *n) +{ + __hlist_del(n); + n->next = NULL; + n->pprev = NULL; +} + +static inline void hlist_del_init(struct hlist_node *n) +{ + if (!hlist_unhashed(n)) { + __hlist_del(n); + INIT_HLIST_NODE(n); + } +} + + +static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) +{ + struct hlist_node *first = h->first; + n->next = first; + if (first) + first->pprev = &n->next; + h->first = n; + n->pprev = &h->first; +} + + +/* next must be != NULL */ +static inline void hlist_add_before(struct hlist_node *n, + struct hlist_node *next) +{ + n->pprev = next->pprev; + n->next = next; + next->pprev = &n->next; + *(n->pprev) = n; +} + +static inline void hlist_add_after(struct hlist_node *n, + struct hlist_node *next) +{ + next->next = n->next; + n->next = next; + next->pprev = &n->next; + + if(next->next) + next->next->pprev = &next->next; +} + +#define hlist_entry(ptr, type, member) container_of(ptr,type,member) + +#define hlist_for_each(pos, head) \ + for (pos = (head)->first; pos; pos = pos->next) + +#define hlist_for_each_safe(pos, n, head) \ + for (pos = (head)->first; pos; pos = n) + +/** + * hlist_for_each_entry - iterate over list of given type + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry(tpos, pos, head, member) \ + for (pos = (head)->first; pos && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_continue - iterate over a hlist continuing after current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_continue(tpos, pos, member) \ + for (pos = (pos)->next; pos && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_from - iterate over a hlist continuing from current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_from(tpos, pos, member) \ + for (; pos && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct hlist_node to use as a loop cursor. + * @n: another &struct hlist_node to use as temporary storage + * @head: the head for your list. + * @member: the name of the hlist_node within the struct. + */ +#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ + for (pos = (head)->first; \ + pos && ({ n = pos->next; 1; }) && \ + ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = n) + +#endif diff --git a/package/tapi_sip/src/session.c b/package/tapi_sip/src/session.c new file mode 100644 index 000000000..97f6f06fe --- /dev/null +++ b/package/tapi_sip/src/session.c @@ -0,0 +1,84 @@ +#include <stdlib.h> +#include <stdio.h> + +#include "agent.h" +#include "session.h" + +#include <tapi-device.h> + + +struct session +{ + struct agent *agents[2]; + + struct tapi_device *tdev; + + void (*release)(struct session *); + + int link; +}; + +struct session *session_alloc(struct tapi_device *dev, struct agent *caller, + struct agent *callee, void (*release)(struct session *)) +{ + struct session *session; + int ret; + + session = malloc(sizeof(*session)); + + session->tdev = dev; + + session->agents[0] = caller; + session->agents[1] = callee; + + ret = agent_invite(callee, session); + if (ret < 0) { + session_hangup(session, callee); + free(session); + return NULL; + } + + session->release = release; + + return session; +} + +void session_accept(struct session *session, struct agent *agent) +{ + int ep[2]; + + printf("session_accept: %p %p\n", session, agent); + printf("session agents: %p %p\n", session->agents[0], session->agents[1]); + printf("session tdev: %p\n", session->tdev); + + agent_accept(session->agents[0], session); + + ep[0] = agent_get_endpoint(session->agents[0], session); + ep[1] = agent_get_endpoint(session->agents[1], session); + session->link = tapi_link_alloc(session->tdev, ep[0], ep[1]); + + printf("eps: %d %d\n", ep[0], ep[1]); + + tapi_link_enable(session->tdev, session->link); + + tapi_sync(session->tdev); +} + +void session_hangup(struct session *session, struct agent *agent) +{ + struct agent *other_agent; + + if (session->agents[0] == agent) + other_agent = session->agents[1]; + else + other_agent = session->agents[0]; + + agent_hangup(other_agent, session); + + tapi_link_disable(session->tdev, session->link); + tapi_link_free(session->tdev, session->link); + tapi_sync(session->tdev); + + if (session->release) + session->release(session); +} diff --git a/package/tapi_sip/src/session.h b/package/tapi_sip/src/session.h new file mode 100644 index 000000000..7403785f5 --- /dev/null +++ b/package/tapi_sip/src/session.h @@ -0,0 +1,14 @@ +#ifndef __SESSION_H__ +#define __SESSION_H__ + +struct agent; +struct session; +struct tapi_device; + +struct session *session_alloc(struct tapi_device *, struct agent *caller, + struct agent *callee, void (*release)(struct session *)); +void session_hangup(struct session *, struct agent *); +void session_accept(struct session *, struct agent *); +void session_free(struct session *); + +#endif diff --git a/package/tapi_sip/src/sip_agent.h b/package/tapi_sip/src/sip_agent.h new file mode 100644 index 000000000..7f711da43 --- /dev/null +++ b/package/tapi_sip/src/sip_agent.h @@ -0,0 +1,27 @@ +#ifndef __SIP_AGENT_H__ +#define __SIP_AGENT_H__ + +#include "agent.h" +#include <events.h> + +struct sip_agent { + struct sip_client *client; + const char *identifier; + + struct tapi_stream *stream; + struct session *session; + + struct pjsip_inv_session *inv; + + int rtp_sockfd; + + struct sockaddr_storage remote_addr; + struct sockaddr_storage local_addr; + + struct agent agent; + + struct event_callback rtp_recv_callback; + struct event_callback stream_recv_callback; +}; + +#endif diff --git a/package/tapi_sip/src/sip_client.c b/package/tapi_sip/src/sip_client.c new file mode 100644 index 000000000..0b1e2ddd8 --- /dev/null +++ b/package/tapi_sip/src/sip_client.c @@ -0,0 +1,800 @@ +#include "sip_client.h" + +#include <stdint.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <netdb.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <sys/epoll.h> +#include <stdbool.h> +#include <unistd.h> +#include <fcntl.h> +#include <net/if.h> +#include <sys/ioctl.h> + +#include "stun.h" +#include "sip_agent.h" +#include "session.h" +#include "list.h" + +static inline struct sip_agent *agent_to_sip_agent(struct agent *agent) +{ + return container_of(agent, struct sip_agent, agent); +} + +static int iface_get_addr(const char *iface, struct sockaddr_storage *addr) +{ + int fd; + int ret; + struct ifreq ifr; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + + ifr.ifr_addr.sa_family = AF_INET; + strncpy(ifr.ifr_name, iface, IFNAMSIZ-1); + ret = ioctl(fd, SIOCGIFADDR, &ifr); + if (ret < 0) + perror("Failed to get interface address"); + + close(fd); + + if (ret == 0) + memcpy(addr, &ifr.ifr_addr, sizeof(ifr.ifr_addr)); + + return ret; +} + +#if 0 +static bool sockaddr_is_local(struct sockaddr_storage *addr) +{ + unsigned long s_addr; + bool is_local = false; + + switch (addr->ss_family) { + case AF_INET: + s_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr; + if ((s_addr & 0xff000000) == 0x10000000) + is_local = true; + else if ((s_addr & 0xfff00000) == 0xac100000) + is_local = true; + else if ((s_addr & 0xffff0000) == 0xc0a80000) + is_local = true; + break; + default: + break; + } + + return is_local; +} +#endif + +static uint16_t sockaddr_get_port(struct sockaddr_storage *addr) +{ + uint16_t port; + + switch (addr->ss_family) { + case AF_INET: + port = ((struct sockaddr_in *)addr)->sin_port; + break; + case AF_INET6: + port = ((struct sockaddr_in6 *)addr)->sin6_port; + break; + default: + port = 0; + break; + } + + return port; +} + +static void sockaddr_set_port(struct sockaddr_storage *addr, uint16_t port) +{ + switch (addr->ss_family) { + case AF_INET: + ((struct sockaddr_in *)addr)->sin_port = port; + break; + case AF_INET6: + ((struct sockaddr_in6 *)addr)->sin6_port = port; + break; + default: + break; + } +} + +static void *sockaddr_get_addr(struct sockaddr_storage *addr) +{ + void *a; + switch (addr->ss_family) { + case AF_INET: + a = &((struct sockaddr_in *)addr)->sin_addr.s_addr; + break; + case AF_INET6: + a = ((struct sockaddr_in6 *)addr)->sin6_addr.s6_addr; + break; + default: + a = NULL; + break; + } + + return a; +} + +static int sockaddr_to_string(struct sockaddr_storage *addr, char *s, size_t n) +{ + return inet_ntop(addr->ss_family, sockaddr_get_addr(addr), s, n) == NULL ? -1 : 0; +} + +static pjsip_module mod_siprtp; +static struct sip_client *global_client; + +/* Creates a datagram socket and binds it to a port in the range of + * start_port-end_port */ +static int sip_client_create_socket(struct sip_client *client, + struct sockaddr_storage *sockaddr, uint16_t start_port, + uint16_t end_port) +{ + int sockfd; + int ret; + + sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + return sockfd; + + memcpy(sockaddr, &client->local_addr, sizeof(client->local_addr)); + do { + sockaddr_set_port(sockaddr, start_port); + ret = bind(sockfd, (struct sockaddr *)sockaddr, sizeof(*sockaddr)); + ++start_port; + } while (ret == -1 && start_port < end_port); + + if (ret == -1) + return -1; + + return sockfd; +} + +static int sip_worker_thread(void *arg) +{ + struct sip_client *client = arg; + + while (1) { + pj_time_val timeout = {0, 10}; + pjsip_endpt_handle_events(client->sip_endpt, &timeout); + } + + return 0; +} + +static bool sip_agent_stream_recv_callback(int events, void *data) +{ + struct sip_agent *agent = data; + char buf[512]; + int len; + int ret; + + len = read(agent->stream->fd, buf, 512); + if (len < 0) + return true; + + ret = sendto(agent->rtp_sockfd, buf, len, 0, + (struct sockaddr *)&agent->remote_addr, sizeof(agent->remote_addr)); + + if (ret < 0) + printf("failed to send rtp data: %d\n", errno); + + return true; +} + + +static bool sip_agent_rtp_recv_callback(int events, void *data) +{ + struct sip_agent *agent = data; + char buf[512]; + int len; + + len = recvfrom(agent->rtp_sockfd, buf, 512, 0, NULL, NULL); + if (agent->stream) + write(agent->stream->fd, buf, len); + + return true; +} + +static pj_status_t sip_client_create_sdp(struct sip_client *client, pj_pool_t *pool, + struct sip_agent *agent, + pjmedia_sdp_session **p_sdp) +{ + pj_time_val tv; + pjmedia_sdp_session *sdp; + pjmedia_sdp_media *m; + pjmedia_sdp_attr *attr; + struct sockaddr_storage rtp_addr; + char addr[INET6_ADDRSTRLEN]; + + PJ_ASSERT_RETURN(pool && p_sdp, PJ_EINVAL); + + agent->rtp_sockfd = sip_client_create_socket(client, &rtp_addr, 4000, 5000); + if (client->stun) + stun_client_resolve(client->stun, agent->rtp_sockfd, + (struct sockaddr *)&rtp_addr); + + agent->rtp_recv_callback.callback = sip_agent_rtp_recv_callback; + agent->rtp_recv_callback.data = agent; + event_register(agent->rtp_sockfd, EPOLLIN, &agent->rtp_recv_callback); + + /* Create and initialize basic SDP session */ + sdp = pj_pool_zalloc (pool, sizeof(pjmedia_sdp_session)); + + pj_gettimeofday(&tv); + sdp->origin.user = pj_str("pjsip-siprtp"); + sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL; + sdp->origin.net_type = pj_str("IN"); + sdp->origin.addr_type = pj_str("IP4"); + sdp->origin.addr = *pj_gethostname(); + sdp->name = pj_str("pjsip"); + + /* Since we only support one media stream at present, put the + * SDP connection line in the session level. + */ + sdp->conn = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_conn)); + sdp->conn->net_type = pj_str("IN"); + sdp->conn->addr_type = pj_str("IP4"); + sockaddr_to_string(&rtp_addr, addr, sizeof(addr)); + pj_strdup2_with_null(pool, &sdp->conn->addr, addr); + + /* SDP time and attributes. */ + sdp->time.start = sdp->time.stop = 0; + sdp->attr_count = 0; + + /* Create media stream 0: */ + + sdp->media_count = 1; + m = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_media)); + sdp->media[0] = m; + + /* Standard media info: */ + m->desc.media = pj_str("audio"); + m->desc.port = sockaddr_get_port(&rtp_addr); + m->desc.port_count = 1; + m->desc.transport = pj_str("RTP/AVP"); + + /* Add format and rtpmap for each codec. */ + m->desc.fmt_count = 1; + m->attr_count = 0; + + { + pjmedia_sdp_rtpmap rtpmap; + char ptstr[10]; + + sprintf(ptstr, "%d", 0); + pj_strdup2_with_null(pool, &m->desc.fmt[0], ptstr); + rtpmap.pt = m->desc.fmt[0]; + rtpmap.clock_rate = 64000; + rtpmap.enc_name = pj_str("PCMU"); + rtpmap.param.slen = 0; + + } + + /* Add sendrecv attribute. */ + attr = pj_pool_zalloc(pool, sizeof(pjmedia_sdp_attr)); + attr->name = pj_str("sendrecv"); + m->attr[m->attr_count++] = attr; + + /* Done */ + *p_sdp = sdp; + + return PJ_SUCCESS; +} + +static int sip_agent_invite(struct agent *agent, struct session *session) +{ + struct sip_agent *sip_agent = agent_to_sip_agent(agent); + struct sip_client *client = sip_agent->client; + pjsip_dialog *dlg; + pjmedia_sdp_session *sdp; + pjsip_tx_data *tdata; + pj_status_t status; + pj_str_t dst_uri; + + sip_agent->session = session; + + dst_uri = pj_str((char *)sip_agent->identifier); + + /* Create UAC dialog */ + status = pjsip_dlg_create_uac(pjsip_ua_instance(), + &client->local_contact, /* local URI */ + &client->local_contact, /* local Contact */ + &dst_uri, /* remote URI */ + &dst_uri, /* remote target */ + &dlg); /* dialog */ + if (status != PJ_SUCCESS) { + fprintf(stderr, "Failed to create uac dialog\n"); + return -1; + } + + pjsip_auth_clt_set_credentials(&dlg->auth_sess, 1, &client->cred); + + /* Create SDP */ + sip_client_create_sdp(client, dlg->pool, sip_agent, &sdp); + + /* Create the INVITE session. */ + status = pjsip_inv_create_uac(dlg, sdp, 0, &sip_agent->inv); + if (status != PJ_SUCCESS) { + fprintf(stderr, "Failed to create invite session\n"); + pjsip_dlg_terminate(dlg); + return -1; + } + + + /* Attach call data to invite session */ + sip_agent->inv->mod_data[mod_siprtp.id] = sip_agent; + + /* Create initial INVITE request. + * This INVITE request will contain a perfectly good request and + * an SDP body as well. + */ + status = pjsip_inv_invite(sip_agent->inv, &tdata); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, -1); /*TODO*/ + + /* Send initial INVITE request. + * From now on, the invite session's state will be reported to us + * via the invite session callbacks. + */ + status = pjsip_inv_send_msg(sip_agent->inv, tdata); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, -1); /*TODO*/ + + return 0; +} + +static int sip_agent_hangup(struct agent *agent, struct session *session) +{ + struct sip_agent *sip_agent = agent_to_sip_agent(agent); + pjsip_tx_data *tdata; + pj_status_t status; + + printf("hangup %p\n", sip_agent->inv); + + status = pjsip_inv_end_session(sip_agent->inv, 603, NULL, &tdata); + if (status == PJ_SUCCESS && tdata != NULL) + pjsip_inv_send_msg(sip_agent->inv, tdata); + + if (sip_agent->rtp_sockfd) { + event_unregister(sip_agent->rtp_sockfd); + close(sip_agent->rtp_sockfd); + } + if (sip_agent->stream) { + event_unregister(sip_agent->stream->fd); + tapi_stream_free(sip_agent->stream); + } + + sip_agent->inv->mod_data[mod_siprtp.id] = NULL; + free(sip_agent); + + return 0; +} + +static int sip_agent_alloc_stream(struct sip_agent *agent) +{ + int flags; + + if (agent->stream) + printf("BUG!!!! %s:%s[%d]\n", __FILE__, __func__, __LINE__); + + agent->stream = tapi_stream_alloc(agent->client->tdev); + agent->stream_recv_callback.callback = sip_agent_stream_recv_callback; + agent->stream_recv_callback.data = agent; + + flags = fcntl(agent->stream->fd, F_GETFL, 0); + fcntl(agent->stream->fd, F_SETFL, flags | O_NONBLOCK); + + event_register(agent->stream->fd, EPOLLIN, &agent->stream_recv_callback); + + return 0; +} + +static void sip_agent_free_stream(struct sip_agent *agent) +{ + if (!agent->stream) + return; + + event_unregister(agent->stream->fd); + tapi_stream_free(agent->stream); + agent->stream = NULL; +} + +static int sip_agent_accept(struct agent *agent, struct session *session) +{ + struct sip_agent *sip_agent = agent_to_sip_agent(agent); + pj_status_t status; + pjsip_tx_data *tdata; + + /* Create 200 response .*/ + status = pjsip_inv_answer(sip_agent->inv, 200, + NULL, NULL, &tdata); + if (status != PJ_SUCCESS) { + status = pjsip_inv_answer(sip_agent->inv, + PJSIP_SC_NOT_ACCEPTABLE, + NULL, NULL, &tdata); + if (status == PJ_SUCCESS) + pjsip_inv_send_msg(sip_agent->inv, tdata); + else + pjsip_inv_terminate(sip_agent->inv, 500, PJ_FALSE); + return -1; + } + + /* Send the 200 response. */ + status = pjsip_inv_send_msg(sip_agent->inv, tdata); + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return -1); + + sip_agent_alloc_stream(sip_agent); + + return 0; +} + +static int sip_agent_get_endpoint(struct agent *agent, struct session *session) +{ + struct sip_agent *sip_agent = agent_to_sip_agent(agent); + return tapi_stream_get_endpoint(sip_agent->stream); +} + +static const struct agent_ops sip_agent_ops = { + .invite = sip_agent_invite, + .accept = sip_agent_accept, + .hangup = sip_agent_hangup, + .get_endpoint = sip_agent_get_endpoint, +}; + +struct sip_agent *sip_client_alloc_agent(struct sip_client *client, + const char *identifier) +{ + struct sip_agent *agent; + + agent = malloc(sizeof(*agent)); + memset(agent, 0, sizeof(*agent)); + + agent->agent.ops = &sip_agent_ops; + agent->identifier = identifier; + agent->client = client; + + return agent; +} + +/* + * Receive incoming call + */ +static void process_incoming_call(struct sip_client *client, pjsip_rx_data *rdata) +{ + unsigned options; + struct sip_agent *agent; + pjsip_tx_data *tdata; + pj_status_t status; + pjsip_dialog *dlg; + pjmedia_sdp_session *sdp; + + agent = sip_client_alloc_agent(client, "extern"); + + /* Verify that we can handle the request. */ + options = 0; + status = pjsip_inv_verify_request(rdata, &options, NULL, NULL, + client->sip_endpt, &tdata); + if (status != PJ_SUCCESS) { + /* + * No we can't handle the incoming INVITE request. + */ + if (tdata) { + pjsip_response_addr res_addr; + pjsip_get_response_addr(tdata->pool, rdata, &res_addr); + pjsip_endpt_send_response(client->sip_endpt, &res_addr, tdata, + NULL, NULL); + } else { + /* Respond with 500 (Internal Server Error) */ + pjsip_endpt_respond_stateless(client->sip_endpt, rdata, 500, NULL, + NULL, NULL); + } + + return; + } + + /* Create UAS dialog */ + status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, + &client->local_contact, &dlg); + if (status != PJ_SUCCESS) { + const pj_str_t reason = pj_str("Unable to create dialog"); + pjsip_endpt_respond_stateless(client->sip_endpt, rdata, + 500, &reason, + NULL, NULL); + return; + } + + /* Create SDP */ + sip_client_create_sdp(client, dlg->pool, agent, &sdp); + + /* Create UAS invite session */ + status = pjsip_inv_create_uas(dlg, rdata, sdp, 0, &agent->inv); + if (status != PJ_SUCCESS) { + pjsip_dlg_create_response(dlg, rdata, 500, NULL, &tdata); + pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata); + return; + } + + /* Attach call data to invite session */ + agent->inv->mod_data[mod_siprtp.id] = agent; + + /* Create 180 response .*/ + status = pjsip_inv_initial_answer(agent->inv, rdata, 180, + NULL, NULL, &tdata); + if (status != PJ_SUCCESS) { + status = pjsip_inv_initial_answer(agent->inv, rdata, + PJSIP_SC_NOT_ACCEPTABLE, + NULL, NULL, &tdata); + if (status == PJ_SUCCESS) + pjsip_inv_send_msg(agent->inv, tdata); + else + pjsip_inv_terminate(agent->inv, 500, PJ_FALSE); + return; + } + + /* Send the 180 response. */ + status = pjsip_inv_send_msg(agent->inv, tdata); + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return); + + if (client->incoming_call_cb) + client->incoming_call_cb(client, agent); +} + +/* Callback to be called to handle incoming requests outside dialogs: */ +static pj_bool_t on_rx_request(pjsip_rx_data *rdata) +{ + struct sip_client *client = global_client; + + /* Ignore strandled ACKs (must not send respone */ + if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD) + return PJ_FALSE; + + /* Respond (statelessly) any non-INVITE requests with 500 */ + if (rdata->msg_info.msg->line.req.method.id != PJSIP_INVITE_METHOD) { + pj_str_t reason = pj_str("Unsupported Operation"); + pjsip_endpt_respond_stateless(client->sip_endpt, rdata, + 500, &reason, + NULL, NULL); + return PJ_TRUE; + } + + /* Handle incoming INVITE */ + process_incoming_call(client, rdata); + + /* Done */ + return PJ_TRUE; +} + +static pjsip_module sip_client_mod = { + NULL, NULL, /* prev, next. */ + { "mod-tapisip", 13 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +/* Callback to be called when dialog has forked: */ +static void call_on_forked(pjsip_inv_session *inv, pjsip_event *e) +{ + PJ_UNUSED_ARG(inv); + PJ_UNUSED_ARG(e); +} + +/* Callback to be called when invite session's state has changed: */ +static void call_on_state_changed(pjsip_inv_session *inv, pjsip_event *e) +{ + struct sip_agent *agent = inv->mod_data[mod_siprtp.id]; + + printf("state changed: %d\n", inv->state); + + if (!agent) + return; + + switch (inv->state) { + case PJSIP_INV_STATE_DISCONNECTED: + printf("Disconnected\n"); + if (agent->session) + session_hangup(agent->session, &agent->agent); + if (agent->rtp_sockfd) { + event_unregister(agent->rtp_sockfd); + close(agent->rtp_sockfd); + } + sip_agent_free_stream(agent); + free(agent); + inv->mod_data[mod_siprtp.id] = NULL; + break; + case PJSIP_INV_STATE_CONFIRMED: + printf("Connected: %p\n", agent->stream); + if (agent->stream) + break; + sip_agent_alloc_stream(agent); + session_accept(agent->session, &agent->agent); + break; + default: + break; + } +} + +static void call_on_media_update(pjsip_inv_session *inv, pj_status_t status) +{ + struct sip_agent *agent; + pj_pool_t *pool; + const pjmedia_sdp_session *local_sdp, *remote_sdp; + char local[100]; + char remote[100]; + int i; + + printf("media updte\n"); + + agent = inv->mod_data[mod_siprtp.id]; + pool = inv->dlg->pool; + + /* Do nothing if media negotiation has failed */ + if (status != PJ_SUCCESS) + return; + /* Capture stream definition from the SDP */ + pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp); + pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp); + + strlcpy(local, local_sdp->conn->addr.ptr, local_sdp->conn->addr.slen + 1); + printf("local media count: %d\n", local_sdp->media_count); + printf("local: %s %d\n", local, + ntohs(local_sdp->media[0]->desc.port)); + strlcpy(remote, remote_sdp->conn->addr.ptr, remote_sdp->conn->addr.slen + 1); + printf("remote media count: %d\n", remote_sdp->media_count); + printf("remote: %s %d\n", remote, + ntohs(remote_sdp->media[0]->desc.port)); + + agent->remote_addr.ss_family = AF_INET; + inet_pton(AF_INET, remote, + sockaddr_get_addr(&agent->remote_addr)); + sockaddr_set_port(&agent->remote_addr, remote_sdp->media[0]->desc.port); + + printf("attributes: %d\n", remote_sdp->attr_count); + for (i = 0; i < remote_sdp->attr_count; ++i) + printf("%s: %s\n", remote_sdp->attr[i]->name.ptr, + remote_sdp->attr[i]->value.ptr); + +} + +static int sip_client_init_sip_endpoint(struct sip_client *client) +{ + pj_status_t status; + pjsip_host_port addrname; + pjsip_inv_callback inv_cb; + pjsip_transport *tp; + char public_addr[INET6_ADDRSTRLEN]; + + global_client = client; + + pj_caching_pool_init(&client->cp, &pj_pool_factory_default_policy, 0); + client->pool = pj_pool_create(&client->cp.factory, "tapi sip", 1000, 1000, NULL); + + status = pjsip_endpt_create(&client->cp.factory, NULL, + &client->sip_endpt); + + client->sockfd = sip_client_create_socket(client, &client->public_addr, 5060, 5100); + if (client->stun) + stun_client_resolve(client->stun, client->sockfd, (struct sockaddr *)&client->public_addr); + + sockaddr_to_string(&client->public_addr, public_addr, sizeof(public_addr)); + + addrname.host = pj_str(public_addr); + addrname.port = sockaddr_get_port(&client->public_addr); + + pjsip_udp_transport_attach(client->sip_endpt, client->sockfd, &addrname, 1, &tp); + + status = pjsip_tsx_layer_init_module(client->sip_endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_ua_init_module(client->sip_endpt, NULL); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + status = pjsip_100rel_init_module(client->sip_endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + pj_bzero(&inv_cb, sizeof(inv_cb)); + inv_cb.on_state_changed = &call_on_state_changed; + inv_cb.on_new_session = &call_on_forked; + inv_cb.on_media_update = &call_on_media_update; + + status = pjsip_inv_usage_init(client->sip_endpt, &inv_cb); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + status = pjsip_endpt_register_module(client->sip_endpt, &sip_client_mod); + + pj_thread_create(client->pool, "sip client", &sip_worker_thread, client, + 0, 0, &client->sip_thread); + + return status; +} + +static void sip_client_register_callback(struct pjsip_regc_cbparam *param) +{ + if (param->status != PJ_SUCCESS || param->code / 100 != 2) + printf("Failed to register: %d %d", param->status, param->code); +} + +void sip_client_free(struct sip_client *client) +{ + pjsip_regc_destroy(client->regc); + pjsip_endpt_destroy(client->sip_endpt); +} + +void sip_client_register(struct sip_client *client) +{ + pjsip_tx_data *tdata; + + pjsip_regc_register(client->regc, true, &tdata); + pjsip_regc_send(client->regc, tdata); +} + +void sip_client_set_cred(struct sip_client *client) +{ + char local_contact[100]; + char server_uri[100]; + char s[INET6_ADDRSTRLEN]; + char contact_addr[INET6_ADDRSTRLEN + 10]; + pj_str_t pj_contact_addr; + + sockaddr_to_string(&client->public_addr, s, sizeof(s)); + + snprintf(contact_addr, sizeof(contact_addr), "sip:%s:%d", s, + sockaddr_get_port(&client->public_addr)); + pj_contact_addr = pj_str(contact_addr); + + client->cred.realm = pj_str((char *)client->config->host); + client->cred.scheme = pj_str("digest"); + client->cred.username = pj_str((char *)client->config->username); + client->cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; + client->cred.data = pj_str((char *)client->config->password); + + snprintf(local_contact, sizeof(local_contact), "sip:%s@%s", + client->config->username, client->config->host); + pj_strdup2_with_null(client->pool, &client->local_contact, local_contact); + + snprintf(server_uri, sizeof(server_uri), "sip:%s\n", client->config->host); + pj_strdup2_with_null(client->pool, &client->server_uri, server_uri); + + pjsip_regc_init(client->regc, &client->server_uri, &client->local_contact, + &client->local_contact, 1, &pj_contact_addr, 3600); + pjsip_regc_set_credentials(client->regc, 1, &client->cred); + + sip_client_register(client); +} + +void sip_client_init(struct sip_client *client, struct tapi_device *tdev, + const struct sip_client_config *config) +{ + global_client = client; + client->config = config; + + client->tdev = tdev; + + iface_get_addr(config->iface, &client->local_addr); + + if (config->stun_host && config->stun_port) + client->stun = stun_client_alloc(config->stun_host, config->stun_port); + else + client->stun = NULL; + + sip_client_init_sip_endpoint(client); + + pjsip_regc_create(client->sip_endpt, client, sip_client_register_callback, + &client->regc); + + sip_client_set_cred(client); +} + + diff --git a/package/tapi_sip/src/sip_client.h b/package/tapi_sip/src/sip_client.h new file mode 100644 index 000000000..1c494b6dc --- /dev/null +++ b/package/tapi_sip/src/sip_client.h @@ -0,0 +1,61 @@ +#ifndef __SIP_CLIENT_H__ +#define __SIP_CLIENT_H__ + +#include <tapi-stream.h> +#include <tapi-device.h> + +#include <pjsip.h> +#include <pjsip_ua.h> +#include <pjsip_simple.h> +#include <pjlib-util.h> +#include <pjlib.h> + +#include <stdlib.h> +#include <sys/socket.h> +#include <stdint.h> + +struct stun_client; +struct sip_agent; + +struct sip_client_config { + const char *iface; + + const char *host; + uint16_t port; + const char *username; + const char *password; + + const char *stun_host; + uint16_t stun_port; +}; + +struct sip_client { + const struct sip_client_config *config; + + struct tapi_device *tdev; + struct stun_client *stun; + + struct sockaddr_storage public_addr; + struct sockaddr_storage local_addr; + + int sockfd; + + pj_thread_t *sip_thread; + pj_caching_pool cp; + pj_pool_t *pool; + pjsip_endpoint *sip_endpt; + pjsip_cred_info cred; + pj_str_t local_contact; + pj_str_t server_uri; + + pjsip_regc *regc; + + int (*incoming_call_cb)(struct sip_client *client, struct sip_agent *agent); +}; + +void sip_client_init(struct sip_client *client, struct tapi_device *dev, + const struct sip_client_config *config); + +struct sip_agent *sip_client_alloc_agent(struct sip_client *client, const char *dst_uri); + +#endif diff --git a/package/tapi_sip/src/stun.c b/package/tapi_sip/src/stun.c new file mode 100644 index 000000000..5c6b24067 --- /dev/null +++ b/package/tapi_sip/src/stun.c @@ -0,0 +1,243 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include <poll.h> + + +struct stun_client { + struct addrinfo *serverinfo; +}; + +struct stun_response { + struct sockaddr addr; +}; + +struct stun_header { + uint16_t type; + uint16_t length; + uint32_t cookie; + uint32_t id[3]; +} __attribute((packed)); + +struct stun_packet { + struct stun_header header; + uint8_t data[0]; +} __attribute((packed)); + +#define STUN_CLASS(c0, c1) (((c0) << 4) | ((c1) << 8)) + +#define STUN_CLASS_REQUEST STUN_CLASS(0, 0) +#define STUN_CLASS_INDICATION STUN_CLASS(0, 1) +#define STUN_CLASS_SUCCESS STUN_CLASS(1, 0) +#define STUN_CLASS_ERROR STUN_CLASS(1, 1) + +#define STUN_CLASS_MASK STUN_CLASS(1, 1) + +#define STUN_MESSAGE(msg) (((msg & 0xf10) << 2) | ((msg & 0x70) << 1) | (msg & 0xf)) +#define STUN_MESSAGE_BIND STUN_MESSAGE(1) + +#define STUN_COOKIE 0x2112a442 + +enum { + STUN_ATTR_TYPE_MAPPED_ADDRESS = 0x1, + STUN_ATTR_TYPE_XOR_MAPPED_ADDRESS = 0x20, + STUN_ATTR_TYPE_XOR_MAPPED_ADDRESS2 = 0x8020, +}; + +static inline uint16_t get_unaligned_be16(const uint8_t *buf) +{ + return (buf[0] << 8) | buf[1]; +} + +static inline uint16_t get_unaligned_be32(const uint8_t *buf) +{ + return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; +} + +static int stun_parse_xor_mapped_address(struct stun_response *response, + const uint8_t *buf, int length) +{ + uint8_t fam = buf[1]; + uint16_t port = get_unaligned_be16(&buf[2]); + struct sockaddr_in *sin = (struct sockaddr_in *)&response->addr; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&response->addr; + + + switch (fam) { + case 0x1: + sin->sin_family = AF_INET; + sin->sin_port = htons((port ^ (uint16_t)((STUN_COOKIE & 0xffff0000) >> 16))); + memcpy(&sin->sin_addr.s_addr, buf + 4, 4); + sin->sin_addr.s_addr ^= htonl(STUN_COOKIE); + printf("xor port: %d\n", sin->sin_port); + break; + case 0x2: + sin6->sin6_family = AF_INET6; + sin->sin_port = htons((port ^ (uint16_t)((STUN_COOKIE & 0xffff0000) >> 16))); + memcpy(sin6->sin6_addr.s6_addr, buf + 4, 16); + break; + } + + return 0; +} + +static int stun_parse_mapped_address(struct stun_response *response, + const uint8_t *buf, int length) +{ + uint8_t fam = buf[1]; + uint16_t port = get_unaligned_be16(&buf[2]); + struct sockaddr_in *sin = (struct sockaddr_in *)&response->addr; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&response->addr; + + printf("port: %d\n", port); + + switch (fam) { + case 0x1: + sin->sin_family = AF_INET; + sin->sin_port = htons(port); + memcpy(&sin->sin_addr.s_addr, buf + 4, 4); + break; + case 0x2: + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(port); + memcpy(sin6->sin6_addr.s6_addr, buf + 4, 16); + break; + } + + return 0; +} + +static int stun_parse_response(struct stun_response *response, + const struct stun_packet *packet) +{ + uint16_t attr_type, attr_length; + const uint8_t *buf; + int length = ntohs(packet->header.length); + int ret; + int i = 0; + + if (packet->header.cookie != htonl(STUN_COOKIE)) + return -1; + + if (packet->header.length < 4) + return 0; + + buf = packet->data; + + do { + attr_type = get_unaligned_be16(&buf[i]); + attr_length = get_unaligned_be16(&buf[i + 2]); + i += 4; + + if (i + attr_length > length) + break; + + switch (attr_type) { + case STUN_ATTR_TYPE_MAPPED_ADDRESS: + ret = stun_parse_mapped_address(response, &buf[i], attr_length); + break; + case STUN_ATTR_TYPE_XOR_MAPPED_ADDRESS: + case STUN_ATTR_TYPE_XOR_MAPPED_ADDRESS2: + ret = stun_parse_xor_mapped_address(response, &buf[i], attr_length); + break; + } + + i += attr_length; + + } while (i < length && ret == 0); + + return 0; +} + +static struct stun_packet *stun_packet_alloc(size_t data_size) +{ + return malloc(sizeof(struct stun_packet) + data_size); +} + +int stun_client_resolve(struct stun_client *stun, int sockfd, struct sockaddr *addr) +{ + struct stun_packet *packet = stun_packet_alloc(200); + struct stun_response response; + int ret; + int retries = 4; + int timeout = 500; + struct pollfd pollfd; + + pollfd.events = POLLIN; + pollfd.fd = sockfd; + + packet->header.type = htons(STUN_CLASS_REQUEST | STUN_MESSAGE_BIND); + packet->header.cookie = htonl(STUN_COOKIE); + packet->header.id[0] = 0x12345678; + packet->header.id[1] = 0x12345678; + packet->header.id[2] = 0x12345678; + packet->header.length = 0; + + while (retries--) { + ret = sendto(sockfd, packet, sizeof(struct stun_header) + packet->header.length, + 0, stun->serverinfo->ai_addr, stun->serverinfo->ai_addrlen); + + ret = poll(&pollfd, 1, timeout); + switch (ret) { + case 0: + timeout <<= 1; + case -EINTR: + printf("retry\n"); + continue; + default: + retries = 0; + } + ret = recvfrom(sockfd, packet, 200, 0, NULL, NULL); + } + + if (ret <= 0) + return ret ? ret : -ETIMEDOUT; + + memset(&response, 0, sizeof(response)); + ret = stun_parse_response(&response, packet); + + *addr = response.addr; + + return ret; +} + +struct stun_client *stun_client_alloc(const char *hostname, uint16_t port) +{ + struct addrinfo hints; + struct stun_client *stun; + int ret; + char p[6]; + + + stun = malloc(sizeof(*stun)); + if (!stun) + return NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_NUMERICSERV; + + snprintf(p, sizeof(p), "%d", port); + if ((ret = getaddrinfo(hostname, p, &hints, &stun->serverinfo)) != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); + return NULL; + } + + + return stun; +} + +void stun_client_free(struct stun_client *stun) +{ + freeaddrinfo(stun->serverinfo); + free(stun); +} diff --git a/package/tapi_sip/src/stun.h b/package/tapi_sip/src/stun.h new file mode 100644 index 000000000..2cd61c9f1 --- /dev/null +++ b/package/tapi_sip/src/stun.h @@ -0,0 +1,12 @@ +#ifndef __STUN_H__ +#define __STUN_H__ + +#include <stdint.h> + +struct stun_client; + +struct stun_client *stun_client_alloc(const char *hostname, uint16_t port); +void stun_client_free(struct stun_client *); +int stun_client_resolve(struct stun_client *stun, int sockfd, struct sockaddr *addr); + +#endif diff --git a/package/tapi_sip/src/tapi_agent.c b/package/tapi_sip/src/tapi_agent.c new file mode 100644 index 000000000..93c73c24d --- /dev/null +++ b/package/tapi_sip/src/tapi_agent.c @@ -0,0 +1,105 @@ + +#include <stdbool.h> +#include <stdio.h> +#include <tapi-port.h> + +#include "session.h" +#include "agent.h" +#include "tapi_agent.h" + +static int tapi_agent_invite(struct agent *agent, struct session *session) +{ + struct tapi_agent *tagent = agent_to_tapi_agent(agent); + + if (tagent->session) + return -1; + + tagent->state = TAPI_AGENT_STATE_RINGING; + tapi_port_set_ring(&tagent->port, true); + + tagent->session = session; + + return 0; +} + +static int tapi_agent_accept(struct agent *agent, struct session *session) +{ + return 0; +} + +static int tapi_agent_hangup(struct agent *agent, struct session *session) +{ + struct tapi_agent *tagent = agent_to_tapi_agent(agent); + + switch (tagent->state) { + case TAPI_AGENT_STATE_RINGING: + tapi_port_set_ring(&tagent->port, false); + break; + default: + break; + } + + tagent->state = TAPI_AGENT_STATE_IDLE; + tagent->session = NULL; + + return 0; +} + +static int tapi_agent_get_endpoint(struct agent *agent, struct session *session) +{ + struct tapi_agent *tagent = agent_to_tapi_agent(agent); + return tapi_port_get_endpoint(&tagent->port); +} + +static const struct agent_ops tapi_agent_ops = { + .invite = tapi_agent_invite, + .accept = tapi_agent_accept, + .hangup = tapi_agent_hangup, + .get_endpoint = tapi_agent_get_endpoint, +}; + +static void tapi_agent_event(struct tapi_port *port, struct tapi_event *event, + void *data) +{ + struct tapi_agent *tagent = data; + + if (event->type != TAPI_EVENT_TYPE_HOOK) + return; + + if (!tagent->session) + return; + + if (event->hook.on) { + session_hangup(tagent->session, &tagent->agent); + tagent->state = TAPI_AGENT_STATE_IDLE; + tagent->session = NULL; + } else { + session_accept(tagent->session, &tagent->agent); + tagent->state = TAPI_AGENT_STATE_ACTIVE; + } +} + +void tapi_agent_init(struct tapi_device *tdev, int port, struct tapi_agent *tagent) +{ + int ret; + + tagent->agent.ops = &tapi_agent_ops; + tagent->state = TAPI_AGENT_STATE_IDLE; + tagent->session = NULL; + + ret = tapi_port_open(tdev, port, &tagent->port); + if (ret) { + printf("Failed to open tapi port %d: %d\n", port, ret); + return; + } + + tagent->event_listener.callback = tapi_agent_event; + tagent->event_listener.data = tagent; + + tapi_port_register_event(&tagent->port, &tagent->event_listener); +} + +void tapi_agent_free(struct tapi_agent *tagent) +{ + tapi_port_unregister_event(&tagent->port, &tagent->event_listener); +} diff --git a/package/tapi_sip/src/tapi_agent.h b/package/tapi_sip/src/tapi_agent.h new file mode 100644 index 000000000..12e870121 --- /dev/null +++ b/package/tapi_sip/src/tapi_agent.h @@ -0,0 +1,37 @@ +#ifndef __TAPI_AGENT_H__ +#define __TAPI_AGENT_H__ + +#include "agent.h" +#include <tapi-port.h> + +struct session; + +enum tapi_agent_state { + TAPI_AGENT_STATE_IDLE, + TAPI_AGENT_STATE_RINGING, + TAPI_AGENT_STATE_ACTIVE, +}; + +struct tapi_agent { + struct agent agent; + struct tapi_port port; + struct tapi_port_event_listener event_listener; + + enum tapi_agent_state state; + + struct session *session; +}; + +static inline struct tapi_agent *agent_to_tapi_agent(struct agent *agent) +{ + return container_of(agent, struct tapi_agent, agent); +} + +static inline struct tapi_agent *port_to_tapi_agent(struct tapi_port *port) +{ + return container_of(port, struct tapi_agent, port); +} + +void tapi_agent_init(struct tapi_device *tdev, int port, struct tapi_agent *tagent); + +#endif diff --git a/package/tapi_sip/src/tapi_sip.c b/package/tapi_sip/src/tapi_sip.c new file mode 100644 index 000000000..518c63c3d --- /dev/null +++ b/package/tapi_sip/src/tapi_sip.c @@ -0,0 +1,179 @@ + +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <poll.h> +#include <string.h> + +#include <linux/input.h> +#include "dialdetector.h" + +#include <tapi-ioctl.h> + +#include <tapi-device.h> +#include <tapi-port.h> + +#include "contact.h" +#include "session.h" +#include "sip_client.h" +#include "sip_agent.h" +#include "tapi_agent.h" + +static struct tapi_device dev; +static struct tapi_agent *ports; + +static struct sip_client sip_client; + +static void release_session(struct session *session) +{ + free(session); +} + +static void dial(struct tapi_agent *caller, struct agent *callee) +{ + struct session *session; + + session = session_alloc(&dev, &caller->agent, callee, release_session); + + if (!session) + return; + + caller->session = session; +} + +static void tel_dial(struct tapi_agent *caller, const char *number) +{ + int callee; + + callee = atoi(number) - 1; + + if (callee < 0 || callee > 1) + return; + dial(caller, &ports[callee].agent); +} + +static void sip_dial(struct tapi_agent *caller, const char *identifier) +{ + struct sip_agent *callee; + + callee = sip_client_alloc_agent(&sip_client, identifier); + if (!callee) + return; + + dial(caller, &callee->agent); +} + +static void dial_callback(struct tapi_port *port, size_t num_digits, const unsigned char *digits) +{ + struct tapi_agent *tagent = port_to_tapi_agent(port); + char number[100]; + struct contact *contact; + size_t i; + + if (tagent->state != TAPI_AGENT_STATE_IDLE) + return; + + for (i = 0; i < num_digits; ++i) { + if (digits[0] > 9) + break; + number[i] = digits[i] + '0'; + } + number[i] = '\0'; + + printf("dial callback: %s\n", number); + + contact = contact_get(number); + + if (!contact) + return; + + if (strncmp("tel:", contact->identifier, 4) == 0) { + tel_dial(tagent, contact->identifier + 4); + } else if (strncmp("sip:", contact->identifier, 4) == 0) { + sip_dial(tagent, contact->identifier); + } + tagent->state = TAPI_AGENT_STATE_ACTIVE; +} + +static int incoming_sip_call(struct sip_client *client, + struct sip_agent *caller) +{ + struct tapi_agent *callee = NULL;; + struct session *session; + int i; + + for (i = 0; i < 2; ++i) { + if (ports[i].state == TAPI_AGENT_STATE_IDLE) { + callee = &ports[i]; + break; + } + } + + if (callee == NULL) + return -1; + + session = session_alloc(&dev, &caller->agent, &callee->agent, + release_session); + caller->session = session; + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct dialdetector *dd, *dd2; + struct account *account; + struct sip_client_config config; + const char *interface = "eth0"; + int ret; + int i; + + if (argc > 1) + interface = argv[1]; + + pj_init(); + pjlib_util_init(); + + contacts_init(); + + account = get_account(); + if (!account) { + printf("No account\n"); + return 1; + } + + ret = tapi_device_open(0, &dev); + if (ret) { + printf("Failed to open tapi device: %d\n", ret); + return 1; + } + + ports = calloc(dev.num_ports, sizeof(*ports)); + for (i = 0; i < dev.num_ports; ++i) + tapi_agent_init(&dev, i, &ports[i]); + + dd = dialdetector_alloc(&ports[0].port); + dd->dial_callback = dial_callback; + dd2 = dialdetector_alloc(&ports[1].port); + dd2->dial_callback = dial_callback; + + config.iface = interface; + config.host = account->realm; + config.port = account->sip_port; + config.username = account->username; + config.password = account->password; + + config.stun_host = account->stun_host; + config.stun_port = account->stun_port; + + sip_client_init(&sip_client, &dev, &config); + + sip_client.incoming_call_cb = incoming_sip_call; + + tapi_mainloop(); + + return 0; +} |