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; +}  | 
