commit dc7517121dd89091997e01762d0cc65bf5b33679 Author: Richard Fuchs Date: Fri Jun 1 16:05:22 2012 +0000 remove uuid helper code diff --git a/daemon/Makefile b/daemon/Makefile new file mode 100644 index 000000000..81b1637ff --- /dev/null +++ b/daemon/Makefile @@ -0,0 +1,51 @@ +CC= gcc +CFLAGS= -g -Wall `pkg-config --cflags glib-2.0` `pcre-config --cflags` -fno-strict-aliasing +CFLAGS+= -I/lib/modules/`uname -r`/build/include/ -I../kernel-module/ +CFLAGS+= -D_GNU_SOURCE +CFLAGS+= -DMEDIAPROXY_VERSION="\"$(shell dpkg-parsechangelog -l../debian/changelog | awk '/^Version: / {print $$2}')\"" +CFLAGS+= -DMP_PLUGIN_DIR="\"/usr/lib/mediaproxy-ng\"" + +ifeq ($(DBG),yes) +CFLAGS+= -D__DEBUG=1 +else +CFLAGS+= -O2 +endif + +LDFLAGS= `pkg-config --libs glib-2.0` `pcre-config --libs` +LDFLAGS+= `xmlrpc-c-config client --libs` +LDFLAGS+= -ldl -rdynamic + +SRCS= main.c kernel.c poller.c aux.c control.c streambuf.c call.c control_udp.c redis.c +OBJS= $(SRCS:.c=.o) + + +.PHONY: all dep clean tests debug + +all: + $(MAKE) mediaproxy-ng + +debug: + $(MAKE) DBG=yes all + +tests: + $(MAKE) aux-test poller-test + +dep: .depend + +clean: + rm -f $(OBJS) mediaproxy-ng aux-test poller-test aux-test.o poller-test.o .depend core + +.depend: $(SRCS) Makefile + $(CC) $(CFLAGS) -M $(SRCS) | sed -e 's/:/ .depend:/' > .depend + +mediaproxy-ng: $(OBJS) .depend + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +aux-test: aux.o aux-test.o .depend + $(CC) $(CFLAGS) -o $@ aux-test.o aux.o $(LDFLAGS) + +poller-test: poller.o poller-test.o aux.o .depend + $(CC) $(CFLAGS) -o $@ poller-test.o poller.o aux.o $(LDFLAGS) + + +include .depend diff --git a/daemon/aux-test.c b/daemon/aux-test.c new file mode 100644 index 000000000..52e0d2c4a --- /dev/null +++ b/daemon/aux-test.c @@ -0,0 +1,305 @@ +#include +#include "aux.h" + + +int test[32] = { + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, + 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0xff, + + 0x10ff, 0x20ff, 0x30ff, 0x40ff, 0x50ff, 0x60ff, 0x70ff, 0x80ff, + 0x90ff, 0xa0ff, 0xb0ff, 0xc0ff, 0xd0ff, 0xe0ff, 0xf0ff, 0xffff, +}; + +void exsrx(int x, int exp, unsigned int s, int ex) { + int ret = mybsearch(test, s, sizeof(int), &x, 0, sizeof(x), ex); + if (ret != exp) + fprintf(stderr, "TEST FAILED! params=%u %i; result=%i, expected=%i\n", s, ex, ret, exp); +} + +void exsr1(int x, int exp) { + exsrx(x, exp, 16, 1); +} + +void exsr2(int x, int exp) { + exsrx(x, exp, 15, 1); +} + +void exsr3(int x, int exp) { + exsrx(x, exp, 2, 1); +} + +void exsr4(int x, int exp) { + exsrx(x, exp, 1, 1); +} + +void exsr5(int x, int exp) { + exsrx(x, exp, 32, 1); +} + +void exsr6(int x, int exp) { + exsrx(x, exp, 31, 1); +} + +void exsr7(int x, int exp) { + exsrx(x, exp, 16, 0); +} + +void exsr8(int x, int exp) { + exsrx(x, exp, 15, 0); +} + +void exsr9(int x, int exp) { + exsrx(x, exp, 2, 0); +} + +void exsr10(int x, int exp) { + exsrx(x, exp, 1, 0); +} + +void exsr11(int x, int exp) { + exsrx(x, exp, 32, 0); +} + +void exsr12(int x, int exp) { + exsrx(x, exp, 31, 0); +} + +int main() { + exsr1(0x10, 0); + exsr1(0x20, 1); + exsr1(0x30, 2); + exsr1(0x40, 3); + exsr1(0x50, 4); + exsr1(0x60, 5); + exsr1(0x70, 6); + exsr1(0x80, 7); + exsr1(0x90, 8); + exsr1(0xa0, 9); + exsr1(0xb0, 10); + exsr1(0xc0, 11); + exsr1(0xd0, 12); + exsr1(0xe0, 13); + exsr1(0xf0, 14); + exsr1(0xff, 15); + exsr1(0xffff, -1); + exsr1(0xfe, -1); + + exsr2(0x10, 0); + exsr2(0x20, 1); + exsr2(0x30, 2); + exsr2(0x40, 3); + exsr2(0x50, 4); + exsr2(0x60, 5); + exsr2(0x70, 6); + exsr2(0x80, 7); + exsr2(0x90, 8); + exsr2(0xa0, 9); + exsr2(0xb0, 10); + exsr2(0xc0, 11); + exsr2(0xd0, 12); + exsr2(0xe0, 13); + exsr2(0xf0, 14); + exsr2(0xff, -1); + + exsr3(0x10, 0); + exsr3(0x20, 1); + exsr3(0x30, -1); + + exsr4(0x10, 0); + exsr4(0x20, -1); + + exsr5(0x10, 0); + exsr5(0x20, 1); + exsr5(0x30, 2); + exsr5(0x40, 3); + exsr5(0x50, 4); + exsr5(0x60, 5); + exsr5(0x70, 6); + exsr5(0x80, 7); + exsr5(0x90, 8); + exsr5(0xa0, 9); + exsr5(0xb0, 10); + exsr5(0xc0, 11); + exsr5(0xd0, 12); + exsr5(0xe0, 13); + exsr5(0xf0, 14); + exsr5(0xff, 15); + exsr5(0x10ff, 16); + exsr5(0x20ff, 17); + exsr5(0x30ff, 18); + exsr5(0x40ff, 19); + exsr5(0x50ff, 20); + exsr5(0x60ff, 21); + exsr5(0x70ff, 22); + exsr5(0x80ff, 23); + exsr5(0x90ff, 24); + exsr5(0xa0ff, 25); + exsr5(0xb0ff, 26); + exsr5(0xc0ff, 27); + exsr5(0xd0ff, 28); + exsr5(0xe0ff, 29); + exsr5(0xf0ff, 30); + exsr5(0xffff, 31); + exsr5(0xfff3, -1); + exsr5(0xffffff, -1); + + exsr6(0x10, 0); + exsr6(0x20, 1); + exsr6(0x30, 2); + exsr6(0x40, 3); + exsr6(0x50, 4); + exsr6(0x60, 5); + exsr6(0x70, 6); + exsr6(0x80, 7); + exsr6(0x90, 8); + exsr6(0xa0, 9); + exsr6(0xb0, 10); + exsr6(0xc0, 11); + exsr6(0xd0, 12); + exsr6(0xe0, 13); + exsr6(0xf0, 14); + exsr6(0xff, 15); + exsr6(0x10ff, 16); + exsr6(0x20ff, 17); + exsr6(0x30ff, 18); + exsr6(0x40ff, 19); + exsr6(0x50ff, 20); + exsr6(0x60ff, 21); + exsr6(0x70ff, 22); + exsr6(0x80ff, 23); + exsr6(0x90ff, 24); + exsr6(0xa0ff, 25); + exsr6(0xb0ff, 26); + exsr6(0xc0ff, 27); + exsr6(0xd0ff, 28); + exsr6(0xe0ff, 29); + exsr6(0xf0ff, 30); + exsr6(0xffff, -1); + + + + + + exsr7(0x10, 0); + exsr7(0x20, 1); + exsr7(0x30, 2); + exsr7(0x40, 3); + exsr7(0x50, 4); + exsr7(0x60, 5); + exsr7(0x70, 6); + exsr7(0x80, 7); + exsr7(0x90, 8); + exsr7(0xa0, 9); + exsr7(0xb0, 10); + exsr7(0xc0, 11); + exsr7(0xd0, 12); + exsr7(0xe0, 13); + exsr7(0xf0, 14); + exsr7(0xff, 15); + exsr7(0xffff, -17); + exsr7(0xfe, -16); + exsr7(0x00, -1); + exsr8(0x05, -1); + exsr8(0x15, -2); + + exsr8(0x10, 0); + exsr8(0x20, 1); + exsr8(0x30, 2); + exsr8(0x40, 3); + exsr8(0x50, 4); + exsr8(0x60, 5); + exsr8(0x70, 6); + exsr8(0x80, 7); + exsr8(0x90, 8); + exsr8(0xa0, 9); + exsr8(0xb0, 10); + exsr8(0xc0, 11); + exsr8(0xd0, 12); + exsr8(0xe0, 13); + exsr8(0xf0, 14); + exsr8(0xff, -16); + exsr8(0xffff, -16); + exsr8(0xef, -15); + exsr8(0x00, -1); + exsr8(0x05, -1); + exsr8(0x15, -2); + + exsr9(0x10, 0); + exsr9(0x20, 1); + exsr9(0x30, -3); + + exsr10(0x10, 0); + exsr10(0x20, -2); + + exsr11(0x10, 0); + exsr11(0x20, 1); + exsr11(0x30, 2); + exsr11(0x40, 3); + exsr11(0x50, 4); + exsr11(0x60, 5); + exsr11(0x70, 6); + exsr11(0x80, 7); + exsr11(0x90, 8); + exsr11(0xa0, 9); + exsr11(0xb0, 10); + exsr11(0xc0, 11); + exsr11(0xd0, 12); + exsr11(0xe0, 13); + exsr11(0xf0, 14); + exsr11(0xff, 15); + exsr11(0x10ff, 16); + exsr11(0x20ff, 17); + exsr11(0x30ff, 18); + exsr11(0x40ff, 19); + exsr11(0x50ff, 20); + exsr11(0x60ff, 21); + exsr11(0x70ff, 22); + exsr11(0x80ff, 23); + exsr11(0x90ff, 24); + exsr11(0xa0ff, 25); + exsr11(0xb0ff, 26); + exsr11(0xc0ff, 27); + exsr11(0xd0ff, 28); + exsr11(0xe0ff, 29); + exsr11(0xf0ff, 30); + exsr11(0xffff, 31); + exsr11(0xfff3, -16); + exsr11(0xffffff, -33); + + exsr12(0x10, 0); + exsr12(0x20, 1); + exsr12(0x30, 2); + exsr12(0x40, 3); + exsr12(0x50, 4); + exsr12(0x60, 5); + exsr12(0x70, 6); + exsr12(0x80, 7); + exsr12(0x90, 8); + exsr12(0xa0, 9); + exsr12(0xb0, 10); + exsr12(0xc0, 11); + exsr12(0xd0, 12); + exsr12(0xe0, 13); + exsr12(0xf0, 14); + exsr12(0xff, 15); + exsr12(0x10ff, 16); + exsr12(0x20ff, 17); + exsr12(0x30ff, 18); + exsr12(0x40ff, 19); + exsr12(0x50ff, 20); + exsr12(0x60ff, 21); + exsr12(0x70ff, 22); + exsr12(0x80ff, 23); + exsr12(0x90ff, 24); + exsr12(0xa0ff, 25); + exsr12(0xb0ff, 26); + exsr12(0xc0ff, 27); + exsr12(0xd0ff, 28); + exsr12(0xe0ff, 29); + exsr12(0xf0ff, 30); + exsr12(0xffff, -32); + + printf("all done\n"); + + return 0; +} diff --git a/daemon/aux.c b/daemon/aux.c new file mode 100644 index 000000000..d4b25756b --- /dev/null +++ b/daemon/aux.c @@ -0,0 +1,175 @@ +#include +#include +#include +#include + +#include "aux.h" + + + +#if 0 +#define BSDB(x...) fprintf(stderr, x) +#else +#define BSDB(x...) ((void)0) +#endif + + + +int mybsearch(void *base, unsigned int len, unsigned int size, void *key, unsigned int key_off, unsigned int key_size, int exact) { + unsigned char *cbase = base; + int pos; + unsigned char *cur; + int res; + unsigned int num; + + if (!len) { + BSDB("zero length array\n"); + return -1; + } + + pos = len / 2; + num = pos; + num += 3; + num /= 2; + pos--; + if (pos < 0) + pos = 0; + + BSDB("starting pos=%u, num=%u\n", pos, num); + + for (;;) { + cur = cbase + (pos * size); + res = memcmp(cur + key_off, key, key_size); + BSDB("compare=%i\n", res); + if (!res) + return pos; + if (!num) { + BSDB("nothing found\n"); + if (exact) + return -1; + if (res > 0) /* cur > key */ + return -1 * pos - 1; + return -1 * pos - 2; + } + + if (res < 0) { /* cur < key */ + pos += num; + if (pos >= len) + pos = len - 1; + } + else { + pos -= num; + if (pos < 0) + pos = 0; + } + + BSDB("new pos=%u\n", pos); + + if (num == 1) + num = 0; + else { + num++; + num /= 2; + } + + BSDB("new num=%u\n", num); + } +} + + +GList *g_list_link(GList *list, GList *el) { + el->prev = NULL; + el->next = list; + if (list) + list->prev = el; + return el; +} + + +GQueue *pcre_multi_match(pcre **re, pcre_extra **ree, const char *rex, const char *s, unsigned int num, parse_func f, void *p) { + GQueue *q; + const char *errptr; + int erroff; + unsigned int start, len; + int ovec[60]; + int *ov; + char **el; + unsigned int i; + void *ins; + + if (!*re) { + *re = pcre_compile(rex, PCRE_DOLLAR_ENDONLY | PCRE_DOTALL, &errptr, &erroff, NULL); + if (!*re) + return NULL; + *ree = pcre_study(*re, 0, &errptr); + } + + q = g_queue_new(); + el = malloc(sizeof(*el) * num); + + for (start = 0, len = strlen(s); pcre_exec(*re, *ree, s + start, len - start, 0, 0, ovec, G_N_ELEMENTS(ovec)) > 0; start += ovec[1]) { + for (i = 0; i < num; i++) { + ov = ovec + 2 + i*2; + el[i] = (ov[0] == -1) ? NULL : g_strndup(s + start + ov[0], ov[1] - ov[0]); + } + + if (!f(el, &ins, p)) + g_queue_push_tail(q, ins); + + for (i = 0; i < num; i++) { + if (el[i]) + free(el[i]); + } + } + + free(el); + + return q; +} + + +void strmove(char **d, char **s) { + if (*d) + free(*d); + *d = *s; + *s = strdup(""); +} + +void strdupfree(char **d, const char *s) { + if (*d) + free(*d); + *d = strdup(s); +} + + + +#if !GLIB_CHECK_VERSION(2,14,0) + +void g_string_vprintf(GString *string, const gchar *format, va_list args) { + char *s; + int r; + + r = vasprintf(&s, format, args); + if (r < 0) + return; + + g_string_assign(string, s); + free(s); +} + +void g_queue_clear(GQueue *q) { + GList *l, *n; + + if (!q) + return; + + for (l = q->head; l; l = n) { + n = l->next; + g_list_free_1(l); + } + + q->head = q->tail = NULL; + q->length = 0; +} + +#endif diff --git a/daemon/aux.h b/daemon/aux.h new file mode 100644 index 000000000..738da830b --- /dev/null +++ b/daemon/aux.h @@ -0,0 +1,150 @@ +#ifndef __AUX_H__ +#define __AUX_H__ + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + + +#define OFFSET_OF(t,e) ((unsigned int) (unsigned long) &(((t *) 0)->e)) +#define ZERO(x) memset(&(x), 0, sizeof(x)) + +#define IPF "%u.%u.%u.%u" +#define IPP(x) ((unsigned char *) (&(x)))[0], ((unsigned char *) (&(x)))[1], ((unsigned char *) (&(x)))[2], ((unsigned char *) (&(x)))[3] +#define IP6F "%x:%x:%x:%x:%x:%x:%x:%x" +#define IP6P(x) ntohs(((u_int16_t *) (x))[0]), \ + ntohs(((u_int16_t *) (x))[1]), \ + ntohs(((u_int16_t *) (x))[2]), \ + ntohs(((u_int16_t *) (x))[3]), \ + ntohs(((u_int16_t *) (x))[4]), \ + ntohs(((u_int16_t *) (x))[5]), \ + ntohs(((u_int16_t *) (x))[6]), \ + ntohs(((u_int16_t *) (x))[7]) +#define D6F "["IP6F"]:%u" +#define D6P(x) IP6P((x).sin6_addr.s6_addr), ntohs((x).sin6_port) +#define DF IPF ":%u" +#define DP(x) IPP((x).sin_addr.s_addr), ntohs((x).sin_port) + +#define BIT_ARRAY_DECLARE(name, size) int name[((size) + sizeof(int) * 8 - 1) / (sizeof(int) * 8)] + + + + +typedef int (*parse_func)(char **, void **, void *); + +int mybsearch(void *, unsigned int, unsigned int, void *, unsigned int, unsigned int, int); +GList *g_list_link(GList *, GList *); +GQueue *pcre_multi_match(pcre **, pcre_extra **, const char *, const char *, unsigned int, parse_func, void *); +void strmove(char **, char **); +void strdupfree(char **, const char *); + + +#if !GLIB_CHECK_VERSION(2,14,0) +#define G_QUEUE_INIT { NULL, NULL, 0 } +void g_string_vprintf(GString *string, const gchar *format, va_list args); +void g_queue_clear(GQueue *); +#endif + + +static inline void nonblock(int fd) { + fcntl(fd, F_SETFL, O_NONBLOCK); +} +static inline void reuseaddr(int fd) { + int one = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); +} +static inline void ipv6only(int fd, int yn) { + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yn, sizeof(yn)); +} + +static inline int bit_array_isset(int *name, unsigned int bit) { + return name[(bit) / (sizeof(int) * 8)] & (1 << ((bit) % (sizeof(int) * 8))); +} + +static inline void bit_array_set(int *name, unsigned int bit) { + name[(bit) / (sizeof(int) * 8)] |= 1 << ((bit) % (sizeof(int) * 8)); +} + +static inline void bit_array_clear(int *name, unsigned int bit) { + name[(bit) / (sizeof(int) * 8)] &= ~(1 << ((bit) % (sizeof(int) * 8))); +} + +static inline char chrtoupper(char x) { + return x & 0xdf; +} + +static inline void swap_ptrs(void *a, void *b) { + void *t, **aa, **bb; + aa = a; + bb = b; + t = *aa; + *aa = *bb; + *bb = t; +} + +static inline void in4_to_6(struct in6_addr *o, u_int32_t ip) { + o->s6_addr32[0] = 0; + o->s6_addr32[1] = 0; + o->s6_addr32[2] = htonl(0xffff); + o->s6_addr32[3] = ip; +} + +static inline void smart_ntop(char *o, struct in6_addr *a, size_t len) { + const char *r; + + if (IN6_IS_ADDR_V4MAPPED(a)) + r = inet_ntop(AF_INET, &(a->s6_addr32[3]), o, len); + else + r = inet_ntop(AF_INET6, a, o, len); + + if (!r) + *o = '\0'; +} + +static inline void smart_ntop_p(char *o, struct in6_addr *a, size_t len) { + int l; + + if (IN6_IS_ADDR_V4MAPPED(a)) { + if (!inet_ntop(AF_INET, &(a->s6_addr32[3]), o, len)) + *o = '\0'; + } + else { + *o = '['; + if (!inet_ntop(AF_INET6, a, o+1, len-2)) { + *o = '\0'; + return; + } + l = strlen(o); + o[l] = ']'; + o[l+1] = '\0'; + } +} + +static inline int smart_pton(int af, char *src, void *dst) { + char *p; + int ret; + + if (af == AF_INET6) { + if (src[0] == '[' && (p = strchr(src, ']'))) { + *p = '\0'; + ret = inet_pton(af, src+1, dst); + *p = ']'; + return ret; + } + } + return inet_pton(af, src, dst); +} + + + +#endif diff --git a/daemon/call.c b/daemon/call.c new file mode 100644 index 000000000..7edfdf79e --- /dev/null +++ b/daemon/call.c @@ -0,0 +1,1635 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "call.h" +#include "poller.h" +#include "aux.h" +#include "log.h" +#include "kernel.h" +#include "control.h" +#include "streambuf.h" +#include "redis.h" +#include "xt_MEDIAPROXY.h" + + + +#ifdef __DEBUG +#define DBG(x...) mylog(LOG_DEBUG, x) +#else +#define DBG(x...) ((void)0) +#endif + +#define LOG_PREFIX_C "[%s] " +#define LOG_PREFIX_CI "[%s - %s] " +#define LOG_PARAMS_C(c) (c)->callid +#define LOG_PARAMS_CI(c) (c)->callid, (c)->log_info + + + +static pcre *info_re; +static pcre_extra *info_ree; +static pcre *streams_re; +static pcre_extra *streams_ree; + +static BIT_ARRAY_DECLARE(ports_used, 0x10000); + + + + +static char *rtp_codecs[] = { + [0] = "G711u", + [1] = "1016", + [2] = "G721", + [3] = "GSM", + [4] = "G723", + [5] = "DVI4", + [6] = "DVI4", + [7] = "LPC", + [8] = "G711a", + [9] = "G722", + [10] = "L16", + [11] = "L16", + [14] = "MPA", + [15] = "G728", + [18] = "G729", + [25] = "CelB", + [26] = "JPEG", + [28] = "nv", + [31] = "H261", + [32] = "MPV", + [33] = "MP2T", + [34] = "H263", +}; + + + + + + +static void call_destroy(struct call *); +static void unkernelize(struct peer *); + + + + +static void stream_closed(int fd, void *p) { + struct streamrelay *r = p; + struct call *c; + + c = r->up->up->call; + + mylog(LOG_WARNING, LOG_PREFIX_C "Read error on RTP socket", LOG_PARAMS_C(c)); + + call_destroy(c); +} + + + + +void kernelize(struct callstream *c) { + int i, j; + struct peer *p, *pp; + struct streamrelay *r, *rp; + struct kernel_stream ks; + struct callmaster *cm = c->call->callmaster; + + if (cm->kernelfd < 0 || cm->kernelid < 0) + return; + + mylog(LOG_DEBUG, LOG_PREFIX_C "Kernelizing RTP streams", LOG_PARAMS_C(c->call)); + + ZERO(ks); + + for (i = 0; i < 2; i++) { + p = &c->peers[i]; + pp = &c->peers[i ^ 1]; + + if (p->kernelized) + continue; + + for (j = 0; j < 2; j++) { + r = &p->rtps[j]; + rp = &pp->rtps[j]; + + if (IN6_IS_ADDR_UNSPECIFIED(&r->peer.ip46) || !r->fd_family || !r->peer.port) + continue; + + ks.local_port = r->localport; + ks.tos = cm->tos; + ks.src.port = rp->localport; + ks.dest.port = r->peer.port; + + if (IN6_IS_ADDR_V4MAPPED(&r->peer.ip46)) { + ks.src.family = AF_INET; + ks.src.ipv4 = cm->ipv4; + ks.dest.family = AF_INET; + ks.dest.ipv4 = r->peer.ip46.s6_addr32[3]; + } + else { + ks.src.family = AF_INET6; + ks.src.ipv6 = cm->ipv6; + ks.dest.family = AF_INET6; + ks.dest.ipv6 = r->peer.ip46; + } + + ZERO(r->kstats); + + kernel_add_stream(cm->kernelfd, &ks, 0); + } + + p->kernelized = 1; + } +} + + + + +static int stream_packet(struct streamrelay *r, char *b, int l, struct sockaddr_in6 *fsin) { + struct streamrelay *p, *p2; + struct peer *pe, *pe2; + struct callstream *cs; + int ret; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct msghdr mh; + struct iovec iov; + unsigned char buf[256]; + struct cmsghdr *ch; + struct in_pktinfo *pi; + struct in6_pktinfo *pi6; + struct call *c; + struct callmaster *m; + unsigned char cc; + char addr[64]; + + pe = r->up; + cs = pe->up; + pe2 = &cs->peers[pe->idx ^ 1]; + p = &pe2->rtps[r->idx]; + c = cs->call; + m = c->callmaster; + smart_ntop_p(addr, &fsin->sin6_addr, sizeof(addr)); + + if (p->fd == -1) { + mylog(LOG_WARNING, LOG_PREFIX_C "RTP packet to port %u discarded from %s:%u", + LOG_PARAMS_C(c), r->localport, addr, ntohs(fsin->sin6_port)); + r->stats.errors++; + m->statsps.errors++; + return 0; + } + + if (!pe->confirmed && pe->filled && r->idx == 0) { + if (l < 2) + goto skip; + + if (c->lookup_done && m->poller->now > (c->lookup_done + 3)) { + if (!pe->codec) { + cc = b[1]; + cc &= 0x7f; + if (cc < G_N_ELEMENTS(rtp_codecs)) + pe->codec = rtp_codecs[cc] ? : "unknown"; + else + pe->codec = "unknown"; + } + + mylog(LOG_DEBUG, LOG_PREFIX_C "Confirmed peer information for port %u - %s:%u", + LOG_PARAMS_C(c), r->localport, addr, ntohs(fsin->sin6_port)); + + pe->confirmed = 1; + } + + p2 = &p->up->rtps[p->idx ^ 1]; + p->peer.ip46 = fsin->sin6_addr; + p->peer.port = ntohs(fsin->sin6_port); + p2->peer.ip46 = p->peer.ip46; + p2->peer.port = p->peer.port + ((int) (p2->idx * 2) - 1); + + + + if (pe->confirmed && pe2->confirmed && pe2->filled) + kernelize(cs); + + if (redis_update) + redis_update(c); + } + +skip: + if (IN6_IS_ADDR_UNSPECIFIED(&r->peer.ip46) || !r->peer.port || !r->fd_family) + goto drop; + + ZERO(mh); + mh.msg_control = buf; + mh.msg_controllen = sizeof(buf); + + ch = CMSG_FIRSTHDR(&mh); + ZERO(*ch); + + switch (r->fd_family) { + case AF_INET: + ZERO(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = r->peer.ip46.s6_addr32[3]; + sin.sin_port = htons(r->peer.port); + mh.msg_name = &sin; + mh.msg_namelen = sizeof(sin); + + ch->cmsg_len = CMSG_LEN(sizeof(*pi)); + ch->cmsg_level = IPPROTO_IP; + ch->cmsg_type = IP_PKTINFO; + + pi = (void *) CMSG_DATA(ch); + ZERO(*pi); + pi->ipi_spec_dst.s_addr = m->ipv4; + + mh.msg_controllen = CMSG_SPACE(sizeof(*pi)); + + break; + + case AF_INET6: + ZERO(sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = r->peer.ip46; + sin6.sin6_port = htons(r->peer.port); + mh.msg_name = &sin6; + mh.msg_namelen = sizeof(sin6); + + ch->cmsg_len = CMSG_LEN(sizeof(*pi6)); + ch->cmsg_level = IPPROTO_IPV6; + ch->cmsg_type = IPV6_PKTINFO; + + pi6 = (void *) CMSG_DATA(ch); + ZERO(*pi6); + pi6->ipi6_addr = m->ipv6; + + mh.msg_controllen = CMSG_SPACE(sizeof(*pi6)); + + break; + + default: + abort(); + } + + ZERO(iov); + iov.iov_base = b; + iov.iov_len = l; + + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + + ret = sendmsg(p->fd, &mh, 0); + + if (ret == -1 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { + r->stats.errors++; + m->statsps.errors++; + return -1; + } + +drop: + r->stats.packets++; + r->stats.bytes += l; + m->statsps.packets++; + m->statsps.bytes += l; + r->last = m->poller->now; + + return 0; +} + + + + +static void stream_readable(int fd, void *p) { + struct streamrelay *r = p; + char buf[1024]; + int ret; + struct sockaddr_storage ss; + struct sockaddr_in6 sin6; + struct sockaddr_in *sin; + unsigned int sinlen; + void *sinp; + + for (;;) { + sinlen = sizeof(ss); + ret = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &ss, &sinlen); + + if (ret == 0) + goto err; + else if (ret < 0) { + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) + break; +err: + stream_closed(fd, r); + break; + } + + if (ss.ss_family != r->fd_family) + abort(); + + sinp = &ss; + if (ss.ss_family == AF_INET) { + sin = sinp; + sinp = &sin6; + ZERO(sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = sin->sin_port; + in4_to_6(&sin6.sin6_addr, sin->sin_addr.s_addr); + } + + if (stream_packet(r, buf, ret, sinp)) { + mylog(LOG_WARNING, "Write error on RTP socket"); + call_destroy(r->up->up->call); + return; + } + } +} + + + + + +static int info_parse_func(char **a, void **ret, void *p) { + GHashTable *h = p; + + g_hash_table_replace(h, strdup(a[0]), a[1] ? strdup(a[1]) : NULL); + + return -1; +} + + +static GHashTable *info_parse(const char *s, GHashTable **h) { + GQueue *q; + + q = pcre_multi_match(&info_re, &info_ree, "^([^:,]+)(?::(.*?))?(?:$|,)", s, 2, info_parse_func, *h); + g_queue_free(q); + + return *h; +} + + +static int streams_parse_func(char **a, void **ret, void *p) { + struct stream *st; + u_int32_t ip; + int *i; + + i = p; + st = g_slice_alloc0(sizeof(*st)); + + ip = inet_addr(a[0]); + if (ip == -1) + goto fail; + + in4_to_6(&st->ip46, ip); + st->port = atoi(a[1]); + st->mediatype = strdup(a[2] ? : ""); + st->num = ++(*i); + + if (!st->port) + goto fail; + + *ret = st; + return 0; + +fail: + mylog(LOG_WARNING, "Failed to parse a media stream: %s:%s", a[0], a[1]); + free(st->mediatype); + g_slice_free1(sizeof(*st), st); + return -1; +} + + +static GQueue *streams_parse(const char *s) { + int i; + i = 0; + return pcre_multi_match(&streams_re, &streams_ree, "^([\\d.]+):(\\d+)(?::(.*?))?(?:$|,)", s, 3, streams_parse_func, &i); +} + +static void streams_free(GQueue *q) { + struct stream *s; + + while (q->head) { + s = g_queue_pop_head(q); + free(s->mediatype); + g_slice_free1(sizeof(*s), s); + } + + g_queue_free(q); +} + + + +struct iterator_helper { + GList *del; + struct streamrelay *ports[0x10000]; +}; + + +static void call_timer_iterator(void *key, void *val, void *ptr) { + struct call *c = val; + struct iterator_helper *hlp = ptr; + GList *it; + struct callstream *cs; + int i; + struct peer *p; + struct poller *po; + struct callmaster *cm; + unsigned int check; + + if (!c->callstreams->head) + goto drop; + + cm = c->callmaster; + po = cm->poller; + + for (it = c->callstreams->head; it; it = it->next) { + cs = it->data; + + for (i = 0; i < 2; i++) { + p = &cs->peers[i]; + + hlp->ports[p->rtps[0].localport] = &p->rtps[0]; + hlp->ports[p->rtps[1].localport] = &p->rtps[1]; + + check = cm->timeout; + if (!p->rtps[0].peer.port) + check = cm->silent_timeout; + else if (IN6_IS_ADDR_UNSPECIFIED(&p->rtps[0].peer.ip46)) + check = cm->silent_timeout; + + if (po->now - p->rtps[0].last < check) + goto good; + } + } + + mylog(LOG_INFO, LOG_PREFIX_C "Closing call branch due to timeout", + LOG_PARAMS_C(c)); + +drop: + hlp->del = g_list_prepend(hlp->del, c); + return; + +good: + ; +} + +static void xmlrpc_kill_calls(GList *list, const char *url) { + pid_t parent; + int i; + xmlrpc_env e; + xmlrpc_client *c; + xmlrpc_value *r; + struct call *ca; + GList *csl; + struct callstream *cs; + + parent = fork(); + if (parent) + return; + parent = fork(); + if (parent) + _exit(0); + + for (i = 0; i < 8192; i++) + close(i); + + xmlrpc_env_init(&e); + xmlrpc_client_setup_global_const(&e); + xmlrpc_client_create(&e, XMLRPC_CLIENT_NO_FLAGS, "ngcp-mediaproxy-ng", MEDIAPROXY_VERSION, NULL, 0, &c); + + while (list) { + ca = list->data; + + for (csl = ca->callstreams->head; csl; csl = csl->next) { + cs = csl->data; + if (!cs->peers[1].tag || !*cs->peers[1].tag) + continue; + alarm(2); + xmlrpc_client_call2f(&e, c, url, "di", &r, "(ssss)", + "sbc", "postControlCmd", cs->peers[1].tag, "teardown"); + xmlrpc_DECREF(r); + alarm(0); + } + + list = list->next; + } + + _exit(0); +} + + +#define DS(x) do { \ + if (ke->stats.x < sr->kstats.x) \ + d = 0; \ + else \ + d = ke->stats.x - sr->kstats.x; \ + sr->stats.x += d; \ + m->statsps.x += d; \ + } while (0) +static void callmaster_timer(void *ptr) { + struct callmaster *m = ptr; + struct iterator_helper hlp; + GList *i, *n; + struct call *c; + struct mediaproxy_list_entry *ke; + struct streamrelay *sr; + struct poller *po; + u_int64_t d; + + ZERO(hlp); + po = m->poller; + + g_hash_table_foreach(m->callhash, call_timer_iterator, &hlp); + + memcpy(&m->stats, &m->statsps, sizeof(m->stats)); + ZERO(m->statsps); + + i = kernel_list(m->kernelid); + while (i) { + ke = i->data; + + sr = hlp.ports[ke->target.target_port]; + if (!sr) + goto next; + + DS(packets); + DS(bytes); + DS(errors); + + if (ke->stats.packets != sr->kstats.packets) + sr->last = po->now; + + sr->kstats.packets = ke->stats.packets; + sr->kstats.bytes = ke->stats.bytes; + sr->kstats.errors = ke->stats.errors; + +next: + g_slice_free1(sizeof(*ke), ke); + i = g_list_delete_link(i, i); + } + + if (m->b2b_url) + xmlrpc_kill_calls(hlp.del, m->b2b_url); + + for (i = hlp.del; i; i = n) { + n = i->next; + c = i->data; + call_destroy(c); + g_list_free_1(i); + } +} +#undef DS + + +struct callmaster *callmaster_new(struct poller *p) { + struct callmaster *c; + + c = g_slice_alloc0(sizeof(*c)); + + c->callhash = g_hash_table_new(g_str_hash, g_str_equal); + if (!c->callhash) + goto fail; + c->poller = p; + + poller_timer(p, callmaster_timer, c); + + return c; + +fail: + g_slice_free1(sizeof(*c), c); + return NULL; +} + + + +static int get_port4(struct streamrelay *r, u_int16_t p) { + int fd; + struct sockaddr_in sin; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return -1; + + nonblock(fd); + reuseaddr(fd); + setsockopt(fd, IPPROTO_IP, IP_TOS, &r->up->up->call->callmaster->tos, sizeof(r->up->up->call->callmaster->tos)); + + ZERO(sin); + sin.sin_family = AF_INET; + sin.sin_port = htons(p); + if (bind(fd, (struct sockaddr *) &sin, sizeof(sin))) + goto fail; + + r->fd = fd; + r->fd_family = AF_INET; + + return 0; + +fail: + close(fd); + return -1; +} + +static int get_port6(struct streamrelay *r, u_int16_t p) { + int fd; + struct sockaddr_in6 sin; + + fd = socket(AF_INET6, SOCK_DGRAM, 0); + if (fd < 0) + return -1; + + nonblock(fd); + reuseaddr(fd); +#ifdef IPV6_TCLASS + setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &r->up->up->call->callmaster->tos, sizeof(r->up->up->call->callmaster->tos)); +#else +#warning "Will not set IPv6 traffic class" +#endif + ipv6only(fd, 0); + + ZERO(sin); + sin.sin6_family = AF_INET6; + sin.sin6_port = htons(p); + if (bind(fd, (struct sockaddr *) &sin, sizeof(sin))) + goto fail; + + r->fd = fd; + r->fd_family = AF_INET6; + + return 0; + +fail: + close(fd); + return -1; +} + +static int get_port(struct streamrelay *r, u_int16_t p) { + int ret; + + if (bit_array_isset(ports_used, p)) + return -1; + + if (IN6_IS_ADDR_UNSPECIFIED(&r->up->up->call->callmaster->ipv6)) + ret = get_port4(r, p); + else + ret = get_port6(r, p); + + if (ret) + return ret; + + r->localport = p; + + return 0; +} + + +static void get_port_pair(struct peer *p, int wanted_port) { + struct call *c; + struct callmaster *m; + struct streamrelay *a, *b; + u_int16_t port, min, max; + + c = p->up->call; + m = c->callmaster; + a = &p->rtps[0]; + b = &p->rtps[1]; + + assert(a->fd == -1 && b->fd == -1); + + if (wanted_port > 0) { + if ((wanted_port & 1)) + goto fail; + if (get_port(a, wanted_port)) + goto fail; + if (get_port(b, wanted_port + 1)) + goto fail; + goto reserve; + } + + min = (m->port_min > 0 && m->port_min < 0xfff0) ? m->port_min : 1024; + max = (m->port_max > 0 && m->port_max > min && m->port_max < 0xfff0) ? m->port_max : 0; + + if (!m->lastport) + m->lastport = max; + port = m->lastport + 1; + + for (;;) { + if (port < min) + port = min; + else if (max && port > max) + port = min; + + if (port == m->lastport) + goto fail; + + if ((port & 1)) + goto next; + + if (get_port(a, port)) + goto next; + + port++; + if (get_port(b, port)) + goto tryagain; + + break; + +tryagain: + close(a->fd); +next: + port++; + } + + m->lastport = port; + mylog(LOG_DEBUG, LOG_PREFIX_CI "Opened ports %u/%u for RTP", + LOG_PARAMS_CI(c), a->localport, b->localport); + +reserve: + bit_array_set(ports_used, a->localport); + bit_array_set(ports_used, b->localport); + + return; + +fail: + mylog(LOG_ERR, LOG_PREFIX_CI "Failed to get RTP port pair", LOG_PARAMS_CI(c)); + if (a->fd != -1) + close(a->fd); + if (b->fd != -1) + close(b->fd); + a->fd = b->fd = -1; +} + + + +static int setup_peer(struct peer *p, struct stream *s, const char *tag) { + struct streamrelay *a, *b; + struct callstream *cs; + int i; + + cs = p->up; + a = &p->rtps[0]; + b = &p->rtps[1]; + + if (a->peer_advertised.port != s->port || !IN6_ARE_ADDR_EQUAL(&a->peer_advertised.ip46, &s->ip46)) { + cs->peers[0].confirmed = 0; + unkernelize(&cs->peers[0]); + cs->peers[1].confirmed = 0; + unkernelize(&cs->peers[1]); + } + + a->peer.ip46 = s->ip46; + b->peer.ip46 = s->ip46; + a->peer.port = b->peer.port = s->port; + if (b->peer.port) + b->peer.port++; + a->peer_advertised = a->peer; + b->peer_advertised = b->peer; + + for (i = 0; i < 2; i++) { + switch (s->direction[i]) { + case DIR_INTERNAL: + cs->peers[i ^ p->idx].desired_family = AF_INET; + break; + case DIR_EXTERNAL: + cs->peers[i ^ p->idx].desired_family = AF_INET6; + break; + default: + break; + } + } + + strdupfree(&p->mediatype, s->mediatype); + strdupfree(&p->tag, tag); + p->filled = 1; + + return 0; +} + +static void steal_peer(struct peer *dest, struct peer *src) { + struct streamrelay *r; + int i; + struct poller_item pi; + struct streamrelay *sr, *srs; + struct call *c; + struct poller *po; + + ZERO(pi); + r = &src->rtps[0]; + c = src->up->call; + po = c->callmaster->poller; + + mylog(LOG_DEBUG, LOG_PREFIX_CI "Re-using existing open RTP port %u", + LOG_PARAMS_CI(c), r->localport); + + dest->confirmed = 0; + unkernelize(dest); + src->confirmed = 0; + unkernelize(src); + + dest->filled = 1; + strmove(&dest->mediatype, &src->mediatype); + strmove(&dest->tag, &src->tag); + //dest->kernelized = src->kernelized; + //src->kernelized = 0; + dest->desired_family = src->desired_family; + + for (i = 0; i < 2; i++) { + sr = &dest->rtps[i]; + srs = &src->rtps[i]; + + if (sr->fd != -1) { + mylog(LOG_DEBUG, LOG_PREFIX_CI "Closing port %u in favor of re-use", + LOG_PARAMS_CI(c), sr->localport); + close(sr->fd); + bit_array_clear(ports_used, sr->localport); + poller_del_item(po, sr->fd); + } + + sr->fd = srs->fd; + sr->fd_family = srs->fd_family; + + sr->peer.ip46 = srs->peer.ip46; + sr->peer.port = srs->peer.port; + sr->peer_advertised.ip46 = srs->peer_advertised.ip46; + sr->peer_advertised.port = srs->peer_advertised.port; + + sr->localport = srs->localport; + + + srs->fd = -1; + srs->fd_family = 0; + ZERO(srs->peer.ip46); + srs->peer.port = 0; + srs->localport = 0; + ZERO(srs->peer_advertised.ip46); + srs->peer_advertised.port = 0; + + pi.fd = sr->fd; + pi.ptr = sr; + pi.readable = stream_readable; + pi.closed = stream_closed; + + poller_update_item(po, &pi); + } +} + + +void callstream_init(struct callstream *s, struct call *ca, int port1, int port2, int num) { + int i, j, tport; + struct peer *p; + struct streamrelay *r; + struct poller_item pi; + struct poller *po; + + po = ca->callmaster->poller; + + ZERO(*s); + ZERO(pi); + + s->call = ca; + DBG("setting new callstream num to %i", num); + s->num = num; + + for (i = 0; i < 2; i++) { + p = &s->peers[i]; + + p->idx = i; + p->up = s; + p->tag = strdup(""); + p->mediatype = strdup(""); + + for (j = 0; j < 2; j++) { + r = &p->rtps[j]; + + r->fd = -1; + r->idx = j; + r->up = p; + r->last = po->now; + } + + tport = (i == 0) ? port1 : port2; + + if (tport >= 0) { + get_port_pair(p, tport); + + for (j = 0; j < 2; j++) { + r = &p->rtps[j]; + + pi.fd = r->fd; + pi.ptr = r; + pi.readable = stream_readable; + pi.closed = stream_closed; + + poller_add_item(po, &pi); + } + } + } +} + + + +static int call_streams(struct call *c, GQueue *s, const char *tag, int opmode) { + GQueue *q; + GList *i, *l; + struct stream *t; + int x; + struct streamrelay *r; + struct callstream *cs, *cs_o; + struct peer *p, *p2; + int ret = 1; + + q = g_queue_new(); /* new callstreams list */ + + if (!tag) + tag = ""; + + for (i = s->head; i; i = i->next) { + t = i->data; + + p = NULL; + + /* look for an existing call stream with identical parameters */ + for (l = c->callstreams->head; l; l = l->next) { + cs_o = l->data; + for (x = 0; x < 2; x++) { + r = &cs_o->peers[x].rtps[0]; + DBG("comparing new ["IP6F"]:%u/%s to old ["IP6F"]:%u/%s", + IP6P(&t->ip46), t->port, tag, + IP6P(&r->peer_advertised.ip46), r->peer_advertised.port, cs_o->peers[x].tag); + + if (!IN6_ARE_ADDR_EQUAL(&r->peer_advertised.ip46, &t->ip46)) + continue; + if (r->peer_advertised.port != t->port) + continue; + if (strcmp(cs_o->peers[x].tag, tag)) + continue; + DBG("found existing call stream to steal"); + goto found; + } + } + + /* not found */ + r = NULL; + cs_o = NULL; + l = NULL; + +found: + + if (!opmode) { /* request */ + DBG("creating new callstream"); + + cs = g_slice_alloc(sizeof(*cs)); + + if (!r) { + /* nothing found to re-use, open new ports */ + callstream_init(cs, c, 0, 0, t->num); + p = &cs->peers[0]; + setup_peer(p, t, tag); + } + else { + /* re-use, so don't open new ports */ + callstream_init(cs, c, -1, -1, t->num); + if (r->up->idx == 0) { + /* request/lookup came in the same order as before */ + steal_peer(&cs->peers[0], &cs_o->peers[0]); + steal_peer(&cs->peers[1], &cs_o->peers[1]); + } + else { + /* reversed request/lookup */ + steal_peer(&cs->peers[0], &cs_o->peers[1]); + steal_peer(&cs->peers[1], &cs_o->peers[0]); + } + } + + g_queue_push_tail(q, cs); + ZERO(c->lookup_done); + continue; + } + + /* lookup */ + for (l = c->callstreams->head; l; l = l->next) { + cs = l->data; + DBG("hunting for callstream, %i <> %i", cs->num, t->num); + if (cs->num != t->num) + continue; + goto got_cs; + } + + mylog(LOG_WARNING, LOG_PREFIX_CI "Got LOOKUP, but no usable callstreams found", + LOG_PARAMS_CI(c)); + break; + +got_cs: + g_queue_delete_link(c->callstreams, l); + p = &cs->peers[1]; + p2 = &cs->peers[0]; + + if (c->lookup_done && r) { + /* duplicate/stray lookup. don't do anything except replying with something + we already have. check whether the direction is reversed or not and return + the appropriate details. if no matching stream was found, results are + undefined. */ + DBG("double lookup"); + if (p == r->up) + goto skip; + if (p2 == r->up) { + ret = -1; + goto skip; + } + } + + + if (r && p == r->up) { + /* best case, nothing to do */ + DBG("case 1"); + ; + } + else if (r && cs_o != cs) { + /* found something, but it's linked to a different stream */ + DBG("case 2"); + steal_peer(p, r->up); + } + else if (!r && !p->filled) { + /* nothing found to steal, but this end is open */ + DBG("case 3"); + setup_peer(p, t, tag); + } + else { + /* nothing found to steal and this end is used */ + /* need a new call stream after all */ + DBG("case 4"); + cs_o = cs; + cs = g_slice_alloc(sizeof(*cs)); + callstream_init(cs, c, 0, 0, t->num); + steal_peer(&cs->peers[0], &cs_o->peers[0]); + p = &cs->peers[1]; + setup_peer(p, t, tag); + g_queue_push_tail(c->callstreams, cs_o); + } + + time(&c->lookup_done); + +skip: + g_queue_push_tail(q, p->up); + } + + ret = ret * q->length; + + if (!q->head) + g_queue_free(q); + else { + if (c->callstreams->head) { + q->tail->next = c->callstreams->head; + c->callstreams->head->prev = q->tail; + q->tail = c->callstreams->tail; + q->length += c->callstreams->length; + c->callstreams->head = c->callstreams->tail = NULL; + c->callstreams->length = 0; + } + g_queue_free(c->callstreams); + c->callstreams = q; + } + + return ret; +} + + + + +static void unkernelize(struct peer *p) { + struct streamrelay *r; + int i; + + if (!p->kernelized) + return; + + for (i = 0; i < 2; i++) { + r = &p->rtps[i]; + + kernel_del_stream(p->up->call->callmaster->kernelfd, r->localport); + + } + + p->kernelized = 0; +} + + + +static void kill_callstream(struct callstream *s) { + int i, j; + struct peer *p; + struct streamrelay *r; + + for (i = 0; i < 2; i++) { + p = &s->peers[i]; + + unkernelize(p); + + free(p->tag); + free(p->mediatype); + + for (j = 0; j < 2; j++) { + r = &p->rtps[j]; + + if (r->fd != -1) { + close(r->fd); + bit_array_clear(ports_used, r->localport); + } + poller_del_item(s->call->callmaster->poller, r->fd); + } + } + + g_slice_free1(sizeof(*s), s); +} + +static void call_destroy(struct call *c) { + struct callmaster *m = c->callmaster; + struct callstream *s; + + g_hash_table_remove(m->callhash, c->callid); + + if (redis_delete) + redis_delete(c); + + free(c->callid); + g_hash_table_destroy(c->infohash); + g_hash_table_destroy(c->branches); + if (c->calling_agent) + free(c->calling_agent); + if (c->called_agent) + free(c->called_agent); + + while (c->callstreams->head) { + s = g_queue_pop_head(c->callstreams); + kill_callstream(s); + } + g_queue_free(c->callstreams); + + g_slice_free1(sizeof(*c), c); +} + + + +static char *streams_print(GQueue *s, unsigned int num, unsigned int off, const char *prefix, int format) { + GString *o; + int i; + GList *l; + struct callstream *t; + struct streamrelay *x; + char ips[64]; + u_int32_t ip4; + int other_off; + char af; + + o = g_string_new(""); + if (prefix) + g_string_append_printf(o, "%s ", prefix); + + if (!s->head) + goto out; + + t = s->head->data; + other_off = (off == 0) ? 1 : 0; + + if (t->peers[other_off].desired_family == AF_INET + || (t->peers[other_off].desired_family == 0 + && IN6_IS_ADDR_V4MAPPED(&t->peers[other_off].rtps[0].peer.ip46)) + || IN6_IS_ADDR_UNSPECIFIED(&t->call->callmaster->ipv6)) { + ip4 = t->peers[off].rtps[0].peer.ip46.s6_addr32[3]; + if (!ip4) + strcpy(ips, "0.0.0.0"); + else if (t->call->callmaster->adv_ipv4) + sprintf(ips, IPF, IPP(t->call->callmaster->adv_ipv4)); + else + sprintf(ips, IPF, IPP(t->call->callmaster->ipv4)); + + af = '4'; + } + else { + if (IN6_IS_ADDR_UNSPECIFIED(&t->peers[off].rtps[0].peer.ip46)) + strcpy(ips, "::"); + else if (!IN6_IS_ADDR_UNSPECIFIED(&t->call->callmaster->adv_ipv6)) + inet_ntop(AF_INET6, &t->call->callmaster->adv_ipv6, ips, sizeof(ips)); + else + inet_ntop(AF_INET6, &t->call->callmaster->ipv6, ips, sizeof(ips)); + + af = '6'; + } + + if (format == 0) + g_string_append(o, ips); + + for (i = 0, l = s->head; i < num && l; i++, l = l->next) { + t = l->data; + x = &t->peers[off].rtps[0]; + g_string_append_printf(o, (format == 1) ? "%u " : " %u", x->localport); + } + + if (format == 1) + g_string_append_printf(o, "%s %c", ips, af); + +out: + g_string_append(o, "\n"); + + return g_string_free(o, FALSE); +} + +static gboolean g_str_equal0(gconstpointer a, gconstpointer b) { + if (!a) { + if (!b) + return TRUE; + return FALSE; + } + if (!b) + return FALSE; + return g_str_equal(a, b); +} + +static guint g_str_hash0(gconstpointer v) { + if (!v) + return 0; + return g_str_hash(v); +} + +static struct call *call_create(const char *callid, struct callmaster *m) { + struct call *c; + + mylog(LOG_NOTICE, LOG_PREFIX_C "Creating new call", + callid); /* XXX will spam syslog on recovery from DB */ + c = g_slice_alloc0(sizeof(*c)); + c->callmaster = m; + c->callid = strdup(callid); + c->callstreams = g_queue_new(); + c->created = m->poller->now; + c->infohash = g_hash_table_new_full(g_str_hash, g_str_equal, free, free); + c->branches = g_hash_table_new_full(g_str_hash0, g_str_equal0, free, NULL); + return c; +} + +struct call *call_get_or_create(const char *callid, const char *viabranch, struct callmaster *m) { + struct call *c; + + c = g_hash_table_lookup(m->callhash, callid); + if (!c) { + /* completely new call-id, create call */ + c = call_create(callid, m); + g_hash_table_insert(m->callhash, c->callid, c); + } + + if (viabranch && !g_hash_table_lookup(c->branches, viabranch)) + g_hash_table_insert(c->branches, strdup(viabranch), (void *) 0x1); + + return c; +} + +static int addr_parse_udp(struct stream *st, const char **out) { + u_int32_t ip4; + const char *cp; + char c; + int i; + + ZERO(*st); + if (out[RE_UDP_UL_ADDR4] && *out[RE_UDP_UL_ADDR4]) { + ip4 = inet_addr(out[RE_UDP_UL_ADDR4]); + if (ip4 == -1) + goto fail; + in4_to_6(&st->ip46, ip4); + } + else if (out[RE_UDP_UL_ADDR6] && *out[RE_UDP_UL_ADDR6]) { + if (inet_pton(AF_INET6, out[RE_UDP_UL_ADDR6], &st->ip46) != 1) + goto fail; + } + else + goto fail; + + st->port = atoi(out[RE_UDP_UL_PORT]); + st->mediatype = "unknown"; + if (!st->port && strcmp(out[RE_UDP_UL_PORT], "0")) + goto fail; + + if (out[RE_UDP_UL_FLAGS] && *out[RE_UDP_UL_FLAGS]) { + i = 0; + for (cp =out[RE_UDP_UL_FLAGS]; *cp && i < 2; cp++) { + c = chrtoupper(*cp); + if (c == 'E') + st->direction[i++] = DIR_EXTERNAL; + else if (c == 'I') + st->direction[i++] = DIR_INTERNAL; + } + } + + if (out[RE_UDP_UL_NUM] && *out[RE_UDP_UL_NUM]) + st->num = atoi(out[RE_UDP_UL_NUM]); + if (!st->num) + st->num = 1; + + return 0; +fail: + return -1; +} + +char *call_update_udp(const char **out, struct callmaster *m) { + struct call *c; + GQueue q = G_QUEUE_INIT; + struct stream st; + int num; + char *ret; + + c = call_get_or_create(out[RE_UDP_UL_CALLID], out[RE_UDP_UL_VIABRANCH], m); + c->log_info = out[RE_UDP_UL_VIABRANCH]; + strdupfree(&c->calling_agent, "UNKNOWN(udp)"); + + if (addr_parse_udp(&st, out)) + goto fail; + + g_queue_push_tail(&q, &st); + num = call_streams(c, &q, out[RE_UDP_UL_FROMTAG], 0); + + g_queue_clear(&q); + + if (redis_update) + redis_update(c); + + ret = streams_print(c->callstreams, 1, (num >= 0) ? 0 : 1, out[RE_UDP_COOKIE], 1); + mylog(LOG_INFO, LOG_PREFIX_CI "Returning to SIP proxy: %s", LOG_PARAMS_CI(c), ret); + c->log_info = NULL; + return ret; + +fail: + mylog(LOG_WARNING, "Failed to parse a media stream: %s/%s:%s", out[RE_UDP_UL_ADDR4], out[RE_UDP_UL_ADDR6], out[RE_UDP_UL_PORT]); + asprintf(&ret, "%s E8\n", out[RE_UDP_COOKIE]); + c->log_info = NULL; + return ret; +} + +char *call_lookup_udp(const char **out, struct callmaster *m) { + struct call *c; + GQueue q = G_QUEUE_INIT; + struct stream st; + int num; + char *ret; + + c = g_hash_table_lookup(m->callhash, out[RE_UDP_UL_CALLID]); + if (!c || !g_hash_table_lookup(c->branches, out[RE_UDP_UL_VIABRANCH])) { + mylog(LOG_WARNING, LOG_PREFIX_CI "Got UDP LOOKUP for unknown call-id or unknown via-branch", + out[RE_UDP_UL_CALLID], out[RE_UDP_UL_VIABRANCH]); + asprintf(&ret, "%s 0 " IPF "\n", out[RE_UDP_COOKIE], IPP(m->ipv4)); + return ret; + } + + c->log_info = out[RE_UDP_UL_CALLID]; + strdupfree(&c->called_agent, "UNKNOWN(udp)"); + + if (addr_parse_udp(&st, out)) + goto fail; + + g_queue_push_tail(&q, &st); + num = call_streams(c, &q, out[RE_UDP_UL_TOTAG], 1); + + g_queue_clear(&q); + + if (redis_update) + redis_update(c); + + ret = streams_print(c->callstreams, 1, (num >= 0) ? 1 : 0, out[RE_UDP_COOKIE], 1); + mylog(LOG_INFO, LOG_PREFIX_CI "Returning to SIP proxy: %s", LOG_PARAMS_CI(c), ret); + c->log_info = NULL; + return ret; + +fail: + mylog(LOG_WARNING, "Failed to parse a media stream: %s/%s:%s", out[RE_UDP_UL_ADDR4], out[RE_UDP_UL_ADDR6], out[RE_UDP_UL_PORT]); + asprintf(&ret, "%s E8\n", out[RE_UDP_COOKIE]); + c->log_info = NULL; + return ret; +} + +char *call_request(const char **out, struct callmaster *m) { + struct call *c; + GQueue *s; + int num; + char *ret; + + c = call_get_or_create(out[RE_TCP_RL_CALLID], NULL, m); + + strdupfree(&c->calling_agent, (out[RE_TCP_RL_AGENT] && *out[RE_TCP_RL_AGENT]) ? out[RE_TCP_RL_AGENT] : "UNKNOWN"); + info_parse(out[RE_TCP_RL_INFO], &c->infohash); + s = streams_parse(out[RE_TCP_RL_STREAMS]); + num = call_streams(c, s, g_hash_table_lookup(c->infohash, "fromtag"), 0); + streams_free(s); + + if (redis_update) + redis_update(c); + + ret = streams_print(c->callstreams, abs(num), (num >= 0) ? 0 : 1, NULL, 0); + mylog(LOG_INFO, LOG_PREFIX_CI "Returning to SIP proxy: %s", LOG_PARAMS_CI(c), ret); + return ret; +} + +char *call_lookup(const char **out, struct callmaster *m) { + struct call *c; + GQueue *s; + int num; + char *ret; + + c = g_hash_table_lookup(m->callhash, out[RE_TCP_RL_CALLID]); + if (!c) { + mylog(LOG_WARNING, LOG_PREFIX_C "Got LOOKUP for unknown call-id", out[RE_TCP_RL_CALLID]); + return NULL; + } + + strdupfree(&c->called_agent, out[RE_TCP_RL_AGENT] ? : "UNKNOWN"); + info_parse(out[RE_TCP_RL_INFO], &c->infohash); + s = streams_parse(out[RE_TCP_RL_STREAMS]); + num = call_streams(c, s, g_hash_table_lookup(c->infohash, "totag"), 1); + streams_free(s); + + if (redis_update) + redis_update(c); + + ret = streams_print(c->callstreams, abs(num), (num >= 0) ? 1 : 0, NULL, 0); + mylog(LOG_INFO, LOG_PREFIX_CI "Returning to SIP proxy: %s", LOG_PARAMS_CI(c), ret); + return ret; +} + +char *call_delete_udp(const char **out, struct callmaster *m) { + struct call *c; + char *ret; + struct callstream *cs; + GList *l; + int i; + struct peer *p, *px; + + DBG("got delete for callid '%s' and viabranch '%s'", + out[RE_UDP_D_CALLID], out[RE_UDP_D_VIABRANCH]); + + c = g_hash_table_lookup(m->callhash, out[RE_UDP_D_CALLID]); + if (!c) { + mylog(LOG_INFO, LOG_PREFIX_C "Call-ID to delete not found", out[RE_UDP_D_CALLID]); + goto err; + } + c->log_info = out[RE_UDP_D_VIABRANCH]; + + if (out[RE_UDP_D_FROMTAG] && *out[RE_UDP_D_FROMTAG]) { + for (l = c->callstreams->head; l; l = l->next) { + cs = l->data; + for (i = 0; i < 2; i++) { + p = &cs->peers[i]; + if (!p->tag) + continue; + if (strcmp(p->tag, out[RE_UDP_D_FROMTAG])) + continue; + if (!out[RE_UDP_D_TOTAG] || !*out[RE_UDP_D_TOTAG]) + goto tag_match; + + px = &cs->peers[i ^ 1]; + if (!px->tag) + continue; + if (strcmp(px->tag, out[RE_UDP_D_TOTAG])) + continue; + + goto tag_match; + } + } + } + + mylog(LOG_INFO, LOG_PREFIX_C "Tags didn't match for delete message, ignoring", c->callid); + goto err; + +tag_match: + if (out[RE_UDP_D_VIABRANCH] && *out[RE_UDP_D_VIABRANCH]) { + if (!g_hash_table_remove(c->branches, out[RE_UDP_D_VIABRANCH])) { + mylog(LOG_INFO, LOG_PREFIX_CI "Branch to delete doesn't exist", c->callid, out[RE_UDP_D_VIABRANCH]); + goto err; + } + + mylog(LOG_INFO, LOG_PREFIX_CI "Branch deleted", LOG_PARAMS_CI(c)); + if (g_hash_table_size(c->branches)) + goto success; + else + DBG("no branches left, deleting full call"); + } + + mylog(LOG_INFO, LOG_PREFIX_C "Deleting full call", c->callid); + call_destroy(c); + + +success: + asprintf(&ret, "%s 0\n", out[RE_UDP_COOKIE]); + goto out; + +err: + + asprintf(&ret, "%s E8\n", out[RE_UDP_COOKIE]); + goto out; + +out: + if (c) + c->log_info = NULL; + return ret; +} + +void call_delete(const char **out, struct callmaster *m) { + struct call *c; + + c = g_hash_table_lookup(m->callhash, out[RE_TCP_D_CALLID]); + if (!c) + return; + + /* delete whole list, as we don't have branches in tcp controller */ + call_destroy(c); +} + + + +static void call_status_iterator(void *key, void *val, void *ptr) { + struct call *c = val; + struct control_stream *s = ptr; + GList *l; + struct callstream *cs; + struct peer *p; + struct streamrelay *r1, *r2; + struct streamrelay *rx1, *rx2; + struct callmaster *m; + char addr1[64], addr2[64], addr3[64]; + + m = c->callmaster; + + /* TODO: only called for tcp controller, so no linked list of calls? */ + + streambuf_printf(s->outbuf, "session %s %s %s %s %s %i\n", + c->callid, + (char *) g_hash_table_lookup(c->infohash, "from"), + (char *) g_hash_table_lookup(c->infohash, "to"), + c->calling_agent, c->called_agent, + (int) (m->poller->now - c->created)); + + for (l = c->callstreams->head; l; l = l->next) { + cs = l->data; + + p = &cs->peers[0]; + r1 = &p->rtps[0]; + r2 = &cs->peers[1].rtps[0]; + rx1 = &p->rtps[1]; + rx2 = &cs->peers[1].rtps[1]; + + if (r1->fd == -1 || r2->fd == -1) + continue; + + smart_ntop_p(addr1, &r1->peer.ip46, sizeof(addr1)); + smart_ntop_p(addr2, &r2->peer.ip46, sizeof(addr2)); + if (IN6_IS_ADDR_V4MAPPED(&r1->peer.ip46)) + inet_ntop(AF_INET, &m->ipv4, addr3, sizeof(addr3)); + else + smart_ntop_p(addr3, &m->ipv6, sizeof(addr3)); + + streambuf_printf(s->outbuf, "stream %s:%u %s:%u %s:%u %llu/%llu/%llu %s %s %s %i\n", + addr1, r1->peer.port, + addr2, r2->peer.port, + addr3, r1->localport, + (long long unsigned int) r1->stats.bytes + rx1->stats.bytes, + (long long unsigned int) r2->stats.bytes + rx2->stats.bytes, + (long long unsigned int) r1->stats.bytes + rx1->stats.bytes + r2->stats.bytes + rx2->stats.bytes, + "active", + p->codec ? : "unknown", + p->mediatype, (int) (m->poller->now - r1->last)); + } + +} + +void calls_status(struct callmaster *m, struct control_stream *s) { + streambuf_printf(s->outbuf, "proxy %u %llu/%llu/%llu\n", + g_hash_table_size(m->callhash), + (long long unsigned int) m->stats.bytes, + (long long unsigned int) m->stats.bytes - m->stats.errors, + (long long unsigned int) m->stats.bytes * 2 - m->stats.errors); + + g_hash_table_foreach(m->callhash, call_status_iterator, s); +} + + + + +static void calls_dump_iterator(void *key, void *val, void *ptr) { + struct call *c = val; + + if (redis_update) + redis_update(c); +} + +void calls_dump_redis(struct callmaster *m) { + if (!m->redis) + return; + + mylog(LOG_DEBUG, "Start dumping all call data to Redis...\n"); + redis_wipe(m); + g_hash_table_foreach(m->callhash, calls_dump_iterator, NULL); + mylog(LOG_DEBUG, "Finished dumping all call data to Redis\n"); +} diff --git a/daemon/call.h b/daemon/call.h new file mode 100644 index 000000000..c41dff302 --- /dev/null +++ b/daemon/call.h @@ -0,0 +1,138 @@ +#ifndef __CALL_H__ +#define __CALL_H__ + + + + +#include +#include +#include + +#include "control.h" +#include "control_udp.h" + +struct poller; +struct control_stream; + + + +struct peer; +struct callstream; +struct call; +struct callmaster; +struct redis; + + + + +struct stats { + u_int64_t packets; + u_int64_t bytes; + u_int64_t errors; +}; + +struct stream { + struct in6_addr ip46; + u_int16_t port; + char *mediatype; + enum { + DIR_UNKNOWN = 0, + DIR_INTERNAL, + DIR_EXTERNAL, + } direction[2]; + int num; +}; +struct streamrelay { + int fd; + int fd_family; + struct stream peer; + struct stream peer_advertised; + u_int16_t localport; + unsigned char idx; + struct peer *up; + struct stats stats; + struct stats kstats; + time_t last; +}; +struct peer { + struct streamrelay rtps[2]; + char *tag; + char *mediatype; + char *codec; + unsigned char idx; + struct callstream *up; + int desired_family; + int kernelized:1; + int filled:1; + int confirmed:1; +}; +struct callstream { + struct peer peers[2]; + struct call *call; + int num; +}; + +struct call { + struct callmaster *callmaster; + + GQueue *callstreams; + + char *callid; + char redis_uuid[37]; + time_t created; + char *calling_agent; + char *called_agent; + GHashTable *infohash; + GHashTable *branches; + time_t lookup_done; + + const char *log_info; /* branch */ +}; + +struct callmaster { + GHashTable *callhash; + u_int16_t lastport; + struct stats statsps; + struct stats stats; + + struct poller *poller; + struct redis *redis; + int kernelfd; + unsigned int kernelid; + u_int32_t ipv4; + u_int32_t adv_ipv4; + struct in6_addr ipv6; + struct in6_addr adv_ipv6; + int port_min; + int port_max; + unsigned int timeout; + unsigned int silent_timeout; + char *b2b_url; + unsigned char tos; +}; + + + + +struct callmaster *callmaster_new(struct poller *); + + + +char *call_request(const char **, struct callmaster *); +char *call_update_udp(const char **, struct callmaster *); +char *call_lookup(const char **, struct callmaster *); +char *call_lookup_udp(const char **, struct callmaster *); +void call_delete(const char **, struct callmaster *); +char *call_delete_udp(const char **, struct callmaster *); + +void calls_status(struct callmaster *, struct control_stream *); + +void calls_dump_redis(struct callmaster *); + +struct call *call_get_or_create(const char *callid, const char *viabranch, struct callmaster *m); +void callstream_init(struct callstream *s, struct call *ca, int port1, int port2, int num); +void kernelize(struct callstream *c); + + + +#endif diff --git a/daemon/control.c b/daemon/control.c new file mode 100644 index 000000000..e007c0320 --- /dev/null +++ b/daemon/control.c @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include +#include + +#include "control.h" +#include "poller.h" +#include "aux.h" +#include "streambuf.h" +#include "log.h" +#include "call.h" + +static pcre *parse_re; +static pcre_extra *parse_ree; + +static void control_stream_closed(int fd, void *p) { + struct control_stream *s = p; + struct control *c; + + mylog(LOG_INFO, "Control connection from " DF " closed", DP(s->inaddr)); + + c = s->control; + + c->stream_head = g_list_remove_link(c->stream_head, &s->link); + + close(fd); + if (poller_del_item(s->poller, fd)) + abort(); + + streambuf_destroy(s->inbuf); + streambuf_destroy(s->outbuf); + free(s); +} + + +static void control_list(struct control *c, struct control_stream *s) { + struct control_stream *i; + + for (i = (void *) c->stream_head; i; i = (void *) i->link.next) + streambuf_printf(s->outbuf, DF "\n", DP(s->inaddr)); + + streambuf_printf(s->outbuf, "End.\n"); +} + + +static int control_stream_parse(struct control_stream *s, char *line) { + const char *errptr; + int erroff; + int ovec[60]; + int ret; + const char **out; + struct control *c = s->control; + char *output = NULL; + + if (!parse_re) { + parse_re = pcre_compile( + /* reqtype callid streams ip fromdom fromtype todom totype agent info |reqtype callid info | reqtype */ + "^(?:(request|lookup)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+info=(\\S*)|(delete)\\s+(\\S+)\\s+info=(\\S*)|(build|version|controls|quit|exit|status))$", + PCRE_DOLLAR_ENDONLY | PCRE_DOTALL, &errptr, &erroff, NULL); + parse_ree = pcre_study(parse_re, 0, &errptr); + } + + ret = pcre_exec(parse_re, parse_ree, line, strlen(line), 0, 0, ovec, G_N_ELEMENTS(ovec)); + if (ret <= 0) { + mylog(LOG_WARNING, "Unable to parse command line from " DF ": %s", DP(s->inaddr), line); + return -1; + } + + mylog(LOG_INFO, "Got valid command from " DF ": %s", DP(s->inaddr), line); + + pcre_get_substring_list(line, ovec, ret, &out); + + + if (!strcmp(out[RE_TCP_RL_CMD], "request")) + output = call_request(out, c->callmaster); + else if (!strcmp(out[RE_TCP_RL_CMD], "lookup")) + output = call_lookup(out, c->callmaster); + else if (!strcmp(out[RE_TCP_D_CMD], "delete")) + call_delete(out, c->callmaster); + else if (!strcmp(out[RE_TCP_DIV_CMD], "status")) + calls_status(c->callmaster, s); + else if (!strcmp(out[RE_TCP_DIV_CMD], "build") | !strcmp(out[RE_TCP_DIV_CMD], "version")) + streambuf_printf(s->outbuf, "Version: %s\n", MEDIAPROXY_VERSION); + else if (!strcmp(out[RE_TCP_DIV_CMD], "controls")) + control_list(c, s); + else if (!strcmp(out[RE_TCP_DIV_CMD], "quit") || !strcmp(out[RE_TCP_DIV_CMD], "exit")) + ; + + if (output) { + streambuf_write(s->outbuf, output, strlen(output)); + free(output); + } + + pcre_free(out); + return -1; +} + + +static void control_stream_timer(int fd, void *p) { + struct control_stream *s = p; + struct poller *o = s->poller; + + if ((o->now - s->inbuf->active) >= 60 || (o->now - s->outbuf->active) >= 60) + control_stream_closed(s->fd, s); +} + + +static void control_stream_readable(int fd, void *p) { + struct control_stream *s = p; + char *line; + int ret; + + if (streambuf_readable(s->inbuf)) + goto close; + + while ((line = streambuf_getline(s->inbuf))) { + mylog(LOG_DEBUG, "Got control line from " DF ": %s", DP(s->inaddr), line); + ret = control_stream_parse(s, line); + free(line); + if (ret) + goto close; + } + + if (streambuf_bufsize(s->inbuf) > 1024) { + mylog(LOG_WARNING, "Buffer length exceeded in control connection from " DF, DP(s->inaddr)); + goto close; + } + + return; + +close: + control_stream_closed(fd, s); +} + +static void control_stream_writeable(int fd, void *p) { + struct control_stream *s = p; + + if (streambuf_writeable(s->outbuf)) + control_stream_closed(fd, s); +} + +static void control_closed(int fd, void *p) { + abort(); +} + +static void control_incoming(int fd, void *p) { + int nfd; + struct control *c = p; + struct control_stream *s; + struct poller_item i; + struct sockaddr_in sin; + socklen_t sinl; + + sinl = sizeof(sin); + nfd = accept(fd, (struct sockaddr *) &sin, &sinl); + if (nfd == -1) + return; + nonblock(nfd); + + mylog(LOG_INFO, "New control connection from " DF, DP(sin)); + + s = malloc(sizeof(*s)); + ZERO(*s); + + ZERO(i); + i.fd = nfd; + i.closed = control_stream_closed; + i.readable = control_stream_readable; + i.writeable = control_stream_writeable; + i.timer = control_stream_timer; + i.ptr = s; + if (poller_add_item(c->poller, &i)) + goto fail; + s->fd = nfd; + s->control = c; + s->poller = c->poller; + s->inbuf = streambuf_new(c->poller, nfd); + s->outbuf = streambuf_new(c->poller, nfd); + memcpy(&s->inaddr, &sin, sizeof(s->inaddr)); + + c->stream_head = g_list_link(c->stream_head, &s->link); + + return; + +fail: + free(s); + close(nfd); +} + + +struct control *control_new(struct poller *p, u_int32_t ip, u_int16_t port, struct callmaster *m) { + int fd; + struct control *c; + struct poller_item i; + struct sockaddr_in sin; + + if (!p) + return NULL; + if (!m) + return NULL; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + return NULL; + + nonblock(fd); + reuseaddr(fd); + + ZERO(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = ip; + sin.sin_port = htons(port); + if (bind(fd, (struct sockaddr *) &sin, sizeof(sin))) + goto fail; + + if (listen(fd, 5)) + goto fail; + + + c = malloc(sizeof(*c)); + ZERO(*c); + + c->fd = fd; + c->poller = p; + c->callmaster = m; + + ZERO(i); + i.fd = fd; + i.closed = control_closed; + i.readable = control_incoming; + i.ptr = c; + if (poller_add_item(p, &i)) + goto fail2; + + return c; + +fail2: + free(c); +fail: + close(fd); + return NULL; +} diff --git a/daemon/control.h b/daemon/control.h new file mode 100644 index 000000000..0f83119b8 --- /dev/null +++ b/daemon/control.h @@ -0,0 +1,61 @@ +#ifndef __CONTROL_H__ +#define __CONTROL_H__ + + + +#include +#include +#include +#include +#include + +#define RE_TCP_RL_CMD 1 +#define RE_TCP_RL_CALLID 2 +#define RE_TCP_RL_STREAMS 3 +#define RE_TCP_RL_IP 4 +#define RE_TCP_RL_FROMDOM 5 +#define RE_TCP_RL_FROMTYPE 6 +#define RE_TCP_RL_TODOM 7 +#define RE_TCP_RL_TOTYPE 8 +#define RE_TCP_RL_AGENT 9 +#define RE_TCP_RL_INFO 10 +#define RE_TCP_D_CMD 11 +#define RE_TCP_D_CALLID 12 +#define RE_TCP_D_INFO 13 +#define RE_TCP_DIV_CMD 14 + +struct poller; +struct control; +struct streambuf; +struct callmaster; + + + +struct control_stream { + GList link; /* must be first */ + + int fd; + struct streambuf *inbuf; + struct streambuf *outbuf; + struct sockaddr_in inaddr; + + struct control *control; + struct poller *poller; +}; + + +struct control { + int fd; + + GList *stream_head; + + struct poller *poller; + struct callmaster *callmaster; +}; + + +struct control *control_new(struct poller *, u_int32_t, u_int16_t, struct callmaster *); + + + +#endif diff --git a/daemon/control_udp.c b/daemon/control_udp.c new file mode 100644 index 000000000..31ec331ad --- /dev/null +++ b/daemon/control_udp.c @@ -0,0 +1,236 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "control_udp.h" +#include "poller.h" +#include "aux.h" +#include "log.h" +#include "call.h" + + +static void control_udp_closed(int fd, void *p) { + abort(); +} + +static void control_udp_incoming(int fd, void *p) { + struct control_udp *u = p; + int ret, len; + char buf[8192]; + struct sockaddr_in6 sin; + socklen_t sin_len; + int ovec[100]; + const char **out; + char *reply; + struct msghdr mh; + struct iovec iov[10]; + char addr[64]; + + sin_len = sizeof(sin); + len = recvfrom(fd, buf, sizeof(buf) - 1, 0, (struct sockaddr *) &sin, &sin_len); + if (len <= 0) { + mylog(LOG_WARNING, "Error reading from UDP socket"); + return; + } + + buf[len] = '\0'; + smart_ntop_p(addr, &sin.sin6_addr, sizeof(addr)); + + ret = pcre_exec(u->parse_re, u->parse_ree, buf, len, 0, 0, ovec, G_N_ELEMENTS(ovec)); + if (ret <= 0) { + ret = pcre_exec(u->fallback_re, NULL, buf, len, 0, 0, ovec, G_N_ELEMENTS(ovec)); + if (ret <= 0) { + mylog(LOG_WARNING, "Unable to parse command line from udp:%s:%u: %s", addr, ntohs(sin.sin6_port), buf); + return; + } + + mylog(LOG_WARNING, "Failed to properly parse UDP command line '%s' from %s:%u, using fallback RE", buf, addr, ntohs(sin.sin6_port)); + + pcre_get_substring_list(buf, ovec, ret, &out); + + ZERO(mh); + mh.msg_name = &sin; + mh.msg_namelen = sizeof(sin); + mh.msg_iov = iov; + + iov[0].iov_base = (void *) out[RE_UDP_COOKIE]; + iov[0].iov_len = strlen(out[RE_UDP_COOKIE]); + if (out[RE_UDP_UL_CMD] && (chrtoupper(out[RE_UDP_UL_CMD][0]) == 'U' || chrtoupper(out[RE_UDP_UL_CMD][0]) == 'L')) { + iov[1].iov_base = (void *) out[RE_UDP_UL_CALLID]; + iov[1].iov_len = strlen(out[RE_UDP_UL_CALLID]); + iov[2].iov_base = (void *) out[RE_UDP_UL_FLAGS]; + iov[2].iov_len = strlen(out[RE_UDP_UL_FLAGS]); + iov[3].iov_base = "\n"; + iov[3].iov_len = 1; + mh.msg_iovlen = 4; + } + else { + iov[1].iov_base = " E8\n"; + iov[1].iov_len = 4; + mh.msg_iovlen = 2; + } + + sendmsg(fd, &mh, 0); + + pcre_free(out); + + return; + } + + mylog(LOG_INFO, "Got valid command from udp:%s:%u: %s", addr, ntohs(sin.sin6_port), buf); + + pcre_get_substring_list(buf, ovec, ret, &out); + + if (u->poller->now - u->oven_time >= 30) { + g_hash_table_remove_all(u->stale_cookies); +#if GLIB_CHECK_VERSION(2,14,0) + g_string_chunk_clear(u->stale_chunks); + swap_ptrs(&u->stale_chunks, &u->fresh_chunks); +#else + g_string_chunk_free(u->stale_chunks); + u->stale_chunks = u->fresh_chunks; + u->fresh_chunks = g_string_chunk_new(4 * 1024); +#endif + swap_ptrs(&u->stale_cookies, &u->fresh_cookies); + u->oven_time = u->poller->now; /* baked new cookies! */ + } + + /* XXX better hashing */ + reply = g_hash_table_lookup(u->fresh_cookies, out[RE_UDP_COOKIE]); + if (!reply) + reply = g_hash_table_lookup(u->stale_cookies, out[RE_UDP_COOKIE]); + if (reply) { + mylog(LOG_INFO, "Detected command from udp:%s:%u as a duplicate", addr, ntohs(sin.sin6_port)); + sendto(fd, reply, strlen(reply), 0, (struct sockaddr *) &sin, sin_len); + goto out; + } + + if (chrtoupper(out[RE_UDP_UL_CMD][0]) == 'U') + reply = call_update_udp(out, u->callmaster); + else if (chrtoupper(out[RE_UDP_UL_CMD][0]) == 'L') + reply = call_lookup_udp(out, u->callmaster); + else if (chrtoupper(out[RE_UDP_D_CMD][0]) == 'D') + reply = call_delete_udp(out, u->callmaster); + else if (chrtoupper(out[RE_UDP_V_CMD][0]) == 'V') { + ZERO(mh); + mh.msg_name = &sin; + mh.msg_namelen = sizeof(sin); + mh.msg_iov = iov; + mh.msg_iovlen = 2; + + iov[0].iov_base = (void *) out[RE_UDP_COOKIE]; + iov[0].iov_len = strlen(out[RE_UDP_COOKIE]); + iov[1].iov_base = " "; + iov[1].iov_len = 1; + + if (chrtoupper(out[RE_UDP_V_FLAGS][0]) == 'F') { + ret = 0; + if (!strcmp(out[RE_UDP_V_PARMS], "20040107")) + ret = 1; + else if (!strcmp(out[RE_UDP_V_PARMS], "20050322")) + ret = 1; + else if (!strcmp(out[RE_UDP_V_PARMS], "20060704")) + ret = 1; + iov[2].iov_base = ret ? "1\n" : "0\n"; + iov[2].iov_len = 2; + mh.msg_iovlen++; + } + else { + iov[2].iov_base = "20040107\n"; + iov[2].iov_len = 9; + mh.msg_iovlen++; + } + sendmsg(fd, &mh, 0); + } + + if (reply) { + sendto(fd, reply, strlen(reply), 0, (struct sockaddr *) &sin, sin_len); + g_hash_table_insert(u->fresh_cookies, g_string_chunk_insert(u->fresh_chunks, out[RE_UDP_COOKIE]), + g_string_chunk_insert(u->fresh_chunks, reply)); + free(reply); + } + +out: + pcre_free(out); +} + +struct control_udp *control_udp_new(struct poller *p, struct in6_addr ip, u_int16_t port, struct callmaster *m) { + int fd; + struct control_udp *c; + struct poller_item i; + struct sockaddr_in6 sin; + const char *errptr; + int erroff; + + if (!p || !m) + return NULL; + + fd = socket(AF_INET6, SOCK_DGRAM, 0); + if (fd == -1) + return NULL; + + nonblock(fd); + reuseaddr(fd); + ipv6only(fd, 0); + + ZERO(sin); + sin.sin6_family = AF_INET6; + sin.sin6_addr = ip; + sin.sin6_port = htons(port); + if (bind(fd, (struct sockaddr *) &sin, sizeof(sin))) + goto fail; + + + c = malloc(sizeof(*c)); + ZERO(*c); + + c->fd = fd; + c->poller = p; + c->callmaster = m; + c->fresh_cookies = g_hash_table_new(g_str_hash, g_str_equal); + c->stale_cookies = g_hash_table_new(g_str_hash, g_str_equal); + c->fresh_chunks = g_string_chunk_new(4 * 1024); + c->stale_chunks = g_string_chunk_new(4 * 1024); + c->oven_time = p->now; + c->parse_re = pcre_compile( + /* cookie cmd flags callid viabranch:5 */ + "^(\\S+)\\s+(?:([ul])(\\S*)\\s+([^;]+)(?:;(\\S+))?\\s+" \ + /* addr4 addr6:7 */ + "(?:([\\d.]+)|([\\da-f:]+(?::ffff:[\\d.]+)?))" \ + /* port fromtag num totag:11 */ + "\\s+(\\d+)\\s+(\\S+?);(\\d+)(?:\\s+(\\S+?);\\d+(?:\\s+.*)?)?\r?\n?$" \ + /* "d" flags callid viabranch fromtag totag:17 */ + "|(d)(\\S*)\\s+([^;\\s]+)(?:;(\\S+))?\\s+(\\S+?)(?:\\s+(\\S+?))?\r?\n?$" \ + /* v flags params:20 */ + "|(v)(\\S*)(?:\\s+(\\S+))?)", + PCRE_DOLLAR_ENDONLY | PCRE_DOTALL | PCRE_CASELESS, &errptr, &erroff, NULL); + c->parse_ree = pcre_study(c->parse_re, 0, &errptr); + /* cookie cmd flags callid addr port */ + c->fallback_re = pcre_compile("^(\\S+)(?:\\s+(\\S)\\S*\\s+\\S+(\\s+\\S+)(\\s+\\S+))?", PCRE_DOLLAR_ENDONLY | PCRE_DOTALL | PCRE_CASELESS, &errptr, &erroff, NULL); + + if (!c->parse_re || !c->fallback_re) + goto fail2; + + ZERO(i); + i.fd = fd; + i.closed = control_udp_closed; + i.readable = control_udp_incoming; + i.ptr = c; + if (poller_add_item(p, &i)) + goto fail2; + + return c; + +fail2: + free(c); +fail: + close(fd); + return NULL; + +} diff --git a/daemon/control_udp.h b/daemon/control_udp.h new file mode 100644 index 000000000..815121872 --- /dev/null +++ b/daemon/control_udp.h @@ -0,0 +1,63 @@ +#ifndef __CONTROL_UDP_H__ +#define __CONTROL_UDP_H__ + + + + + +#include +#include +#include +#include + +#define RE_UDP_COOKIE 1 +#define RE_UDP_UL_CMD 2 +#define RE_UDP_UL_FLAGS 3 +#define RE_UDP_UL_CALLID 4 +#define RE_UDP_UL_VIABRANCH 5 +#define RE_UDP_UL_ADDR4 6 +#define RE_UDP_UL_ADDR6 7 +#define RE_UDP_UL_PORT 8 +#define RE_UDP_UL_FROMTAG 9 +#define RE_UDP_UL_NUM 10 +#define RE_UDP_UL_TOTAG 11 +#define RE_UDP_D_CMD 12 +#define RE_UDP_D_FLAGS 13 +#define RE_UDP_D_CALLID 14 +#define RE_UDP_D_VIABRANCH 15 +#define RE_UDP_D_FROMTAG 16 +#define RE_UDP_D_TOTAG 17 +#define RE_UDP_V_CMD 18 +#define RE_UDP_V_FLAGS 19 +#define RE_UDP_V_PARMS 20 + +struct poller; +struct callmaster; + + + + + +struct control_udp { + int fd; + + struct poller *poller; + struct callmaster *callmaster; + + pcre *parse_re; + pcre_extra *parse_ree; + pcre *fallback_re; + GHashTable *fresh_cookies, *stale_cookies; + GStringChunk *fresh_chunks, *stale_chunks; + time_t oven_time; +}; + + + + + +struct control_udp *control_udp_new(struct poller *, struct in6_addr, u_int16_t, struct callmaster *); + + + +#endif diff --git a/daemon/kernel.c b/daemon/kernel.c new file mode 100644 index 000000000..708bae24d --- /dev/null +++ b/daemon/kernel.c @@ -0,0 +1,142 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "xt_MEDIAPROXY.h" + +#include "aux.h" +#include "kernel.h" + + + + +#if 1 +#define PREFIX "/proc/mediaproxy" +#else +#define PREFIX "/tmp/mediaproxy" +#endif + + + + + +int kernel_create_table(unsigned int id) { + char str[64]; + int fd; + int i; + + fd = open(PREFIX "/control", O_WRONLY | O_TRUNC); + if (fd == -1) + return -1; + sprintf(str, "add %u\n", id); + i = write(fd, str, strlen(str)); + if (i == -1) + goto fail; + close(fd); + + return 0; + +fail: + close(fd); + return -1; +} + + +int kernel_open_table(unsigned int id) { + char str[64]; + int fd; + struct mediaproxy_message msg; + int i; + + sprintf(str, PREFIX "/%u/control", id); + fd = open(str, O_WRONLY | O_TRUNC); + if (fd == -1) + return -1; + + ZERO(msg); + msg.cmd = MMG_NOOP; + i = write(fd, &msg, sizeof(msg)); + if (i <= 0) + goto fail; + + return fd; + +fail: + close(fd); + return -1; +} + + +static void addr_copy(struct mp_address *mp, struct ip_port *ap) { + mp->family = ap->family; + mp->port = ap->port; + switch (mp->family) { + case AF_INET: + mp->ipv4 = ap->ipv4; + break; + case AF_INET6: + memcpy(mp->ipv6, &ap->ipv6, 16); + break; + default: + /* XXX panic */ + break; + } +} + + +int kernel_add_stream(int fd, struct kernel_stream *info, int update) { + struct mediaproxy_message msg; + + ZERO(msg); + msg.cmd = update ? MMG_UPDATE : MMG_ADD; + msg.target.target_port = info->local_port; + addr_copy(&msg.target.src_addr, &info->src); + addr_copy(&msg.target.dst_addr, &info->dest); + addr_copy(&msg.target.mirror_addr, &info->mirror); + msg.target.tos = info->tos; + + return write(fd, &msg, sizeof(msg)) <= 0 ? -1 : 0; +} + + +int kernel_del_stream(int fd, u_int16_t p) { + struct mediaproxy_message msg; + + ZERO(msg); + msg.cmd = MMG_DEL; + msg.target.target_port = p; + + return write(fd, &msg, sizeof(msg)) <= 0 ? -1 : 0; +} + + +GList *kernel_list(unsigned int id) { + char str[64]; + int fd; + struct mediaproxy_list_entry *buf; + GList *li = NULL; + int ret; + + sprintf(str, PREFIX "/%u/blist", id); + fd = open(str, O_RDONLY); + if (fd == -1) + return NULL; + + + for (;;) { + buf = g_slice_alloc(sizeof(*buf)); + ret = read(fd, buf, sizeof(*buf)); + if (ret != sizeof(*buf)) + break; + li = g_list_prepend(li, buf); + } + + g_slice_free1(sizeof(*buf), buf); + close(fd); + + return li; +} diff --git a/daemon/kernel.h b/daemon/kernel.h new file mode 100644 index 000000000..fef3ab19c --- /dev/null +++ b/daemon/kernel.h @@ -0,0 +1,42 @@ +#ifndef __KERNEL_H__ +#define __KERNEL_H__ + + + +#include +#include + + + + +struct ip_port { + int family; + union { + u_int32_t ipv4; + struct in6_addr ipv6; + }; + u_int16_t port; +}; + +struct kernel_stream { + u_int16_t local_port; + struct ip_port src; + struct ip_port dest; + struct ip_port mirror; + unsigned char tos; +}; + + + + +int kernel_create_table(unsigned int); +int kernel_open_table(unsigned int); + +int kernel_add_stream(int, struct kernel_stream *, int); +int kernel_del_stream(int, u_int16_t); +GList *kernel_list(unsigned int); + + + + +#endif diff --git a/daemon/log.h b/daemon/log.h new file mode 100644 index 000000000..7767281d2 --- /dev/null +++ b/daemon/log.h @@ -0,0 +1,12 @@ +#ifndef __LOG_H__ +#define __LOG_H__ + + +#include + + +#define mylog(x,y...) syslog(x,y) + + + +#endif diff --git a/daemon/main.c b/daemon/main.c new file mode 100644 index 000000000..1c5fd3ead --- /dev/null +++ b/daemon/main.c @@ -0,0 +1,370 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "poller.h" +#include "control.h" +#include "control_udp.h" +#include "aux.h" +#include "log.h" +#include "call.h" +#include "kernel.h" +#include "redis.h" + + + + +#define die(x...) do { fprintf(stderr, x); exit(-1); } while(0) +#define dlresolve(m,n) do { \ + n = dlsym(m, "mod_" #n); \ + if (!n) \ + die("Failed to resolve symbol from plugin: %s\n", #n); \ + } while(0) + + + + + +static char *pidfile; +static gboolean foreground; +static u_int32_t ipv4; +static u_int32_t adv_ipv4; +static struct in6_addr ipv6; +static struct in6_addr adv_ipv6; +static u_int32_t listenp; +static u_int16_t listenport; +static struct in6_addr udp_listenp; +static u_int16_t udp_listenport; +static int tos; +static int table; +static int no_fallback; +static int timeout; +static int silent_timeout; +static int port_min; +static int port_max; +static u_int32_t redis_ip; +static u_int16_t redis_port; +static int redis_db = -1; +static char *b2b_url; + + + + +static void signals(void) { + signal(SIGPIPE, SIG_IGN); + signal(SIGCHLD, SIG_IGN); +} + +static int rlim(int res, rlim_t val) { + struct rlimit rlim; + + ZERO(rlim); + rlim.rlim_cur = rlim.rlim_max = val; + return setrlimit(res, &rlim); +} + +static void resources(void) { + int tryv; + + rlim(RLIMIT_CORE, RLIM_INFINITY); + for (tryv = ((1<<16) - 1); tryv && rlim(RLIMIT_NOFILE, tryv) == -1; tryv >>= 1) + ; + + rlim(RLIMIT_DATA, RLIM_INFINITY); + rlim(RLIMIT_RSS, RLIM_INFINITY); + rlim(RLIMIT_AS, RLIM_INFINITY); +} + + + +static int parse_ip_port(u_int32_t *ip, u_int16_t *port, char *s) { + char *p = NULL; + int ret = -1; + + p = strchr(s, ':'); + if (p) { + *p++ = 0; + *ip = inet_addr(s); + if (*ip == -1) + goto out; + *port = atoi(p); + } + else { + *ip = 0; + if (strchr(s, '.')) + goto out; + *port = atoi(s); + } + if (!*port) + goto out; + + ret = 0; + +out: + if (p) + *--p = ':'; + return ret; +} + +static int parse_ip6_port(struct in6_addr *ip6, u_int16_t *port, char *s) { + u_int32_t ip; + char *p; + + if (!parse_ip_port(&ip, port, s)) { + if (ip) + in4_to_6(ip6, ip); + else + *ip6 = in6addr_any; + return 0; + } + if (*s != '[') + return -1; + p = strstr(s, "]:"); + if (!p) + return -1; + *p = '\0'; + if (inet_pton(AF_INET6, s+1, ip6) != 1) + goto fail; + *p = ']'; + *port = atoi(p+2); + if (!*port) + return -1; + + return 0; + +fail: + *p = ']'; + return -1; +} + + + +static void options(int *argc, char ***argv) { + static char *ipv4s; + static char *adv_ipv4s; + static char *ipv6s; + static char *adv_ipv6s; + static char *listenps; + static char *listenudps; + static char *redisps; + static int version; + + static GOptionEntry e[] = { + { "version", 'v', 0, G_OPTION_ARG_NONE, &version, "Print build time and exit", NULL }, + { "table", 't', 0, G_OPTION_ARG_INT, &table, "Kernel table to use", "INT" }, + { "no-fallback",'F', 0, G_OPTION_ARG_NONE, &no_fallback, "Only start when kernel module is available", NULL }, + { "ip", 'i', 0, G_OPTION_ARG_STRING, &ipv4s, "Local IPv4 address for RTP", "IP" }, + { "advertised-ip", 'a', 0, G_OPTION_ARG_STRING, &adv_ipv4s, "IPv4 address to advertise", "IP" }, + { "ip6", 'I', 0, G_OPTION_ARG_STRING, &ipv6s, "Local IPv6 address for RTP", "IP6" }, + { "advertised-ip6",'A',0,G_OPTION_ARG_STRING, &adv_ipv6s, "IPv6 address to advertise", "IP6" }, + { "listen", 'l', 0, G_OPTION_ARG_STRING, &listenps, "TCP port to listen on", "[IP:]PORT" }, + { "listen-udp", 'u', 0, G_OPTION_ARG_STRING, &listenudps, "UDP port to listen on", "[IP46:]PORT" }, + { "tos", 'T', 0, G_OPTION_ARG_INT, &tos, "TOS value to set on streams", "INT" }, + { "timeout", 'o', 0, G_OPTION_ARG_INT, &timeout, "RTP timeout", "SECS" }, + { "silent-timeout",'s',0,G_OPTION_ARG_INT, &silent_timeout,"RTP timeout for muted", "SECS" }, + { "pidfile", 'p', 0, G_OPTION_ARG_STRING, &pidfile, "Write PID to file", "FILE" }, + { "foreground", 'f', 0, G_OPTION_ARG_NONE, &foreground, "Don't fork to background", NULL }, + { "port-min", 'm', 0, G_OPTION_ARG_INT, &port_min, "Lowest port to use for RTP", "INT" }, + { "port-max", 'M', 0, G_OPTION_ARG_INT, &port_max, "Highest port to use for RTP", "INT" }, + { "redis", 'r', 0, G_OPTION_ARG_STRING, &redisps, "Connect to Redis database", "IP:PORT" }, + { "redis-db", 'R', 0, G_OPTION_ARG_INT, &redis_db, "Which Redis DB to use", "INT" }, + { "b2b-url", 'b', 0, G_OPTION_ARG_STRING, &b2b_url, "XMLRPC URL of B2B UA" , "STRING" }, + { NULL, } + }; + + GOptionContext *c; + GError *er = NULL; + + c = g_option_context_new(" - next-generation media proxy"); + g_option_context_add_main_entries(c, e, NULL); + if (!g_option_context_parse(c, argc, argv, &er)) + die("Bad command line: %s\n", er->message); + + if (version) + die("%s\n", MEDIAPROXY_VERSION); + + if (!ipv4s) + die("Missing option --ip\n"); + if (!listenps && !listenudps) + die("Missing option --listen or --listen-udp\n"); + + ipv4 = inet_addr(ipv4s); + if (ipv4 == -1) + die("Invalid IPv4 address (--ip)\n"); + + if (adv_ipv4s) { + adv_ipv4 = inet_addr(adv_ipv4s); + if (adv_ipv4 == -1) + die("Invalid IPv4 address (--advertised-ip)\n"); + } + + if (ipv6s) { + if (smart_pton(AF_INET6, ipv6s, &ipv6) != 1) + die("Invalid IPv6 address (--ip6)\n"); + } + if (adv_ipv6s) { + if (smart_pton(AF_INET6, adv_ipv6s, &adv_ipv6) != 1) + die("Invalid IPv6 address (--advertised-ip6)\n"); + } + + if (listenps) { + if (parse_ip_port(&listenp, &listenport, listenps)) + die("Invalid IP or port (--listen)\n"); + } + if (listenudps) { + if (parse_ip6_port(&udp_listenp, &udp_listenport, listenudps)) + die("Invalid IP or port (--listen-udp)\n"); + } + + if (tos < 0 || tos > 255) + die("Invalid TOS value\n"); + + if (timeout <= 0) + timeout = 60; + if (silent_timeout <= 0) + silent_timeout = 3600; + + if (redisps) { + if (parse_ip_port(&redis_ip, &redis_port, redisps) || !redis_ip) + die("Invalid IP or port (--redis)\n"); + if (redis_db < 0) + die("Must specify Redis DB number (--redis-db) when using Redis\n"); + } +} + + +static void daemonize(void) { + printf("Going to background...\n"); + if (fork()) + _exit(0); + freopen("/dev/null", "r", stdin); + freopen("/dev/null", "w", stdout); + freopen("/dev/null", "w", stderr); + setpgrp(); +} + +static void wpidfile(void) { + FILE *fp; + + if (!pidfile) + return; + + fp = fopen(pidfile, "w"); + if (fp) { + fprintf(fp, "%u\n", getpid()); + fclose(fp); + } +} + + +int main(int argc, char **argv) { + struct poller *p; + struct callmaster *m; + struct control *c; + struct control_udp *cu; + int kfd = -1; + int ret; + void *dlh; + const char **strp; + + options(&argc, &argv); + signals(); + resources(); + + + if (table >= 0 && kernel_create_table(table)) { + fprintf(stderr, "FAILED TO CREATE KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table); + mylog(LOG_CRIT, "FAILED TO CREATE KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table); + table = -1; + if (no_fallback) + exit(-1); + } + if (table >= 0) { + kfd = kernel_open_table(table); + if (kfd == -1) { + fprintf(stderr, "FAILED TO OPEN KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table); + mylog(LOG_CRIT, "FAILED TO OPEN KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table); + table = -1; + if (no_fallback) + exit(-1); + } + } + + p = poller_new(); + if (!p) + die("poller creation failed\n"); + + m = callmaster_new(p); + if (!m) + return -1; + m->kernelfd = kfd; + m->kernelid = table; + m->ipv4 = ipv4; + m->adv_ipv4 = adv_ipv4; + m->ipv6 = ipv6; + m->adv_ipv6 = adv_ipv6; + m->port_min = port_min; + m->port_max = port_max; + m->timeout = timeout; + m->silent_timeout = silent_timeout; + m->tos = tos; + m->b2b_url = b2b_url; + + c = NULL; + if (listenport) { + c = control_new(p, listenp, listenport, m); + if (!c) + die("Failed to open TCP control connection port\n"); + } + + cu = NULL; + if (udp_listenport) { + cu = control_udp_new(p, udp_listenp, udp_listenport, m); + if (!cu) + die("Failed to open UDP control connection port\n"); + } + + if (redis_ip) { + dlh = dlopen(MP_PLUGIN_DIR "/redis.so", RTLD_NOW | RTLD_GLOBAL); + if (!dlh) + die("Failed to open redis plugin, aborting (%s)\n", dlerror()); + strp = dlsym(dlh, "__module_version"); + if (!strp || !*strp || strcmp(*strp, "redis/1.0.0")) + die("Incorrect redis module version: %s\n", *strp); + + dlresolve(dlh, redis_new); + dlresolve(dlh, redis_restore); + dlresolve(dlh, redis_update); + dlresolve(dlh, redis_delete); + dlresolve(dlh, redis_wipe); + + m->redis = redis_new(redis_ip, redis_port, redis_db); + if (!m->redis) + die("Cannot start up without Redis database\n"); + } + + mylog(LOG_INFO, "Startup complete"); + + if (!foreground) + daemonize(); + wpidfile(); + + if (m->redis) { + if (redis_restore(m)) + die("Refusing to continue without working Redis database\n"); + } + + for (;;) { + ret = poller_poll(p, 100); + if (ret == -1) + break; + } + + return 0; +} diff --git a/daemon/poller-test.c b/daemon/poller-test.c new file mode 100644 index 000000000..6a2aef8ff --- /dev/null +++ b/daemon/poller-test.c @@ -0,0 +1,60 @@ +#include +#include +#include "poller.h" + + +void dummy(int a, void *b) { +} + + +int main() { + struct poller *p; + struct poller_item i; + + p = poller_new(); + if (!p) { + fprintf(stderr, "poller creation failed\n"); + return -1; + } + + assert(p->items_size == 0); + assert(p->pollfds_size == 0); + + i.readable = dummy; + i.writeable = dummy; + i.closed = dummy; + + + i.fd = 3; + assert(poller_add_item(p, &i) == 0); + i.fd = 4; + assert(poller_add_item(p, &i) == 0); + i.fd = 2; + assert(poller_add_item(p, &i) == 0); + i.fd = 6; + assert(poller_add_item(p, &i) == 0); + i.fd = 0; + assert(poller_add_item(p, &i) == 0); + i.fd = 1; + assert(poller_add_item(p, &i) == 0); + i.fd = 5; + assert(poller_add_item(p, &i) == 0); + i.fd = 7; + assert(poller_add_item(p, &i) == 0); + i.fd = 9; + assert(poller_add_item(p, &i) == 0); + + + assert(poller_del_item(p, 10) == -1); + assert(poller_del_item(p, 6) == 0); + assert(poller_del_item(p, 8) == -1); + assert(poller_del_item(p, 0) == 0); + assert(poller_del_item(p, 3) == 0); + assert(poller_del_item(p, 11) == -1); + assert(poller_del_item(p, 9) == 0); + assert(poller_del_item(p, 11) == -1); + assert(poller_del_item(p, 4) == 0); + + + return 0; +} diff --git a/daemon/poller.c b/daemon/poller.c new file mode 100644 index 000000000..9bc7e1fa3 --- /dev/null +++ b/daemon/poller.c @@ -0,0 +1,311 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "poller.h" +#include "aux.h" + + + + +#define POLLER_BSEARCH(b,l,k,e) mybsearch(b, l, sizeof(struct pollfd), k, OFFSET_OF(struct pollfd, fd), sizeof(*(k)), e) + + + + +struct timer_item { + void (*func)(void *); + void *ptr; +}; + + + + + +struct poller *poller_new(void) { + struct poller *p; + + p = malloc(sizeof(*p)); + memset(p, 0, sizeof(*p)); + p->now = time(NULL); + + return p; +} + + +int poller_add_item(struct poller *p, struct poller_item *i) { + struct poller_item *ip; + struct pollfd *pf; + int idx; + unsigned int u; + + if (!p || !i) + return -1; + if (i->fd < 0) + return -1; + if (!i->readable && !i->writeable) + return -1; + if (!i->closed) + return -1; + + if (i->fd < p->items_size && p->items[i->fd]) + return -1; + + idx = POLLER_BSEARCH(p->pollfds, p->pollfds_size, &i->fd, 0); + assert(idx < 0); + + idx *= -1; + idx--; + + p->pollfds_size++; + p->pollfds = realloc(p->pollfds, p->pollfds_size * sizeof(*p->pollfds)); + memmove(p->pollfds + idx + 1, p->pollfds + idx, (p->pollfds_size - idx - 1) * sizeof(*p->pollfds)); + pf = &p->pollfds[idx]; + + pf->fd = i->fd; + pf->events = POLLHUP | POLLERR | ((i->writeable && i->blocked) ? POLLOUT : 0) | (i->readable ? POLLIN : 0); + pf->revents = 0; + + if (i->fd >= p->items_size) { + u = p->items_size; + p->items_size = i->fd + 1; + p->items = realloc(p->items, sizeof(*p->items) * p->items_size); + memset(p->items + u, 0, sizeof(*p->items) * (p->items_size - u - 1)); + } + + ip = malloc(sizeof(*ip)); + memcpy(ip, i, sizeof(*ip)); + p->items[i->fd] = ip; + + return 0; +} + + +int poller_del_item(struct poller *p, int fd) { + int idx; + + if (!p || fd < 0) + return -1; + if (fd >= p->items_size) + return -1; + if (!p->items || !p->items[fd]) + return -1; + if (!p->pollfds || !p->pollfds_size) + return -1; + + idx = POLLER_BSEARCH(p->pollfds, p->pollfds_size, &fd, 1); + assert(idx != -1); + + memmove(p->pollfds + idx, p->pollfds + idx + 1, (p->pollfds_size - idx - 1) * sizeof(*p->pollfds)); + p->pollfds_size--; + p->pollfds = realloc(p->pollfds, p->pollfds_size * sizeof(*p->pollfds)); + + if (p->pollfds_work) { + idx = POLLER_BSEARCH(p->pollfds_work, p->pollfds_work_size, &fd, 1); + + if (idx != -1) + p->pollfds_work[idx].fd = -1; + } + + free(p->items[fd]); + p->items[fd] = NULL; + + return 0; +} + + +int poller_update_item(struct poller *p, struct poller_item *i) { + struct poller_item *np; + + if (!p || !i) + return -1; + if (i->fd < 0) + return -1; + if (!i->readable && !i->writeable) + return -1; + if (!i->closed) + return -1; + + if (i->fd >= p->items_size || !(np = p->items[i->fd])) + return poller_add_item(p, i); + + np->ptr = i->ptr; + np->readable = i->readable; + np->writeable = i->writeable; + np->closed = i->closed; + np->timer = i->timer; + + return 0; +} + + +int poller_poll(struct poller *p, int timeout) { + struct pollfd *pfd, *pf; + int ret, i; + struct poller_item *it; + int idx; + time_t last; + int do_timer; + GList *li; + struct timer_item *ti; + + if (!p) + return -1; + if (!p->pollfds || !p->pollfds_size) + return -1; + if (!p->items || !p->items_size) + return -1; + + p->pollfds_work_size = i = p->pollfds_size; + p->pollfds_work = pfd = malloc(sizeof(*pfd) * i); + memcpy(pfd, p->pollfds, sizeof(*pfd) * i); + + do_timer = 0; + last = p->now; + p->now = time(NULL); + if (last != p->now) { + do_timer = 1; + ret = i; + + for (li = p->timers; li; li = li->next) { + ti = li->data; + ti->func(ti->ptr); + } + } + else { + ret = poll(pfd, i, timeout); + if (errno == EINTR) + ret = 0; + if (ret < 0) + goto out; + } + + pf = pfd; + for (pf = pfd; i; pf++) { + i--; + + if (pf->fd < 0) + continue; + + it = (pf->fd < p->items_size) ? p->items[pf->fd] : NULL; + if (!it) + continue; + + if (do_timer) { + if (it->timer) + it->timer(it->fd, it->ptr); + continue; + } + + if (it->error) { + it->closed(it->fd, it->ptr); + continue; + } + + if ((pf->revents & (POLLERR | POLLHUP))) + it->closed(it->fd, it->ptr); + else if ((pf->revents & POLLOUT)) { + it->blocked = 0; + + idx = POLLER_BSEARCH(p->pollfds, p->pollfds_size, &it->fd, 1); + assert(idx != -1); + + p->pollfds[idx].events &= ~POLLOUT; + + it->writeable(it->fd, it->ptr); + } + else if ((pf->revents & POLLIN)) + it->readable(it->fd, it->ptr); + else if (!pf->revents) + continue; + else + abort(); + } + + +out: + free(pfd); + p->pollfds_work = NULL; + p->pollfds_work_size = 0; + return ret; +} + + +void poller_blocked(struct poller *p, int fd) { + int idx; + + if (!p || fd < 0) + return; + if (fd >= p->items_size) + return; + if (!p->items || !p->items[fd]) + return; + if (!p->pollfds || !p->pollfds_size) + return; + if (!p->items[fd]->writeable) + return; + + p->items[fd]->blocked = 1; + + idx = POLLER_BSEARCH(p->pollfds, p->pollfds_size, &fd, 1); + assert(idx != -1); + + p->pollfds[idx].events |= POLLOUT; +} + +void poller_error(struct poller *p, int fd) { + if (!p || fd < 0) + return; + if (fd >= p->items_size) + return; + if (!p->items || !p->items[fd]) + return; + if (!p->pollfds || !p->pollfds_size) + return; + if (!p->items[fd]->writeable) + return; + + p->items[fd]->error = 1; + p->items[fd]->blocked = 1; +} + +int poller_isblocked(struct poller *p, int fd) { + if (!p || fd < 0) + return -1; + if (fd >= p->items_size) + return -1; + if (!p->items || !p->items[fd]) + return -1; + if (!p->pollfds || !p->pollfds_size) + return -1; + if (!p->items[fd]->writeable) + return -1; + + return p->items[fd]->blocked; +} + + + + +int poller_timer(struct poller *p, void (*f)(void *), void *ptr) { + struct timer_item *i; + + if (!p || !f) + return -1; + + i = malloc(sizeof(*i)); + ZERO(*i); + + i->func = f; + i->ptr = ptr; + + p->timers = g_list_prepend(p->timers, i); + + return 0; +} diff --git a/daemon/poller.h b/daemon/poller.h new file mode 100644 index 000000000..946c9d863 --- /dev/null +++ b/daemon/poller.h @@ -0,0 +1,51 @@ +#ifndef __POLLER_H__ +#define __POLLER_H__ + + + +#include +#include +#include + + + +struct poller_item { + int fd; + void *ptr; + + void (*readable)(int, void *); + void (*writeable)(int, void *); + void (*closed)(int, void *); + void (*timer)(int, void *); + + int blocked:1; + int error:1; +}; + +struct poller { + struct poller_item **items; + unsigned int items_size; + struct pollfd *pollfds; + unsigned int pollfds_size; + GList *timers; + + time_t now; + + struct pollfd *pollfds_work; + unsigned int pollfds_work_size; +}; + + +struct poller *poller_new(void); +int poller_add_item(struct poller *, struct poller_item *); +int poller_update_item(struct poller *, struct poller_item *); +int poller_del_item(struct poller *, int); +int poller_poll(struct poller *, int); +void poller_blocked(struct poller *, int); +int poller_isblocked(struct poller *, int); +void poller_error(struct poller *, int); + +int poller_timer(struct poller *, void (*)(void *), void *); + + +#endif diff --git a/daemon/redis.c b/daemon/redis.c new file mode 100644 index 000000000..532c257b5 --- /dev/null +++ b/daemon/redis.c @@ -0,0 +1,7 @@ +#include "redis.h" + +struct redis *(*redis_new)(u_int32_t, u_int16_t, int); +int (*redis_restore)(struct callmaster *); +void (*redis_update)(struct call *); +void (*redis_delete)(struct call *); +void (*redis_wipe)(struct callmaster *); diff --git a/daemon/redis.h b/daemon/redis.h new file mode 100644 index 000000000..113967ffa --- /dev/null +++ b/daemon/redis.h @@ -0,0 +1,26 @@ +#ifndef __REDIS_H__ +#define __REDIS_H__ + + + + +#include + + + + +struct callmaster; +struct call; + + + +extern struct redis *(*redis_new)(u_int32_t, u_int16_t, int); +extern int (*redis_restore)(struct callmaster *); +extern void (*redis_update)(struct call *); +extern void (*redis_delete)(struct call *); +extern void (*redis_wipe)(struct callmaster *); + + + + +#endif diff --git a/daemon/streambuf.c b/daemon/streambuf.c new file mode 100644 index 000000000..ccb55c0fe --- /dev/null +++ b/daemon/streambuf.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "streambuf.h" +#include "poller.h" +#include "aux.h" + + + +struct streambuf *streambuf_new(struct poller *p, int fd) { + struct streambuf *b; + + b = malloc(sizeof(*b)); + ZERO(*b); + + b->buf = g_string_new(""); + b->fd = fd; + b->poller = p; + b->active = p->now; + + return b; +} + + +void streambuf_destroy(struct streambuf *b) { + g_string_free(b->buf, TRUE); + free(b); +} + + +int streambuf_writeable(struct streambuf *b) { + int ret; + unsigned int out; + + for (;;) { + if (!b->buf->len) + break; + + out = (b->buf->len > 1024) ? 1024 : b->buf->len; + ret = write(b->fd, b->buf->str, out); + + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) + return -1; + ret = 0; + } + + if (ret > 0) { + g_string_erase(b->buf, 0, ret); + b->active = b->poller->now; + } + + if (ret != out) { + poller_blocked(b->poller, b->fd); + break; + } + } + + return 0; +} + +int streambuf_readable(struct streambuf *b) { + int ret; + char buf[1024]; + + for (;;) { + ret = read(b->fd, buf, 1024); + + if (ret == 0) + return -1; + if (ret < 0) { + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) + break; + return -1; + } + + g_string_append_len(b->buf, buf, ret); + b->active = b->poller->now; + } + + return 0; +} + + +char *streambuf_getline(struct streambuf *b) { + char *p; + int len; + char *s = NULL; + + for (;;) { + if (s) + free(s); + + p = memchr(b->buf->str, '\n', b->buf->len); + if (!p) + return NULL; + + len = p - b->buf->str; + if (len == 0) { + g_string_erase(b->buf, 0, 1); + continue; + } + + s = malloc(len + 1); + memcpy(s, b->buf->str, len); + s[len] = '\0'; + g_string_erase(b->buf, 0, len + 1); + + if (s[--len] == '\r') { + if (len == 0) + continue; + s[len] = '\0'; + } + + break; + } + + return s; +} + +unsigned int streambuf_bufsize(struct streambuf *b) { + return b->buf->len; +} + + +void streambuf_printf(struct streambuf *b, char *f, ...) { + va_list va; + GString *gs; + + va_start(va, f); + gs = g_string_new(""); + g_string_vprintf(gs, f, va); + va_end(va); + + streambuf_write(b, gs->str, gs->len); + g_string_free(gs, TRUE); +} + +void streambuf_write(struct streambuf *b, char *s, unsigned int len) { + unsigned int out; + int ret; + + while (len && !poller_isblocked(b->poller, b->fd)) { + out = (len > 1024) ? 1024 : len; + ret = write(b->fd, s, out); + + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { + poller_error(b->poller, b->fd); + break; + } + poller_blocked(b->poller, b->fd); + break; + } + if (ret == 0) + break; + + s += ret; + len -= ret; + b->active = b->poller->now; + } + + if (b->buf->len > 5242880) + poller_error(b->poller, b->fd); + else if (len) + g_string_append_len(b->buf, s, len); +} diff --git a/daemon/streambuf.h b/daemon/streambuf.h new file mode 100644 index 000000000..7a9d7d4e0 --- /dev/null +++ b/daemon/streambuf.h @@ -0,0 +1,35 @@ +#ifndef __BUFFER_H__ +#define __BUFFER_H__ + + + +#include +#include +#include + + + +struct poller; + + + +struct streambuf { + GString *buf; + int fd; + struct poller *poller; + time_t active; +}; + + + +struct streambuf *streambuf_new(struct poller *, int); +void streambuf_destroy(struct streambuf *); +int streambuf_writeable(struct streambuf *); +int streambuf_readable(struct streambuf *); +char *streambuf_getline(struct streambuf *); +unsigned int streambuf_bufsize(struct streambuf *); +void streambuf_printf(struct streambuf *, char *, ...) __attribute__ ((format (printf, 2, 3))); +void streambuf_write(struct streambuf *, char *, unsigned int); + + +#endif diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 000000000..463fc1cad --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,25 @@ +ngcp-mediaproxy-ng-kernel for Debian +------------------------------------ + +This package only works with kernels >= 2.6.32. + +Instructions for usage +~~~~~~~~~~~~~~~~~~~~~~ + +1) module-assistant: + +% sudo apt-get install linux-headers-$(uname -r) +% sudo apt-get install ngcp-mediaproxy-ng-kernel-source +% sudo m-a prepare +% sudo m-a -v -f -l $(uname -r) -k /usr/src/linux-headers-$(uname -r) a-b ngcp-mediaproxy-ng-kernel + +How to debug build process: + +% sudo m-a -d -v --text-mode -l $(uname -r) -k /usr/src/linux-headers-$(uname -r) a-i ngcp-mediaproxy-ng-kernel + +2) DKMS: + +% sudo apt-get install ngcp-mediaproxy-ng-kernel-dkms +% sudo dkms add -m ngcp-mediaproxy-ng -v 0.1 +% sudo dkms build --kernelsourcedir /usr/src/linux-headers-$(uname -r) -m ngcp-mediaproxy-ng -v 0.1 +% sudo dkms install -m ngcp-mediaproxy-ng -v 0.1 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 000000000..7737f63df --- /dev/null +++ b/debian/changelog @@ -0,0 +1,138 @@ +ngcp-mediaproxy-ng (1.6.6) unstable; urgency=low + + * Support userspace-only operation mode + + -- Richard Fuchs Wed, 16 May 2012 09:33:18 -0400 + +ngcp-mediaproxy-ng (1.6.5) unstable; urgency=low + + * Fix daemon failing to correctly interpret "delete full call" message + + -- Richard Fuchs Fri, 04 May 2012 11:21:11 -0400 + +ngcp-mediaproxy-ng (1.6.4) unstable; urgency=low + + * Fix segfault + + -- Richard Fuchs Tue, 06 Mar 2012 09:51:07 -0500 + +ngcp-mediaproxy-ng (1.6.3) unstable; urgency=low + + * Fix the UDP control protocol + + -- Richard Fuchs Tue, 06 Mar 2012 07:12:47 -0500 + +ngcp-mediaproxy-ng (1.6.2) unstable; urgency=low + + * Check from/to tags in delete message + * Implement via-branch handling + * Don't strip debug symbols + + -- Richard Fuchs Mon, 05 Mar 2012 04:31:07 -0500 + +ngcp-mediaproxy-ng (1.6.1) unstable; urgency=low + + * Correctly remember address family across re-invites etc + + -- Richard Fuchs Wed, 08 Feb 2012 10:34:33 -0500 + +ngcp-mediaproxy-ng (1.6.0) unstable; urgency=low + + * Add full IPv6 support + * Attempt to handle unparsable proxy commands as much as possible + * Improve human-readable output in /proc + * Fix handling of calls with multiple media streams + + -- Richard Fuchs Thu, 26 Jan 2012 07:30:25 -0500 + +ngcp-mediaproxy-ng (1.5.3) unstable; urgency=low + + * Fix incorrect handling of lookups received without prior request + * Fix a long-standing bug that caused from/to tags to be ignored when using UDP protocol + * Properly timeout and cleanup UDP cookies + * Fix table 0 not showing up in /proc/mediaproxy/list + + -- Richard Fuchs Thu, 08 Dec 2011 11:05:30 -0500 + +ngcp-mediaproxy-ng (1.5.2) unstable; urgency=low + + * Fix bad tagging + + -- Richard Fuchs Tue, 20 Sep 2011 10:41:42 -0400 + +ngcp-mediaproxy-ng (1.5.1) unstable; urgency=low + + * Return a dummy/error reply over UDP when a call doesn't exist, so + kamailio doesn't think we're dead + + -- Richard Fuchs Tue, 20 Sep 2011 10:41:42 -0400 + +ngcp-mediaproxy-ng (1.5.0) unstable; urgency=low + + * Rework port re-use logic so it never opens new ports when it doesn't + need to. + + -- Richard Fuchs Thu, 15 Sep 2011 10:42:57 -0400 + +ngcp-mediaproxy-ng (1.4.2) unstable; urgency=low + + * Slightly increase syslog verbosity + * Fix obscure 3-way call connect issue + + -- Richard Fuchs Fri, 02 Sep 2011 17:09:38 -0400 + +ngcp-mediaproxy-ng (1.4.1) unstable; urgency=low + + * Fix a memory leak + + -- Richard Fuchs Wed, 10 Aug 2011 17:01:56 -0400 + +ngcp-mediaproxy-ng (1.4.0) unstable; urgency=low + + * Support HA through persistent Redis storage + + -- Richard Fuchs Fri, 10 Jun 2011 13:50:50 -0400 + +ngcp-mediaproxy-ng (1.3.5) unstable; urgency=low + + * Fix dst reference count issues causing kernel warnings under some circumstances + + -- Richard Fuchs Thu, 19 May 2011 13:43:16 -0400 + +ngcp-mediaproxy-ng (1.3.4) unstable; urgency=low + + * Make the daemon more aggressive with invalidating peer information + + -- Richard Fuchs Thu, 05 May 2011 16:08:31 -0400 + +ngcp-mediaproxy-ng (1.3.3) unstable; urgency=low + + * Add --advertised-ip to defaults file. + + -- Andreas Granig Wed, 04 May 2011 23:26:30 +0200 + +ngcp-mediaproxy-ng (1.3.2) unstable; urgency=low + + * Introduce --advertised-ip parameter + * Minor code & help text cleanups + + -- Richard Fuchs Tue, 03 May 2011 17:20:11 -0400 + +ngcp-mediaproxy-ng (1.3.1) unstable; urgency=low + + * dkms postinst: do not execute init script if it is not present yet. + + -- Michael Prokop Fri, 29 Apr 2011 17:18:41 +0200 + +ngcp-mediaproxy-ng (1.3.0) unstable; urgency=low + + * Release for 2.2 + * Fixed version number to align with old, non-debianized versioning scheme. + + -- Andreas Granig Fri, 29 Apr 2011 12:01:56 +0200 + +ngcp-mediaproxy-ng (0.1) unstable; urgency=low + + * Initial release. + + -- Andreas Granig Tue, 26 Apr 2011 18:55:01 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 000000000..7ed6ff82d --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 000000000..086ce68e5 --- /dev/null +++ b/debian/control @@ -0,0 +1,60 @@ +Source: ngcp-mediaproxy-ng +Section: net +Priority: extra +Maintainer: Sipwise Development Team +Build-Depends: debhelper (>= 5), iptables-dev (>= 1.4), libglib2.0-dev, libpcre3-dev, + libxmlrpc-c3-dev (>= 1.16.07) | libxmlrpc-core-c3-dev (>= 1.16.07), libcurl4-openssl-dev | libcurl4-gnutls-dev | + libcurl3-openssl-dev | libcurl3-gnutls-dev +Standards-Version: 3.9.2 +Homepage: http://sipwise.com/ + +Package: ngcp-mediaproxy-ng-daemon +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Pre-Depends: ngcp-mediaproxy-ng-kernel-dkms (>= ${source:Version}) +Description: Proxy for RTP and media streams used in NGCP, userspace part. + This daemon handles the first stages of proxying media streams and talks to + the kernel part of the proxy for eventual high-performance packet forwarding. + +Package: ngcp-mediaproxy-ng-iptables +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: IPtables extension module for the kernel-space NGCP media proxy. + Provides the IPtables extension needed to configure the mediaproxy rule. + +Package: ngcp-mediaproxy-ng +Architecture: all +Depends: ngcp-mediaproxy-ng-daemon (>= ${source:Version}), ngcp-mediaproxy-ng-iptables (>= ${source:Version}), + ngcp-mediaproxy-ng-kernel-dkms (>= ${source:Version}) +Description: NGCP RTP/media proxy - meta package. + This is a meta package for easy installation of all three parts of the NGCP + media proxy. It will install the user-space daemon, the kernel-space IPtables + module, and the IPtables extension module. + +Package: ngcp-mediaproxy-ng-kernel-source +Architecture: all +Depends: debhelper (>= 5), module-assistant, ${misc:Depends} +Description: IPtables kernel module for the NGCP media proxy - source. + Provides the kernel-space part of the NGCP media proxy for high- + performance packet forwarding. + This package contains the source to be built with module-assistant or + kernel-package. + +Package: ngcp-mediaproxy-ng-kernel-dkms +Architecture: all +Depends: dkms (>= 1.95), ${misc:Depends} +Description: IPtables kernel module for the NGCP media proxy - DKMS. + Provides the kernel-space part of the NGCP media proxy for high- + performance packet forwarding. + This package contains the source to be built with dkms. + +Package: ngcp-mediaproxy-ng-dev +Architecture: all +Section: libdevel +Depends: debhelper (>= 5), ${misc:Depends} +Description: Development files for mediaproxy-ng + This package provides the header files of the mediaproxy-ng + software. + . + Install this package if you wish to develop your own programs using + mediaproxy-ng. diff --git a/debian/control.modules.in b/debian/control.modules.in new file mode 100644 index 000000000..394d85ddd --- /dev/null +++ b/debian/control.modules.in @@ -0,0 +1,15 @@ +Source: ngcp-mediaproxy-ng-kernel +Section: kernel +Priority: optional +Maintainer: Richard Fuchs +Build-Depends: debhelper (>= 5) +Standards-Version: 3.9.1 +Homepage: http://sipwise.com/ + +Package: ngcp-mediaproxy-ng-kernel-modules-_KVERS_ +Architecture: any +Depends: linux-modules-_KVERS_ | linux-image-_KVERS_ +Provides: ngcp-mediaproxy-ng-kernel +Description: TODO + This package provides the ngcp-mediaproxy-ng module for + the Linux kernel version _KVERS_. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 000000000..c2f30716c --- /dev/null +++ b/debian/copyright @@ -0,0 +1,7 @@ +Upstream Author: The Sipwise Team - http://sipwise.com/ +Copyright: Copyright (c) 2007-2011 Sipwise GmbH, Austria +License: All software included in this package is + Copyright (c) Sipwise GmbH, Austria. + All rights reserved. You may not copy, distribute + or modify without prior written permission from + Sipwise GmbH, Austria. diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 000000000..236670a2d --- /dev/null +++ b/debian/dirs @@ -0,0 +1 @@ +usr/sbin diff --git a/debian/dkms.conf.in b/debian/dkms.conf.in new file mode 100644 index 000000000..876955f67 --- /dev/null +++ b/debian/dkms.conf.in @@ -0,0 +1,7 @@ +PACKAGE_NAME="ngcp-mediaproxy-ng" +PACKAGE_VERSION="__VERSION__" +MAKE[0]="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build MEDIAPROXY_VERSION=\"__VERSION__\"" +CLEAN="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build clean" +AUTOINSTALL=yes +BUILT_MODULE_NAME[0]="xt_MEDIAPROXY" +DEST_MODULE_LOCATION[0]=/extra diff --git a/debian/ngcp-mediaproxy-ng-daemon.default b/debian/ngcp-mediaproxy-ng-daemon.default new file mode 100644 index 000000000..3d2966b13 --- /dev/null +++ b/debian/ngcp-mediaproxy-ng-daemon.default @@ -0,0 +1,19 @@ +RUN_MEDIAPROXY=no +LISTEN=25060 +LISTEN_UDP=12222 +# ADDRESS=... +# ADV_ADDRESS=... +# ADDRESS_IPV6=... +# ADV_ADDRESS_IPV6=... +TIMEOUT=60 +SILENT_TIMEOUT=3600 +PIDFILE=/var/run/ngcp-mediaproxy-ng-daemon.pid +FORK=yes +# TOS=184 +TABLE=0 +# NO_FALLBACK=yes +# PORT_MIN=30000 +# PORT_MAX=50000 +# REDIS=127.0.0.1:6379 +# REDIS_DB=1 +# B2B_URL=http://127.0.0.1:8090/ diff --git a/debian/ngcp-mediaproxy-ng-daemon.init b/debian/ngcp-mediaproxy-ng-daemon.init new file mode 100755 index 000000000..816f48a4f --- /dev/null +++ b/debian/ngcp-mediaproxy-ng-daemon.init @@ -0,0 +1,138 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: mediaproxy-ng +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Ngcp Mediaproxy-ng +# Description: Proxy for RTP and other media streams +### END INIT INFO + + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +NAME=ngcp-mediaproxy-ng-daemon +DESC="RTP/media proxy" +RUN_MEDIAPROXY=no +TABLE=0 + +case $(dirname $0) in + /*) FULLPATH=$(dirname $0);; + *) FULLPATH=$(pwd)/$(dirname $0);; +esac + +DAEMON=`which mediaproxy-ng` +DEFAULTS=/etc/default/${NAME} + +test -f $DAEMON || exit 0 + +# Load startup options if available +if [ -f $DEFAULTS ]; then + . $DEFAULTS || true +fi + +if [ "$RUN_MEDIAPROXY" != "yes" ]; then + echo "mediaproxy-ng not yet configured. Edit $DEFAULTS first." + exit 0 +fi +[ -z "$PIDFILE" ] && PIDFILE="/var/run/mediaproxy-ng.pid" + +set -e + +. /lib/lsb/init-functions + +OPTIONS="" + +[ -z "$ADDRESS" ] || OPTIONS="$OPTIONS --ip=$ADDRESS" +[ -z "$LISTEN" ] || OPTIONS="$OPTIONS --listen=$LISTEN" +[ -z "$LISTEN_UDP" ] || OPTIONS="$OPTIONS --listen-udp=$LISTEN_UDP" +[ -z "$TIMEOUT" ] || OPTIONS="$OPTIONS --timeout=$TIMEOUT" +[ -z "$SILENT_TIMEOUT" ] || OPTIONS="$OPTIONS --silent-timeout=$SILENT_TIMEOUT" +[ -z "$PIDFILE" ] || OPTIONS="$OPTIONS --pidfile=$PIDFILE" +[ -z "$TOS" ] || OPTIONS="$OPTIONS --tos=$TOS" +[ -z "$PORT_MIN" ] || OPTIONS="$OPTIONS --port-min=$PORT_MIN" +[ -z "$PORT_MAX" ] || OPTIONS="$OPTIONS --port-max=$PORT_MAX" +[ -z "$ADV_ADDRESS" ] || OPTIONS="$OPTIONS --advertised-ip=$ADV_ADDRESS" +[ -z "$ADDRESS_IPV6" ] || OPTIONS="$OPTIONS --ip6=$ADDRESS_IPV6" +[ -z "$ADV_ADDRESS_IPV6" ] || OPTIONS="$OPTIONS --advertised-ip6=$ADV_ADDRESS_IPV6" +[ -z "$REDIS" ] || OPTIONS="$OPTIONS --redis=$REDIS" +[ -z "$REDIS_DB" ] || OPTIONS="$OPTIONS --redis-db=$REDIS_DB" +[ -z "$B2B_URL" ] || OPTIONS="$OPTIONS --b2b-url=$B2B_URL" +[ -z "$NO_FALLBACK" -o \( "$NO_FALLBACK" != "1" -a "$NO_FALLBACK" != "yes" \) ] || OPTIONS="$OPTIONS --no-fallback" +OPTIONS="$OPTIONS --table=$TABLE" +if test "$FORK" = "no" ; then + OPTIONS="$OPTIONS --foreground" +fi + +case "$1" in + start) + + echo -n "Starting $DESC: $NAME" + set +e + modprobe xt_MEDIAPROXY + echo "del $TABLE" > /proc/mediaproxy/control 2>/dev/null + iptables -D INPUT -j MEDIAPROXY --id $TABLE 2>/dev/null + iptables -D INPUT -p udp -j MEDIAPROXY --id $TABLE 2>/dev/null + ip6tables -D INPUT -p udp -j MEDIAPROXY --id $TABLE 2>/dev/null + iptables -I INPUT -p udp -j MEDIAPROXY --id $TABLE + ip6tables -I INPUT -p udp -j MEDIAPROXY --id $TABLE + set -e + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + --exec $DAEMON -- $OPTIONS || echo -n " already running" + log_end_msg $? + ;; + stop) + echo -n "Stopping $DESC: $NAME" + start-stop-daemon --oknodo --stop --quiet --pidfile $PIDFILE \ + --exec $DAEMON + if [ "$?" -ne 0 ]; then + return $? + fi + set +e + echo "del $TABLE" > /proc/mediaproxy/control 2>/dev/null + iptables -D INPUT -j MEDIAPROXY --id $TABLE 2>/dev/null + iptables -D INPUT -p udp -j MEDIAPROXY --id $TABLE 2>/dev/null + ip6tables -D INPUT -p udp -j MEDIAPROXY --id $TABLE 2>/dev/null + rmmod ipt_MEDIAPROXY 2>/dev/null + rmmod xt_MEDIAPROXY 2>/dev/null + set -e + rm -f $PIDFILE + log_end_msg $? + ;; + restart|force-reload) + + echo -n "Restarting $DESC: $NAME" + start-stop-daemon --oknodo --stop --quiet --pidfile \ + $PIDFILE --exec $DAEMON + if [ "$?" -ne 0 ]; then + return $? + fi + rm -f $PIDFILE + sleep 1 + set +e + if [ -e /proc/mediaproxy/control ]; then + echo "del $TABLE" > /proc/mediaproxy/control 2>/dev/null + fi + iptables -D INPUT -j MEDIAPROXY --id $TABLE 2>/dev/null + iptables -D INPUT -p udp -j MEDIAPROXY --id $TABLE 2>/dev/null + ip6tables -D INPUT -p udp -j MEDIAPROXY --id $TABLE 2>/dev/null + rmmod ipt_MEDIAPROXY 2>/dev/null + rmmod xt_MEDIAPROXY 2>/dev/null + modprobe xt_MEDIAPROXY + iptables -I INPUT -p udp -j MEDIAPROXY --id $TABLE + ip6tables -I INPUT -p udp -j MEDIAPROXY --id $TABLE + set -e + start-stop-daemon --start --quiet --pidfile \ + $PIDFILE --exec $DAEMON -- $OPTIONS + log_end_msg $? + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + *) + echo "Usage: $0 {start|stop|restart|force-reload|status}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/ngcp-mediaproxy-ng-daemon.install b/debian/ngcp-mediaproxy-ng-daemon.install new file mode 100644 index 000000000..77a48e8aa --- /dev/null +++ b/debian/ngcp-mediaproxy-ng-daemon.install @@ -0,0 +1 @@ +daemon/mediaproxy-ng /usr/sbin/ diff --git a/debian/ngcp-mediaproxy-ng-daemon.postinst b/debian/ngcp-mediaproxy-ng-daemon.postinst new file mode 100755 index 000000000..5f48c7756 --- /dev/null +++ b/debian/ngcp-mediaproxy-ng-daemon.postinst @@ -0,0 +1,54 @@ +#!/bin/sh +# postinst script for ngcp-mediaproxy-ng-daemon + +set -e + +restart_handler() { + if [ -x "/etc/init.d/ngcp-mediaproxy-ng-daemon" ]; then + if [ -x "$(which invoke-rc.d 2>/dev/null)" ]; then + invoke-rc.d ngcp-mediaproxy-ng-daemon restart || exit $? + else + /etc/init.d/ngcp-mediaproxy-ng-daemon restart || exit $? + fi + fi +} + +initscript_handler() { + if [ -x "/etc/init.d/ngcp-mediaproxy-ng-daemon" ]; then + update-rc.d ngcp-mediaproxy-ng-daemon defaults >/dev/null + invoke-rc.d ngcp-mediaproxy-ng-daemon start || exit $? + fi +} + +init_handler() { + # just invoke init script wrappers on ce systems since + # they do not provide ngcp-check_active and we don't + # have to handle inactive nodes + if ! [ -x "$(which ngcp-check_active 2>/dev/null)" ]; then + restart_handler + initscript_handler + else # do not restart daemon on inactive node in pro systems + if ngcp-check_active ; then + echo "Active node detected, restarting ngcp-mediaproxy-ng-daemon" + restart_handler + else + echo "Inactive node detected, ignoring request to restart ngcp-mediaproxy-ng-daemon" + fi + fi +} + +case "$1" in + configure) + init_handler + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/ngcp-mediaproxy-ng-daemon.postrm b/debian/ngcp-mediaproxy-ng-daemon.postrm new file mode 100755 index 000000000..d6a4f0acf --- /dev/null +++ b/debian/ngcp-mediaproxy-ng-daemon.postrm @@ -0,0 +1,18 @@ +#!/bin/sh +# postrm script for ngcp-mediaproxy-ng-daemon + +set -e + +removal_wrapper() { + # remove the init script only on ce systems, as the + # the pro system handle it inside the monitoring/HA setup + if ! [ -x "$(which ngcp-check_active 2>/dev/null)" ]; then + update-rc.d ngcp-mediaproxy-ng-daemon remove >/dev/null + fi +} + +if [ "$1" = "purge" ] ; then + removal_wrapper +fi + +exit 0 diff --git a/debian/ngcp-mediaproxy-ng-daemon.prerm b/debian/ngcp-mediaproxy-ng-daemon.prerm new file mode 100755 index 000000000..e8f3824fa --- /dev/null +++ b/debian/ngcp-mediaproxy-ng-daemon.prerm @@ -0,0 +1,33 @@ +#!/bin/sh +# prerm script for ngcp-mediaproxy-ng-daemon + +set -e + +stop_handler() { + if [ -x "/etc/init.d/ngcp-mediaproxy-ng-daemon" ]; then + invoke-rc.d ngcp-mediaproxy-ng-daemon stop || exit $? + fi +} + +stop_daemon() { + # just stop the service on ce systems because + # the pro system handle it as part of their monitoring/HA setup + if ! [ -x "$(which ngcp-check_active 2>/dev/null)" ]; then + stop_handler + else + case "$2" in + upgrade) + echo "Detected upgrade procedure, not stopping ngcp-mediaproxy-ng-daemon service." + ;; + remove|purge) + stop_handler + ;; + esac + fi +} + +if [ "$1" = "prerm" ] ; then + stop_daemon +fi + +exit 0 diff --git a/debian/ngcp-mediaproxy-ng-dev.install b/debian/ngcp-mediaproxy-ng-dev.install new file mode 100644 index 000000000..48aa522e1 --- /dev/null +++ b/debian/ngcp-mediaproxy-ng-dev.install @@ -0,0 +1 @@ +daemon/*.h /usr/include/mediaproxy-ng/ diff --git a/debian/ngcp-mediaproxy-ng-iptables.install b/debian/ngcp-mediaproxy-ng-iptables.install new file mode 100644 index 000000000..e91fc8026 --- /dev/null +++ b/debian/ngcp-mediaproxy-ng-iptables.install @@ -0,0 +1 @@ +iptables-extension/libxt_MEDIAPROXY.so /lib/xtables/ diff --git a/debian/ngcp-mediaproxy-ng-kernel-dkms.postinst b/debian/ngcp-mediaproxy-ng-kernel-dkms.postinst new file mode 100644 index 000000000..66de7230c --- /dev/null +++ b/debian/ngcp-mediaproxy-ng-kernel-dkms.postinst @@ -0,0 +1,29 @@ +#!/bin/sh + +set -e + +package=ngcp-mediaproxy-ng-kernel-dkms +name=ngcp-mediaproxy-ng + +version=`dpkg-query -W -f='${Version}' "$package" \ + |rev|cut -d- -f2-|rev|cut -d':' -f2|tr -d "\n"` + +isadded=`dkms status -m "$name" -v "$version"` + +if [ "x${isadded}" = "x" ] ; then + dkms add -m "$name" -v "$version" +fi + +if [ "$1" = 'configure' ] ; then + dkms build -m "$name" -v "$version" && dkms install -m "$name" -v "$version" || true + + # try to start the daemon + if [ -x /etc/init.d/ngcp-mediaproxy-ng-daemon ] ; then + invoke-rc.d ngcp-mediaproxy-ng-daemon start || true + fi +fi + +#DEBHELPER# + +exit 0 + diff --git a/debian/ngcp-mediaproxy-ng-kernel-dkms.prerm b/debian/ngcp-mediaproxy-ng-kernel-dkms.prerm new file mode 100644 index 000000000..61a0200c6 --- /dev/null +++ b/debian/ngcp-mediaproxy-ng-kernel-dkms.prerm @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e + +package=ngcp-mediaproxy-ng-kernel-dkms +name=ngcp-mediaproxy-ng + +version=`dpkg-query -W -f='${Version}' "$package" \ + |rev|cut -d- -f2-|rev|cut -d':' -f2|tr -d "\n"` + +# make sure it's not running +if [ -x /etc/init.d/ngcp-mediaproxy-ng-daemon ] ; then + invoke-rc.d ngcp-mediaproxy-ng-daemon stop || true +fi + +dkms remove -m "$name" -v "$version" --all || true + +#DEBHELPER# + +exit 0 diff --git a/debian/ngcp-mediaproxy-ng-kernel-source.links b/debian/ngcp-mediaproxy-ng-kernel-source.links new file mode 100644 index 000000000..e246b833c --- /dev/null +++ b/debian/ngcp-mediaproxy-ng-kernel-source.links @@ -0,0 +1 @@ +/usr/share/modass/packages/default.sh /usr/share/modass/overrides/ngcp-mediaproxy-ng-kernel-source diff --git a/debian/rules b/debian/rules new file mode 100755 index 000000000..1e7901766 --- /dev/null +++ b/debian/rules @@ -0,0 +1,108 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +# export DH_VERBOSE=1 + +b=$(CURDIR)/debian + +## kernel package specific stuff +# Name of the source package +psource:=ngcp-mediaproxy-ng-kernel-source +# Name of the dkms package +pdkms:=ngcp-mediaproxy-ng-kernel-dkms +# short upstream name, used for module source directory +sname:=ngcp-mediaproxy-ng +# Source version +sversion:=$(shell dpkg-parsechangelog|grep "^Version:"|cut -d" " -f2|rev|cut -d- -f2-|rev|cut -d':' -f2) + +PACKAGE=ngcp-mediaproxy-ng-kernel +MA_DIR ?= /usr/share/modass +-include $(MA_DIR)/include/generic.make +-include $(MA_DIR)/include/common-rules.make + +kdist_configure: prep-deb-files + +kdist_clean: clean + $(MAKE) $(MFLAGS) -f debian/rules clean +## end of kernel package specific stuff + +build: build-stamp + +build-stamp: + dh_testdir + make -C iptables-extension + make -C daemon + touch $@ + +clean: + dh_testdir + dh_testroot + cd daemon && $(MAKE) clean && cd .. + rm -f build-stamp + rm -f iptables-extension/libxt_MEDIAPROXY.so + rm -f daemon/mediaproxy-ng daemon/build_time.h daemon/.depend kernel-module/.xt_MEDIAPROXY.o.d + rm -rf kernel-module/.tmp_versions + dh_clean + -rm -rf debian/build + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Create the directories to install the source into + dh_installdirs -p$(psource) usr/src/modules/$(sname)/debian + dh_installdirs -p$(pdkms) usr/src/$(sname)-$(sversion) + + # Copy only the driver source to the proper locations + cd kernel-module && cp Makefile *.c *.h ../debian/$(psource)/usr/src/modules/$(sname) + cd kernel-module && cp Makefile *.c *.h ../debian/$(pdkms)/usr/src/$(sname)-$(sversion) + + # Copy the needed debian/ pieces to the proper location + cp debian/*.modules.in* debian/$(psource)/usr/src/modules/$(sname)/debian + cp debian/control debian/changelog debian/copyright debian/README.Debian \ + debian/compat debian/$(psource)/usr/src/modules/$(sname)/debian/ + install -m 0755 debian/rules.modules debian/$(psource)/usr/src/modules/$(sname)/debian/rules + cd debian/$(psource)/usr/src && tar c modules | bzip2 -9 > $(sname).tar.bz2 && rm -rf modules + + # Prepare dkms.conf from the dkms.conf.in template + sed "s/__VERSION__/$(sversion)/g" debian/dkms.conf.in > debian/$(pdkms)/usr/src/$(sname)-$(sversion)/dkms.conf + + dh_install + +%: + @echo "--- Building: $@" + dh_installdirs -p$@ -P$(b)/$@ + dh_link -p$@ -P$(b)/$@ + dh_installdocs -p$@ -P$(b)/$@ + dh_installchangelogs -p$@ -P$(b)/$@ + dh_installinit -p$@ -P$(b)/$@ + dh_install -p$@ -P$(b)/$@ + dh_strip -p$@ -P$(b)/$@ --keep-debug + dh_compress -p$@ -P$(b)/$@ + dh_fixperms -p$@ -P$(b)/$@ + dh_makeshlibs -p$@ -P$(b)/$@ -V + dh_installdeb -p$@ -P$(b)/$@ + dh_shlibdeps -p$@ -P$(b)/$@ + dh_installdebconf -p$@ -P$(b)/$@ + dh_gencontrol -p$@ -P$(b)/$@ + dh_md5sums -p$@ -P$(b)/$@ + dh_builddeb -p$@ -P$(b)/$@ + +# Build architecture dependant packages +binary-arch: install \ + ngcp-mediaproxy-ng-iptables ngcp-mediaproxy-ng + +# Build architecture independant packages +binary-indep: build install \ + ngcp-mediaproxy-ng-daemon ngcp-mediaproxy-ng-kernel-dkms ngcp-mediaproxy-ng-kernel-source ngcp-mediaproxy-ng-dev + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install kdist kdist_configure kdist_image kdist_clean diff --git a/debian/rules.modules b/debian/rules.modules new file mode 100644 index 000000000..e6edfd923 --- /dev/null +++ b/debian/rules.modules @@ -0,0 +1,83 @@ +#!/usr/bin/make -f + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +CFLAGS ?= -Wall -g + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +# Name of the source package +psource:=ngcp-mediaproxy-ng-kernel-source + +# The short upstream name, used for the module source directory +sname:=ngcp-mediaproxy-ng + +### KERNEL SETUP +### Setup the stuff needed for making kernel module packages +### taken from /usr/share/kernel-package/sample.module.rules + +# prefix of the target package name +PACKAGE=ngcp-mediaproxy-ng-kernel-modules +# modifieable for experiments or debugging m-a +MA_DIR ?= /usr/share/modass +# load generic variable handling +-include $(MA_DIR)/include/generic.make +# load default rules, including kdist, kdist_image, ... +-include $(MA_DIR)/include/common-rules.make + +# module assistant calculates all needed things for us and sets +# following variables: +# KSRC (kernel source directory), KVERS (kernel version string), KDREV +# (revision of the Debian kernel-image package), CC (the correct +# compiler), VERSION (the final package version string), PKGNAME (full +# package name with KVERS included), DEB_DESTDIR (path to store DEBs) + +# The kdist_configure target is called by make-kpkg modules_config and +# by kdist* rules by dependency. It should configure the module so it is +# ready for compilation (mostly useful for calling configure). +# prep-deb-files from module-assistant creates the neccessary debian/ files +kdist_configure: prep-deb-files + +# the kdist_clean target is called by make-kpkg modules_clean and from +# kdist* rules. It is responsible for cleaning up any changes that have +# been made by the other kdist_commands (except for the .deb files created) +kdist_clean: clean + $(MAKE) $(MFLAGS) -f debian/rules clean +# +### end KERNEL SETUP + +# the binary-modules rule is invoked by module-assistant while processing the +# kdist* targets. It is called by module-assistant or make-kpkg and *not* +# during a normal build +binary-modules: prep-deb-files + dh_testroot + dh_clean -k + + # Build the module + $(MAKE) modules KSRC=$(KSRC) KVER=$(KVERS) + + # Install the module + install -D -m 0644 xt_MEDIAPROXY.ko debian/$(PACKAGE)-$(KVERS)/lib/modules/$(KVERS)/extra/xt_MEDIAPROXY.ko + + dh_installdocs + dh_installchangelogs + dh_compress + dh_fixperms + dh_installmodules + dh_installdeb + dh_gencontrol -- -v$(VERSION) + dh_md5sums + dh_builddeb --destdir=$(DEB_DESTDIR) + dh_clean -k + +clean: + dh_testdir + $(MAKE) clean + dh_clean + +.PHONY: clean binary-modules kdist kdist_configure kdist_image kdist_clean diff --git a/iptables-extension/Makefile b/iptables-extension/Makefile new file mode 100644 index 000000000..4b99a2cf5 --- /dev/null +++ b/iptables-extension/Makefile @@ -0,0 +1,37 @@ +CFLAGS = -O2 -Wall -shared -fPIC +CFLAGS += -DMEDIAPROXY_VERSION="\"$(shell dpkg-parsechangelog -l../debian/changelog | awk '/^Version: / {print $$2}')\"" + +XTABLES = $(shell test -e /usr/include/xtables.h && echo 1) +IPTABLES = $(shell test -e /usr/include/iptables.h && echo 1) +IP6TABLES = $(shell test -e /usr/include/ip6tables.h && echo 1) + +.PHONY: all module clean + +all: module + +ifeq ($(XTABLES),1) + +module: libxt_MEDIAPROXY.so + +libxt_MEDIAPROXY.so: libxt_MEDIAPROXY.c + gcc $(CFLAGS) -o libxt_MEDIAPROXY.so libxt_MEDIAPROXY.c + +else + +ifeq ($(IPTABLES),1) +module: libipt_MEDIAPROXY.so +endif +ifeq ($(IP6TABLES),1) +module: libip6t_MEDIAPROXY.so +endif + +libipt_MEDIAPROXY.so: libxt_MEDIAPROXY.c + gcc $(CFLAGS) -D__ipt -o libipt_MEDIAPROXY.so libxt_MEDIAPROXY.c + +libip6t_MEDIAPROXY.so: libxt_MEDIAPROXY.c + gcc $(CFLAGS) -D__ip6t -o libip6t_MEDIAPROXY.so libxt_MEDIAPROXY.c + +endif + +clean: + rm -f libxt_MEDIAPROXY.so libipt_MEDIAPROXY.so libip6t_MEDIAPROXY.so diff --git a/iptables-extension/libxt_MEDIAPROXY.c b/iptables-extension/libxt_MEDIAPROXY.c new file mode 100644 index 000000000..c307c991b --- /dev/null +++ b/iptables-extension/libxt_MEDIAPROXY.c @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include +#include + +#if defined(__ipt) +#include +#elif defined(__ip6t) +#include +#else +#include +#endif + +#include + +#if defined(__ipt) +#include +#elif defined(__ip6t) +#include +#else +#include +#endif + +#include "../kernel-module/xt_MEDIAPROXY.h" + + +static void help(void) { + printf( + "MEDIAPROXY (version %s) target options:\n" + " --id \n" + " Unique ID for this instance\n", + MEDIAPROXY_VERSION + ); +} + +#if defined(__ipt) +static int parse(int c, + char **argv, + int invert, + unsigned int *flags, + const struct ipt_entry *entry, + struct ipt_entry_target **target) { +#elif defined(__ip6t) +static int parse(int c, + char **argv, + int invert, + unsigned int *flags, + const struct ip6t_entry *entry, + struct ip6t_entry_target **target) { +#else +static int parse(int c, + char **argv, + int invert, + unsigned int *flags, + const void *entry, + struct xt_entry_target **target) { +#endif + + struct xt_mediaproxy_info *info = (void *) (*target)->data; + + if (c == '1') { + info->id = atoi(optarg); + if (flags) + *flags = 1; + } + else + return 0; + + return 1; +} + +static void final_check(unsigned int flags) { +#if defined(__ipt) || defined(__ip6t) + if (!flags) + exit_error(PARAMETER_PROBLEM, "You must specify --id"); +#else + if (!flags) + xtables_error(PARAMETER_PROBLEM, "You must specify --id"); +#endif +} + +#if defined(__ipt) +static void print(const struct ipt_ip *ip, const struct xt_entry_target *target, int numeric) { +#elif defined(__ip6t) +static void print(const struct ip6t_ip6 *ip, const struct xt_entry_target *target, int numeric) { +#else +static void print(const void *ip, const struct xt_entry_target *target, int numeric) { +#endif + struct xt_mediaproxy_info *info = (void *) target->data; + + printf("id %u", info->id); +} + +#if defined(__ipt) +static void save(const struct ipt_ip *ip, const struct xt_entry_target *target) { +#elif defined(__ip6t) +static void save(const struct ip6t_ip6 *ip, const struct xt_entry_target *target) { +#else +static void save(const void *ip, const struct xt_entry_target *target) { +#endif + struct xt_mediaproxy_info *info = (void *) target->data; + + printf("--id %u", info->id); +} + +static struct option opts[] = { + { "id", 1, NULL, '1' }, + { NULL, }, +}; + + +#if defined(__ipt) +static struct iptables_target mediaproxy4 = { + .name = "MEDIAPROXY", + .version = "1.3.6", + .size = IPT_ALIGN(sizeof(struct xt_mediaproxy_info)), + .userspacesize = IPT_ALIGN(sizeof(struct xt_mediaproxy_info)), + .help = help, + .parse = parse, + .final_check = final_check, + .print = print, + .save = save, + .extra_opts = opts, +}; +#elif defined(__ip6t) +static struct ip6tables_target mediaproxy6 = { + .name = "MEDIAPROXY", + .version = "1.3.6", + .size = IP6T_ALIGN(sizeof(struct xt_mediaproxy_info)), + .userspacesize = IP6T_ALIGN(sizeof(struct xt_mediaproxy_info)), + .help = help, + .parse = parse, + .final_check = final_check, + .print = print, + .save = save, + .extra_opts = opts, +}; +#else +static struct xtables_target mediaproxy4 = { + .name = "MEDIAPROXY", + .family = NFPROTO_IPV4, + .version = XTABLES_VERSION, + .size = XT_ALIGN(sizeof(struct xt_mediaproxy_info)), + .userspacesize = XT_ALIGN(sizeof(struct xt_mediaproxy_info)), + .help = help, + .parse = parse, + .final_check = final_check, + .print = print, + .save = save, + .extra_opts = opts, +}; + +static struct xtables_target mediaproxy6 = { + .name = "MEDIAPROXY", + .family = NFPROTO_IPV6, + .version = XTABLES_VERSION, + .size = XT_ALIGN(sizeof(struct xt_mediaproxy_info)), + .userspacesize = XT_ALIGN(sizeof(struct xt_mediaproxy_info)), + .help = help, + .parse = parse, + .final_check = final_check, + .print = print, + .save = save, + .extra_opts = opts, +}; +#endif + +void _init(void) { +#if defined(__ipt) + register_target(&mediaproxy4); +#elif defined(__ip6t) + register_target6(&mediaproxy6); +#else + xtables_register_target(&mediaproxy4); + xtables_register_target(&mediaproxy6); +#endif +} diff --git a/kernel-module/Makefile b/kernel-module/Makefile new file mode 100644 index 000000000..bed10ccb0 --- /dev/null +++ b/kernel-module/Makefile @@ -0,0 +1,21 @@ +PWD := $(shell pwd) +KSRC ?= /lib/modules/$(shell uname -r)/build +KBUILD := $(KSRC) +ifeq ($(origin MEDIAPROXY_VERSION), undefined) +MEDIAPROXY_VERSION := $(shell dpkg-parsechangelog -l../debian/changelog | awk '/^Version: / {print $$2}') +export MEDIAPROXY_VERSION +endif +EXTRA_CFLAGS += -DMEDIAPROXY_VERSION="\"$(MEDIAPROXY_VERSION)\"" -D__MP_EXTERNAL + +obj-m += xt_MEDIAPROXY.o + +.PHONY: modules clean patch + +modules: + make -C $(KBUILD) M=$(PWD) O=$(KBUILD) modules + +clean: + make -C $(KBUILD) M=$(PWD) clean + +patch: + ../utils/patch-kernel magic "$(PWD)" "$(KERNEL)" "$(MEDIAPROXY_VERSION)" diff --git a/kernel-module/xt_MEDIAPROXY.c b/kernel-module/xt_MEDIAPROXY.c new file mode 100644 index 000000000..36d3192ed --- /dev/null +++ b/kernel-module/xt_MEDIAPROXY.c @@ -0,0 +1,1424 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef __MP_EXTERNAL +#include +#else +#include "xt_MEDIAPROXY.h" +#endif + +MODULE_LICENSE("GPL"); + + + + +#define MAX_ID 64 /* - 1 */ + +#define MIPF "%i:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x:%u" +#define MIPP(x) (x).family, \ + (x).u8[0], \ + (x).u8[1], \ + (x).u8[2], \ + (x).u8[3], \ + (x).u8[4], \ + (x).u8[5], \ + (x).u8[6], \ + (x).u8[7], \ + (x).u8[8], \ + (x).u8[9], \ + (x).u8[10], \ + (x).u8[11], \ + (x).u8[12], \ + (x).u8[13], \ + (x).u8[14], \ + (x).u8[15], \ + (x).port + +#if 0 +#define DBG(x...) printk(KERN_DEBUG x) +#else +#define DBG(x...) ((void)0) +#endif + + + + + +static struct proc_dir_entry *my_proc_root; +static struct proc_dir_entry *proc_list; +static struct proc_dir_entry *proc_control; +static struct mediaproxy_table *table[64]; +static rwlock_t table_lock; + + + + +static ssize_t proc_control_write(struct file *, const char __user *, size_t, loff_t *); +static int proc_control_open(struct inode *, struct file *); +static int proc_control_close(struct inode *, struct file *); +static int proc_status(char *, char **, off_t, int, int *, void *); + +static ssize_t proc_main_control_write(struct file *, const char __user *, size_t, loff_t *); +static int proc_main_control_open(struct inode *, struct file *); +static int proc_main_control_close(struct inode *, struct file *); + +static int proc_list_open(struct inode *, struct file *); + +static void *proc_list_start(struct seq_file *, loff_t *); +static void proc_list_stop(struct seq_file *, void *); +static void *proc_list_next(struct seq_file *, void *, loff_t *); +static int proc_list_show(struct seq_file *, void *); + +static int proc_blist_open(struct inode *, struct file *); +static int proc_blist_close(struct inode *, struct file *); +static ssize_t proc_blist_read(struct file *, char __user *, size_t, loff_t *); + +static int proc_main_list_open(struct inode *, struct file *); + +static void *proc_main_list_start(struct seq_file *, loff_t *); +static void proc_main_list_stop(struct seq_file *, void *); +static void *proc_main_list_next(struct seq_file *, void *, loff_t *); +static int proc_main_list_show(struct seq_file *, void *); + +static void table_push(struct mediaproxy_table *); +static struct mediaproxy_target *get_target(struct mediaproxy_table *, u_int16_t); + + +static const struct file_operations proc_control_ops = { + .write = proc_control_write, + .open = proc_control_open, + .release = proc_control_close, +}; + +static const struct file_operations proc_main_control_ops = { + .write = proc_main_control_write, + .open = proc_main_control_open, + .release = proc_main_control_close, +}; + +static const struct file_operations proc_list_ops = { + .open = proc_list_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static const struct file_operations proc_blist_ops = { + .open = proc_blist_open, + .read = proc_blist_read, + .release = proc_blist_close, +}; + +static struct seq_operations proc_list_seq_ops = { + .start = proc_list_start, + .next = proc_list_next, + .stop = proc_list_stop, + .show = proc_list_show, +}; + +static const struct file_operations proc_main_list_ops = { + .open = proc_main_list_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static struct seq_operations proc_main_list_seq_ops = { + .start = proc_main_list_start, + .next = proc_main_list_next, + .stop = proc_main_list_stop, + .show = proc_main_list_show, +}; + + + + + +static struct mediaproxy_table *new_table(void) { + struct mediaproxy_table *t; + + DBG("Creating new table\n"); + + if (!try_module_get(THIS_MODULE)) + return NULL; + + t = kmalloc(sizeof(*t), GFP_KERNEL); + if (!t) { + module_put(THIS_MODULE); + return NULL; + } + + memset(t, 0, sizeof(*t)); + + atomic_set(&t->refcnt, 1); + rwlock_init(&t->target_lock); + t->id = -1; + + return t; +} + + + + +static void table_hold(struct mediaproxy_table *t) { + atomic_inc(&t->refcnt); +} + + + + + +static int table_create_proc(struct mediaproxy_table *t, u_int32_t id) { + char num[10]; + + sprintf(num, "%u", id); + + t->proc = create_proc_entry(num, S_IFDIR | S_IRUGO | S_IXUGO, my_proc_root); + if (!t->proc) + return -1; + /* t->proc->owner = THIS_MODULE; */ + + t->status = create_proc_entry("status", S_IFREG | S_IRUGO, t->proc); + if (!t->status) + return -1; + /* t->status->owner = THIS_MODULE; */ + t->status->read_proc = proc_status; + t->status->data = (void *) (unsigned long) id; + + t->control = create_proc_entry("control", S_IFREG | S_IWUSR | S_IWGRP, t->proc); + if (!t->control) + return -1; + /* t->control->owner = THIS_MODULE; */ + t->control->proc_fops = &proc_control_ops; + t->control->data = (void *) (unsigned long) id; + + t->list = create_proc_entry("list", S_IFREG | S_IRUGO, t->proc); + if (!t->list) + return -1; + /* t->list->owner = THIS_MODULE; */ + t->list->proc_fops = &proc_list_ops; + t->list->data = (void *) (unsigned long) id; + + t->blist = create_proc_entry("blist", S_IFREG | S_IRUGO, t->proc); + if (!t->blist) + return -1; + /* t->blist->owner = THIS_MODULE; */ + t->blist->proc_fops = &proc_blist_ops; + t->blist->data = (void *) (unsigned long) id; + + return 0; +} + + + + +static struct mediaproxy_table *new_table_link(u_int32_t id) { + struct mediaproxy_table *t; + unsigned long flags; + + if (id >= MAX_ID) + return NULL; + + t = new_table(); + if (!t) { + printk(KERN_WARNING "xt_MEDIAPROXY out of memory\n"); + return NULL; + } + + write_lock_irqsave(&table_lock, flags); + if (table[id]) { + write_unlock_irqrestore(&table_lock, flags); + table_push(t); + printk(KERN_WARNING "xt_MEDIAPROXY duplicate ID %u\n", id); + return NULL; + } + + table_hold(t); + table[id] = t; + t->id = id; + write_unlock_irqrestore(&table_lock, flags); + + if (table_create_proc(t, id)) + printk(KERN_WARNING "xt_MEDIAPROXY failed to create /proc entry for ID %u\n", id); + + + return t; +} + + + + + +static void target_push(struct mediaproxy_target *t) { + if (!t) + return; + + if (!atomic_dec_and_test(&t->refcnt)) + return; + + DBG("Freeing target\n"); + + kfree(t); +} + + + + + + +static void target_hold(struct mediaproxy_target *t) { + atomic_inc(&t->refcnt); +} + + + + + + +static void clear_proc(struct proc_dir_entry **e) { + if (!e || !*e) + return; + + remove_proc_entry((*e)->name, (*e)->parent); + *e = NULL; +} + + + + + +static void table_push(struct mediaproxy_table *t) { + int i, j; + + if (!t) + return; + + if (!atomic_dec_and_test(&t->refcnt)) + return; + + DBG("Freeing table\n"); + + for (i = 0; i < 256; i++) { + if (!t->target[i]) + continue; + + for (j = 0; j < 256; j++) { + if (!t->target[i][j]) + continue; + t->target[i][j]->table = -1; + target_push(t->target[i][j]); + t->target[i][j] = NULL; + } + + kfree(t->target[i]); + t->target[i] = NULL; + } + + clear_proc(&t->status); + clear_proc(&t->control); + clear_proc(&t->list); + clear_proc(&t->blist); + clear_proc(&t->proc); + + kfree(t); + + module_put(THIS_MODULE); +} + + + + +static int unlink_table(struct mediaproxy_table *t) { + unsigned long flags; + + if (t->id >= MAX_ID) + return -EINVAL; + + DBG("Unlinking table %u\n", t->id); + + write_lock_irqsave(&table_lock, flags); + if (t->id >= MAX_ID || table[t->id] != t) { + write_unlock_irqrestore(&table_lock, flags); + return -EINVAL; + } + if (t->pid) { + write_unlock_irqrestore(&table_lock, flags); + return -EBUSY; + } + table[t->id] = NULL; + t->id = -1; + write_unlock_irqrestore(&table_lock, flags); + + clear_proc(&t->status); + clear_proc(&t->control); + clear_proc(&t->list); + clear_proc(&t->blist); + clear_proc(&t->proc); + + table_push(t); + + return 0; +} + + + + +static struct mediaproxy_table *get_table(u_int32_t id) { + struct mediaproxy_table *t; + unsigned long flags; + + if (id >= MAX_ID) + return NULL; + + read_lock_irqsave(&table_lock, flags); + t = table[id]; + if (t) + table_hold(t); + read_unlock_irqrestore(&table_lock, flags); + + return t; +} + + + + +static int proc_status(char *page, char **start, off_t off, int count, int *eof, void *data) { + struct mediaproxy_table *t; + int len = 0; + unsigned long flags; + + u_int32_t id = (u_int32_t) (unsigned long) data; + t = get_table(id); + if (!t) + return -ENOENT; + + read_lock_irqsave(&t->target_lock, flags); + len += sprintf(page + len, "Refcount: %u\n", atomic_read(&t->refcnt) - 1); + len += sprintf(page + len, "Control PID: %u\n", t->pid); + len += sprintf(page + len, "Targets: %u\n", t->targets); + len += sprintf(page + len, "Buckets: %u\n", t->buckets); + read_unlock_irqrestore(&t->target_lock, flags); + + table_push(t); + + return len; +} + + + + +static int proc_main_list_open(struct inode *i, struct file *f) { + return seq_open(f, &proc_main_list_seq_ops); +} + + + + + +static void *proc_main_list_start(struct seq_file *f, loff_t *o) { + if (!try_module_get(THIS_MODULE)) + return NULL; + return proc_main_list_next(f, NULL, o); +} + +static void proc_main_list_stop(struct seq_file *f, void *v) { + module_put(THIS_MODULE); +} + +static void *proc_main_list_next(struct seq_file *f, void *v, loff_t *o) { /* v is invalid */ + struct mediaproxy_table *t = NULL; + u_int32_t id; + + if (*o < 0) + return NULL; + id = *o; + + while (id < MAX_ID) { + t = get_table(id++); + if (!t) + continue; + break; + } + + *o = id; + + return t; /* might be NULL */ +} + +static int proc_main_list_show(struct seq_file *f, void *v) { + struct mediaproxy_table *g = v; + + seq_printf(f, "%u\n", g->id); + table_push(g); + + return 0; +} + + + + + +static int proc_blist_open(struct inode *i, struct file *f) { + struct proc_dir_entry *pde; + u_int32_t id; + struct mediaproxy_table *t; + + pde = PDE(i); + id = (u_int32_t) (unsigned long) pde->data; + t = get_table(id); + if (!t) + return -ENOENT; + + table_push(t); + + return 0; +} + +static int proc_blist_close(struct inode *i, struct file *f) { + struct proc_dir_entry *pde; + u_int32_t id; + struct mediaproxy_table *t; + + pde = PDE(i); + id = (u_int32_t) (unsigned long) pde->data; + t = get_table(id); + if (!t) + return 0; + + table_push(t); + + return 0; +} + +static ssize_t proc_blist_read(struct file *f, char __user *b, size_t l, loff_t *o) { + struct inode *inode; + struct proc_dir_entry *pde; + u_int32_t id; + struct mediaproxy_table *t; + struct mediaproxy_list_entry op; + int err; + struct mediaproxy_target *g; + unsigned long flags; + + if (l != sizeof(op)) + return -EINVAL; + if (*o < 0) + return -EINVAL; + + inode = f->f_path.dentry->d_inode; + pde = PDE(inode); + id = (u_int32_t) (unsigned long) pde->data; + t = get_table(id); + if (!t) + return -ENOENT; + + for (;;) { + err = 0; + if (*o > 0xffff) + goto err; + + g = get_target(t, (*o)++); + if (g) + break; + } + + memset(&op, 0, sizeof(op)); + spin_lock_irqsave(&g->lock, flags); + memcpy(&op.target, &g->target, sizeof(op.target)); + memcpy(&op.stats, &g->stats, sizeof(op.stats)); + spin_unlock_irqrestore(&g->lock, flags); + + target_push(g); + + err = -EFAULT; + if (copy_to_user(b, &op, sizeof(op))) + goto err; + + table_push(t); + return l; + +err: + table_push(t); + return err; +} + + + + + +static int proc_list_open(struct inode *i, struct file *f) { + int err; + struct seq_file *p; + struct proc_dir_entry *pde; + u_int32_t id; + struct mediaproxy_table *t; + + pde = PDE(i); + id = (u_int32_t) (unsigned long) pde->data; + t = get_table(id); + if (!t) + return -ENOENT; + table_push(t); + + err = seq_open(f, &proc_list_seq_ops); + if (err) + return err; + + p = f->private_data; + p->private = (void *) (unsigned long) id; + + return 0; +} + + + + +static void *proc_list_start(struct seq_file *f, loff_t *o) { + return proc_list_next(f, NULL, o); +} + +static void proc_list_stop(struct seq_file *f, void *v) { +} + +static void *proc_list_next(struct seq_file *f, void *v, loff_t *o) { /* v is invalid */ + u_int32_t id = (u_int32_t) (unsigned long) f->private; + struct mediaproxy_target *g = NULL; + struct mediaproxy_table *t; + u_int16_t port; + unsigned char hi, lo; + unsigned long flags; + + if (*o < 0 || *o > 0xffff) + return NULL; + port = (u_int16_t) *o; + + t = get_table(id); + if (!t) + return NULL; + + hi = (port & 0xff00) >> 8; + lo = port & 0xff; + + read_lock_irqsave(&t->target_lock, flags); + for (;;) { + lo++; /* will make the iteration start from 1 */ + if (lo == 0) { + hi++; + if (hi == 0) + break; + } + if (!t->target[hi]) { + lo = 0xff; + continue; + } + + g = t->target[hi][lo]; + if (!g) + continue; + + target_hold(g); + break; + } + read_unlock_irqrestore(&t->target_lock, flags); + + *o = (hi << 8) | lo; + table_push(t); + + return g; +} + +static void proc_list_addr_print(struct seq_file *f, const char *s, const struct mp_address *a) { + seq_printf(f, " %6s ", s); + switch (a->family) { + case 0: + seq_printf(f, "\n"); + break; + case AF_INET: + seq_printf(f, "inet4 %u.%u.%u.%u:%u\n", a->u8[0], a->u8[1], a->u8[2], a->u8[3], a->port); + break; + case AF_INET6: + seq_printf(f, "inet6 [%x:%x:%x:%x:%x:%x:%x:%x]:%u\n", htons(a->u16[0]), htons(a->u16[1]), + htons(a->u16[2]), htons(a->u16[3]), htons(a->u16[4]), htons(a->u16[5]), + htons(a->u16[6]), htons(a->u16[7]), a->port); + break; + default: + seq_printf(f, "\n"); + break; + } +} + +static int proc_list_show(struct seq_file *f, void *v) { + struct mediaproxy_target *g = v; + unsigned long flags; + + spin_lock_irqsave(&g->lock, flags); + seq_printf(f, "port %5u:\n", g->target.target_port); + proc_list_addr_print(f, "src", &g->target.src_addr); + proc_list_addr_print(f, "dst", &g->target.dst_addr); + proc_list_addr_print(f, "mirror", &g->target.mirror_addr); + seq_printf(f, " stats: %20llu bytes, %20llu packets, %20llu errors\n", + g->stats.bytes, g->stats.packets, g->stats.errors); + spin_unlock_irqrestore(&g->lock, flags); + + target_push(g); + + return 0; +} + + + + + +static int table_del_target(struct mediaproxy_table *t, u_int16_t port) { + unsigned char hi, lo; + struct mediaproxy_target *g; + unsigned long flags; + + if (!port) + return -EINVAL; + + hi = (port & 0xff00) >> 8; + lo = port & 0xff; + + write_lock_irqsave(&t->target_lock, flags); + g = t->target[hi] ? t->target[hi][lo] : NULL; + if (g) { + t->target[hi][lo] = NULL; + t->targets--; + } + write_unlock_irqrestore(&t->target_lock, flags); + + if (!g) + return -ENOENT; + + target_push(g); + + return 0; +} + + + + +static int is_valid_address(struct mp_address *mpa) { + switch (mpa->family) { + case AF_INET: + if (!mpa->ipv4) + return 0; + break; + + case AF_INET6: + if (!mpa->u32[0] && !mpa->u32[1] && !mpa->u32[2] && !mpa->u32[3]) + return 0; + break; + + default: + return 0; + } + + if (!mpa->port) + return 0; + + return 1; +} + + + + + +static int table_new_target(struct mediaproxy_table *t, struct mediaproxy_target_info *i, int update) { + unsigned char hi, lo; + struct mediaproxy_target *g; + struct mediaproxy_target **gp; + struct mediaproxy_target *og = NULL; + int err; + unsigned long flags; + + if (!i->target_port) + return -EINVAL; + if (!is_valid_address(&i->src_addr)) + return -EINVAL; + if (!is_valid_address(&i->dst_addr)) + return -EINVAL; + if (i->src_addr.family != i->dst_addr.family) + return -EINVAL; + if (i->mirror_addr.family) { + if (!is_valid_address(&i->mirror_addr)) + return -EINVAL; + if (i->mirror_addr.family != i->src_addr.family) + return -EINVAL; + } + + DBG("Creating new target\n"); + + err = -ENOMEM; + g = kmalloc(sizeof(*g), GFP_KERNEL); + if (!g) + goto fail1; + memset(g, 0, sizeof(*g)); + g->table = t->id; + atomic_set(&g->refcnt, 1); + spin_lock_init(&g->lock); + memcpy(&g->target, i, sizeof(*i)); + + if (update) + gp = NULL; + else { + gp = kmalloc(sizeof(void *) * 256, GFP_KERNEL); + if (!gp) + goto fail2; + memset(gp, 0, sizeof(void *) * 256); + } + + hi = (i->target_port & 0xff00) >> 8; + lo = i->target_port & 0xff; + + write_lock_irqsave(&t->target_lock, flags); + if (!t->target[hi]) { + err = -ENOENT; + if (update) + goto fail4; + t->target[hi] = gp; + gp = NULL; + t->buckets++; + } + if (update) { + err = -ENOENT; + og = t->target[hi][lo]; + if (!og) + goto fail4; + + spin_lock(&og->lock); /* nested lock! irqs are disabled already */ + memcpy(&g->stats, &og->stats, sizeof(g->stats)); + spin_unlock(&og->lock); + } + else { + err = -EEXIST; + if (t->target[hi][lo]) + goto fail4; + } + + t->target[hi][lo] = g; + g = NULL; + if (!update) + t->targets++; + write_unlock_irqrestore(&t->target_lock, flags); + + if (gp) + kfree(gp); + if (og) + target_push(og); + + return 0; + +fail4: + write_unlock_irqrestore(&t->target_lock, flags); + if (gp) + kfree(gp); +fail2: + kfree(g); +fail1: + return err; +} + + + + + +static struct mediaproxy_target *get_target(struct mediaproxy_table *t, u_int16_t port) { + unsigned char hi, lo; + struct mediaproxy_target *r; + unsigned long flags; + + if (!t) + return NULL; + if (!port) + return NULL; + + hi = (port & 0xff00) >> 8; + lo = port & 0xff; + + read_lock_irqsave(&t->target_lock, flags); + r = t->target[hi] ? t->target[hi][lo] : NULL; + if (r) + target_hold(r); + read_unlock_irqrestore(&t->target_lock, flags); + + return r; +} + + + + + +static int proc_main_control_open(struct inode *inode, struct file *file) { + if (!try_module_get(THIS_MODULE)) + return -ENXIO; + return 0; +} + +static int proc_main_control_close(struct inode *inode, struct file *file) { + module_put(THIS_MODULE); + return 0; +} + +static ssize_t proc_main_control_write(struct file *file, const char __user *buf, size_t buflen, loff_t *off) { + char b[30]; + unsigned long id; + char *endp; + struct mediaproxy_table *t; + int err; + + if (buflen < 6 || buflen > 20) + return -EINVAL; + + if (copy_from_user(&b, buf, buflen)) + return -EFAULT; + + if (!strncmp(b, "add ", 4)) { + id = simple_strtoul(b + 4, &endp, 10); + if (endp == b + 4) + return -EINVAL; + if (id >= MAX_ID) + return -EINVAL; + t = new_table_link((u_int32_t) id); + if (!t) + return -EEXIST; + table_push(t); + t = NULL; + } + else if (!strncmp(b, "del ", 4)) { + id = simple_strtoul(b + 4, &endp, 10); + if (endp == b + 4) + return -EINVAL; + if (id >= MAX_ID) + return -EINVAL; + t = get_table((u_int32_t) id); + if (!t) + return -ENOENT; + err = unlink_table(t); + table_push(t); + t = NULL; + if (err) + return err; + } + else + return -EINVAL; + + return buflen; +} + + + + + +static int proc_control_open(struct inode *inode, struct file *file) { + struct proc_dir_entry *pde; + u_int32_t id; + struct mediaproxy_table *t; + unsigned long flags; + + pde = PDE(inode); + id = (u_int32_t) (unsigned long) pde->data; + t = get_table(id); + if (!t) + return -ENOENT; + + write_lock_irqsave(&table_lock, flags); + if (t->pid) { + write_unlock_irqrestore(&table_lock, flags); + table_push(t); + return -EBUSY; + } + t->pid = current->tgid; + write_unlock_irqrestore(&table_lock, flags); + + table_push(t); + return 0; +} + +static int proc_control_close(struct inode *inode, struct file *file) { + struct proc_dir_entry *pde; + u_int32_t id; + struct mediaproxy_table *t; + unsigned long flags; + + pde = PDE(inode); + id = (u_int32_t) (unsigned long) pde->data; + t = get_table(id); + if (!t) + return 0; + + write_lock_irqsave(&table_lock, flags); + t->pid = 0; + write_unlock_irqrestore(&table_lock, flags); + + table_push(t); + + return 0; +} + +static ssize_t proc_control_write(struct file *file, const char __user *buf, size_t buflen, loff_t *off) { + struct inode *inode; + struct proc_dir_entry *pde; + u_int32_t id; + struct mediaproxy_table *t; + struct mediaproxy_message msg; + int err; + + if (buflen != sizeof(msg)) + return -EINVAL; + + inode = file->f_path.dentry->d_inode; + pde = PDE(inode); + id = (u_int32_t) (unsigned long) pde->data; + t = get_table(id); + if (!t) + return -ENOENT; + + err = -EFAULT; + if (copy_from_user(&msg, buf, sizeof(msg))) + goto err; + + switch (msg.cmd) { + case MMG_NOOP: + DBG("noop.\n"); + break; + + case MMG_ADD: + err = table_new_target(t, &msg.target, 0); + if (err) + goto err; + break; + + case MMG_DEL: + err = table_del_target(t, msg.target.target_port); + if (err) + goto err; + break; + + case MMG_UPDATE: + err = table_new_target(t, &msg.target, 1); + if (err) + goto err; + break; + + default: + printk(KERN_WARNING "xt_MEDIAPROXY unimplemented op %u\n", msg.cmd); + err = -EINVAL; + goto err; + } + + table_push(t); + + return buflen; + +err: + table_push(t); + return err; +} + + + + + +static int send_proxy_packet4(struct sk_buff *skb, struct mp_address *src, struct mp_address *dst, unsigned char tos) { + struct iphdr *ih; + struct udphdr *uh; + unsigned int datalen; + + datalen = skb->len; + + uh = (void *) skb_push(skb, sizeof(*uh)); + skb_reset_transport_header(skb); + ih = (void *) skb_push(skb, sizeof(*ih)); + skb_reset_network_header(skb); + + DBG("datalen=%u network_header=%p transport_header=%p\n", datalen, skb_network_header(skb), skb_transport_header(skb)); + + datalen += sizeof(*uh); + *uh = (struct udphdr) { + .source = htons(src->port), + .dest = htons(dst->port), + .len = htons(datalen), + }; + *ih = (struct iphdr) { + .version = 4, + .ihl = 5, + .tos = tos, + .tot_len = htons(sizeof(*ih) + datalen), + .ttl = 64, + .protocol = IPPROTO_UDP, + .saddr = src->ipv4, + .daddr = dst->ipv4, + }; + + skb->csum_start = skb_transport_header(skb) - skb->head; + skb->csum_offset = offsetof(struct udphdr, check); + uh->check = csum_tcpudp_magic(src->ipv4, dst->ipv4, datalen, IPPROTO_UDP, csum_partial(uh, datalen, 0)); + if (uh->check == 0) + uh->check = CSUM_MANGLED_0; + + skb->protocol = htons(ETH_P_IP); + if (ip_route_me_harder(skb, RTN_UNSPEC)) + goto drop; + + skb->ip_summed = CHECKSUM_NONE; + + ip_local_out(skb); + + return 0; + +drop: + kfree_skb(skb); + return -1; +} + + + + + +static int send_proxy_packet6(struct sk_buff *skb, struct mp_address *src, struct mp_address *dst, unsigned char tos) { + struct ipv6hdr *ih; + struct udphdr *uh; + unsigned int datalen; + + datalen = skb->len; + + uh = (void *) skb_push(skb, sizeof(*uh)); + skb_reset_transport_header(skb); + ih = (void *) skb_push(skb, sizeof(*ih)); + skb_reset_network_header(skb); + + DBG("datalen=%u network_header=%p transport_header=%p\n", datalen, skb_network_header(skb), skb_transport_header(skb)); + + datalen += sizeof(*uh); + *uh = (struct udphdr) { + .source = htons(src->port), + .dest = htons(dst->port), + .len = htons(datalen), + }; + *ih = (struct ipv6hdr) { + .version = 6, + .priority = (tos & 0xf0) >> 4, + .flow_lbl = {(tos & 0xf) << 4, 0, 0}, + .payload_len = htons(datalen), + .nexthdr = IPPROTO_UDP, + .hop_limit = 64, + }; + memcpy(&ih->saddr, src->ipv6, sizeof(ih->saddr)); + memcpy(&ih->daddr, dst->ipv6, sizeof(ih->daddr)); + + skb->csum_start = skb_transport_header(skb) - skb->head; + skb->csum_offset = offsetof(struct udphdr, check); + uh->check = csum_ipv6_magic(&ih->saddr, &ih->daddr, datalen, IPPROTO_UDP, csum_partial(uh, datalen, 0)); + if (uh->check == 0) + uh->check = CSUM_MANGLED_0; + + skb->protocol = htons(ETH_P_IPV6); + if (ip6_route_me_harder(skb)) + goto drop; + + skb->ip_summed = CHECKSUM_NONE; + + ip6_local_out(skb); + + return 0; + +drop: + kfree_skb(skb); + return -1; +} + + + + +static int send_proxy_packet(struct sk_buff *skb, struct mp_address *src, struct mp_address *dst, unsigned char tos) { + if (src->family != dst->family) + goto drop; + + switch (src->family) { + case AF_INET: + return send_proxy_packet4(skb, src, dst, tos); + break; + + case AF_INET6: + return send_proxy_packet6(skb, src, dst, tos); + break; + + default: + goto drop; + } + +drop: + kfree_skb(skb); + return -1; +} + + + + + + +static unsigned int mediaproxy46(struct sk_buff *skb, struct mediaproxy_table *t) { + struct udphdr *uh; + struct mediaproxy_target *g; + struct sk_buff *skb2; + int err; + unsigned int datalen; + unsigned long flags; + + skb_reset_transport_header(skb); + uh = udp_hdr(skb); + skb_pull(skb, sizeof(*uh)); + + datalen = ntohs(uh->len); + if (datalen < sizeof(*uh)) + goto skip2; + datalen -= sizeof(*uh); + DBG("udp payload = %u\n", datalen); + skb_trim(skb, datalen); + + g = get_target(t, ntohs(uh->dest)); + if (!g) + goto skip2; + + DBG("target found, src "MIPF" -> dst "MIPF"\n", MIPP(g->target.src_addr), MIPP(g->target.dst_addr)); + + if (g->target.mirror_addr.family) { + DBG("sending mirror packet to dst "MIPF"\n", MIPP(g->target.mirror_addr)); + skb2 = skb_copy(skb, GFP_ATOMIC); + err = send_proxy_packet(skb2, &g->target.src_addr, &g->target.mirror_addr, g->target.tos); + if (err) { + spin_lock_irqsave(&g->lock, flags); + g->stats.errors++; + spin_unlock_irqrestore(&g->lock, flags); + } + } + + err = send_proxy_packet(skb, &g->target.src_addr, &g->target.dst_addr, g->target.tos); + + spin_lock_irqsave(&g->lock, flags); + if (err) + g->stats.errors++; + else { + g->stats.packets++; + g->stats.bytes += skb->len; + } + spin_unlock_irqrestore(&g->lock, flags); + + target_push(g); + table_push(t); + + return NF_DROP; + +skip2: + kfree_skb(skb); + table_push(t); + return XT_CONTINUE; +} + + + + + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) +static unsigned int mediaproxy4(struct sk_buff *oskb, const struct xt_target_param *par) { +#else +static unsigned int mediaproxy4(struct sk_buff *oskb, const struct xt_action_param *par) { +#endif + const struct xt_mediaproxy_info *pinfo = par->targinfo; + struct sk_buff *skb; + struct iphdr *ih; + struct mediaproxy_table *t; + int headroom; + + t = get_table(pinfo->id); + if (!t) + goto skip; + + headroom = MAX_HEADER - sizeof(*ih); + if (skb_headroom(oskb) >= headroom) + skb = skb_copy(oskb, GFP_ATOMIC); + else + skb = skb_copy_expand(oskb, headroom, 0, GFP_ATOMIC); + if (!skb) + goto skip3; + + skb_reset_network_header(skb); + ih = ip_hdr(skb); + skb_pull(skb, (ih->ihl << 2)); + if (ih->protocol != IPPROTO_UDP) + goto skip2; + + return mediaproxy46(skb, t); + +skip2: + kfree_skb(skb); +skip3: + table_push(t); +skip: + return XT_CONTINUE; +} + + + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) +static unsigned int mediaproxy6(struct sk_buff *oskb, const struct xt_target_param *par) { +#else +static unsigned int mediaproxy6(struct sk_buff *oskb, const struct xt_action_param *par) { +#endif + const struct xt_mediaproxy_info *pinfo = par->targinfo; + struct sk_buff *skb; + struct ipv6hdr *ih; + struct mediaproxy_table *t; + int headroom; + + t = get_table(pinfo->id); + if (!t) + goto skip; + + headroom = MAX_HEADER - sizeof(*ih); + if (skb_headroom(oskb) >= headroom) + skb = skb_copy(oskb, GFP_ATOMIC); + else + skb = skb_copy_expand(oskb, headroom, 0, GFP_ATOMIC); + if (!skb) + goto skip3; + + skb_reset_network_header(skb); + ih = ipv6_hdr(skb); + skb_pull(skb, sizeof(*ih)); + if (ih->nexthdr != IPPROTO_UDP) + goto skip2; + + return mediaproxy46(skb, t); + +skip2: + kfree_skb(skb); +skip3: + table_push(t); +skip: + return XT_CONTINUE; +} + + + + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) +#define CHECK_ERR false +#define CHECK_SCC true +static bool check(const struct xt_tgchk_param *par) { +#else +#define CHECK_ERR -EINVAL +#define CHECK_SCC 0 +static int check(const struct xt_tgchk_param *par) { +#endif + const struct xt_mediaproxy_info *pinfo = par->targinfo; + + if (!my_proc_root) { + printk(KERN_WARNING "xt_MEDIAPROXY check() without proc_root\n"); + return CHECK_ERR; + } + if (pinfo->id >= MAX_ID) { + printk(KERN_WARNING "xt_MEDIAPROXY ID too high (%u >= %u)\n", pinfo->id, MAX_ID); + return CHECK_ERR; + } + + return CHECK_SCC; +} + + + + +static struct xt_target xt_mediaproxy_regs[] = { + { + .name = "MEDIAPROXY", + .family = NFPROTO_IPV4, + .target = mediaproxy4, + .targetsize = sizeof(struct xt_mediaproxy_info), + .table = "filter", + .hooks = (1 << NF_INET_LOCAL_IN), + .checkentry = check, + .me = THIS_MODULE, + }, + { + .name = "MEDIAPROXY", + .family = NFPROTO_IPV6, + .target = mediaproxy6, + .targetsize = sizeof(struct xt_mediaproxy_info), + .table = "filter", + .hooks = (1 << NF_INET_LOCAL_IN), + .checkentry = check, + .me = THIS_MODULE, + }, +}; + +static int __init init(void) { + int ret; + + printk(KERN_NOTICE "Registering xt_MEDIAPROXY module - version %s\n", MEDIAPROXY_VERSION); + + rwlock_init(&table_lock); + + ret = -ENOMEM; + my_proc_root = proc_mkdir("mediaproxy", NULL); + if (!my_proc_root) + goto fail; + /* my_proc_root->owner = THIS_MODULE; */ + + proc_control = create_proc_entry("control", S_IFREG | S_IWUSR | S_IWGRP, my_proc_root); + if (!proc_control) + goto fail; + /* proc_control->owner = THIS_MODULE; */ + proc_control->proc_fops = &proc_main_control_ops; + + proc_list = create_proc_entry("list", S_IFREG | S_IRUGO, my_proc_root); + if (!proc_list) + goto fail; + /* proc_list->owner = THIS_MODULE; */ + proc_list->proc_fops = &proc_main_list_ops; + + ret = xt_register_targets(xt_mediaproxy_regs, ARRAY_SIZE(xt_mediaproxy_regs)); + if (ret) + goto fail; + + return 0; + +fail: + clear_proc(&proc_control); + clear_proc(&proc_list); + clear_proc(&my_proc_root); + + return ret; +} + +static void __exit fini(void) { + printk(KERN_NOTICE "Unregistering xt_MEDIAPROXY module\n"); + xt_unregister_targets(xt_mediaproxy_regs, ARRAY_SIZE(xt_mediaproxy_regs)); + + clear_proc(&proc_control); + clear_proc(&proc_list); + clear_proc(&my_proc_root); +} + +module_init(init); +module_exit(fini); diff --git a/kernel-module/xt_MEDIAPROXY.h b/kernel-module/xt_MEDIAPROXY.h new file mode 100644 index 000000000..57ab027e7 --- /dev/null +++ b/kernel-module/xt_MEDIAPROXY.h @@ -0,0 +1,85 @@ +#ifndef IPT_RTPPROXY_H +#define IPT_RTPPROXY_H + +struct xt_mediaproxy_info { + u_int32_t id; +}; + +struct mediaproxy_stats { + u_int64_t packets; + u_int64_t bytes; + u_int64_t errors; +}; + +struct mp_address { + int family; + union { + unsigned char ipv6[16]; + u_int32_t ipv4; + + unsigned char u8[16]; + u_int16_t u16[8]; + u_int32_t u32[4]; + }; + u_int16_t port; +}; + +struct mediaproxy_target_info { + u_int16_t target_port; + + struct mp_address src_addr; + struct mp_address dst_addr; + + struct mp_address mirror_addr; + + unsigned char tos; +}; + +struct mediaproxy_message { + enum { + MMG_NOOP = 1, + MMG_ADD, + MMG_DEL, + MMG_UPDATE, + } cmd; + + struct mediaproxy_target_info target; +}; + +struct mediaproxy_list_entry { + struct mediaproxy_target_info target; + struct mediaproxy_stats stats; +}; + +#ifdef __KERNEL__ + +struct mediaproxy_target { + atomic_t refcnt; + u_int32_t table; + struct mediaproxy_target_info target; + + spinlock_t lock; + struct mediaproxy_stats stats; +}; + +struct mediaproxy_table { + atomic_t refcnt; + rwlock_t target_lock; + pid_t pid; + + u_int32_t id; + struct proc_dir_entry *proc; + struct proc_dir_entry *status; + struct proc_dir_entry *control; + struct proc_dir_entry *list; + struct proc_dir_entry *blist; + + struct mediaproxy_target **target[256]; + + unsigned int buckets; + unsigned int targets; +}; + +#endif + +#endif diff --git a/tests/3-way-connect-simulator b/tests/3-way-connect-simulator new file mode 100755 index 000000000..453de2e9b --- /dev/null +++ b/tests/3-way-connect-simulator @@ -0,0 +1,246 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use Socket; + +$| = 1; + + + + + + +my $local_ip = '192.168.1.90'; + + + + + + +sub brk { + sleep(1); +} + +sub mp_msg { + my ($cmd) = @_; + + my $fd; + socket($fd, AF_INET, SOCK_STREAM, 0) or die; + connect($fd, sockaddr_in(25060, inet_aton('127.0.0.1'))) or die; + my $old = select($fd); + $| = 1; + print("$cmd\n"); + my $ret = <$fd>; + select($old); + close($fd); + chomp($ret); + return $ret; +} + +sub udp_sock { + while (1) { + my $fd; + socket($fd, AF_INET, SOCK_DGRAM, 0) or die; + bind($fd, sockaddr_in(0, inet_aton('0.0.0.0'))) or die; + my $sa = getsockname($fd); + my ($port, $addr) = sockaddr_in($sa); + (($port % 2) == 0) or next; + return ($fd, $port); + } +} + +sub send_rcv { + my ($sendfd, $sendtoip, $sendtoport, $recvfd) = @_; + print("sending to $sendtoip:$sendtoport... "); + my $pkt = join('',map(rand,1..10)); + send($sendfd, $pkt, 0, sockaddr_in($sendtoport, inet_aton($sendtoip))) or die; + my $inc; + { + local $SIG{ALRM} = sub { + print("timeout!\n"); + }; + alarm(1); + recv($recvfd, $inc, length($pkt), 0); + alarm(0); + } + $inc eq $pkt or print("NOT received packed ok\n"), return; + print("received packet ok\n"); +} + +sub send_rcv4 { + for (1 .. 4) { + send_rcv(@_[0,1,2,3]); + sleep(1); + send_rcv(@_[3,4,5,0]); + sleep(1); + } +} + +sub sim_req_lk { + my ($method, $callid, $ip, $port, $fromtag, $totag) = @_; + + $totag or $totag = ''; + + my $ret = mp_msg("$method $callid $ip:$port:audio 192.168.101.11 80.110.1.48 remote 212.41.253.181 remote CS2000_NGSS/9.0 info=domain:voip.sipwise.local,from:431960681661\@80.110.1.48:5060,totag:$totag,to:43720890289\@77.244.249.84:5060,fromtag:$fromtag"); + + return split(/ /, $ret); +} +sub sim_rq { + return sim_req_lk("request", @_); +} +sub sim_lk { + return sim_req_lk("lookup", @_); +} + + + + + + + +my $callid1 = join('',map(rand,1..2)); +my $fromtag1 = join('',map(rand,1..4)); +my $totag1 = join('',map(rand,1..4)); +my $callid2 = join('',map(rand,1..2)); +my $fromtag2 = join('',map(rand,1..4)); +my $totag2 = join('',map(rand,1..4)); + +my ($client1, $lp1) = udp_sock(); +my ($client2, $lp2) = udp_sock(); +my ($client3, $lp3) = udp_sock(); +my ($client4, $lp4) = udp_sock(); + +print("call-ids: $callid1 & $callid2\n"); +print("opened ports $lp1 [A->B], $lp2 [B->A], $lp3 [B->C], $lp4 [C->B] as RTP clients\n"); +brk(); + +print("CALL 1: A calling B\n"); +print("A tells B: send RTP to $lp1\n"); +brk(); + +my ($mpip1, $mpport1) = sim_rq($callid1, $local_ip, $lp1, $fromtag1); +print("mediaproxy: tell B to send to $mpport1 instead of $lp1\n"); +brk(); + +print("B tells A: send RTP to $lp2\n"); +brk(); + +my ($mpip2, $mpport2) = sim_lk($callid1, $local_ip, $lp2, $fromtag1, $totag1); +print("mediaproxy: tell A to send to $mpport2 instead of $lp2\n"); +brk(); + +send_rcv4($client2, $mpip1, $mpport1, $client1, $mpip2, $mpport2); + +brk(); + + + + + + + +print("B puts A on hold\n"); +print("B tells A: send RTP to 0.0.0.0\n"); + +brk(); + +my ($mpip3, $mpport3) = sim_rq($callid1, '0.0.0.0', $lp2, $totag1, $fromtag1); +print("mediaproxy: tell A to send to $mpip3:$mpport3 instead of 0.0.0.0:$lp2\n"); +brk(); + +print("A tells B: send RTP to 0.0.0.0\n"); +brk(); + +my ($mpip4, $mpport4) = sim_lk($callid1, '0.0.0.0', $lp1, $totag1, $fromtag1); +print("mediaproxy: tell B to send to $mpip4:$mpport4 instead of 0.0.0.0:$lp1\n"); +brk(); + + + + + + + +print("CALL 2: B calling C\n"); +print("B tells C: send RTP to $lp3\n"); + +brk(); + +my ($mpip5, $mpport5) = sim_rq($callid2, $local_ip, $lp3, $fromtag2); +print("mediaproxy: tell C to send to $mpport5 instead of $lp3\n"); +brk(); + +print("C tells B: send RTP to $lp4\n"); +brk(); + +my ($mpip6, $mpport6) = sim_lk($callid2, $local_ip, $lp4, $fromtag2, $totag2); +print("mediaproxy: tell B to send to $mpport6 instead of $lp4\n"); +brk(); + +send_rcv4($client4, $mpip5, $mpport5, $client3, $mpip6, $mpport6); + +brk(); + + + + + + + +print("B un-holds A\n"); +print("B tells A: send RTP to $lp2\n"); + +brk(); + +my ($mpip7, $mpport7) = sim_rq($callid1, $local_ip, $lp2, $totag1, $fromtag1); +print("mediaproxy: tell A to send to $mpport7 instead of $lp2\n"); +brk(); + +print("A tells B: send RTP to $lp1\n"); +brk(); + +my ($mpip8, $mpport8) = sim_lk($callid1, $local_ip, $lp1, $totag1, $fromtag1); +print("mediaproxy: tell B to send to $mpport8 instead of $lp1\n"); +brk(); + +send_rcv4($client2, $mpip1, $mpport1, $client1, $mpip2, $mpport2); + +brk(); + + + + + +print("CONNECT: B connects A to C\n"); +print("B tells C [call 2]: send RTP to $mpip8:$mpport8\n"); + +brk(); + +my ($mpip9, $mpport9) = sim_rq($callid2, $mpip8, $mpport8, $fromtag2, $totag2); +print("mediaproxy: tell C to send to $mpport9 instead of $mpip8:$mpport8\n"); +brk(); + +print("C tells B: send RTP to $lp4\n"); +my ($mpip10, $mpport10) = sim_lk($callid2, $local_ip, $lp4, $fromtag2, $totag2); +print("mediaproxy: tell B to send to $mpport10 instead of $lp4\n"); +brk(); + + +print("B tells A [call 1]: send RTP to $mpip10:$mpport10\n"); + +my ($mpip11, $mpport11) = sim_rq($callid1, $mpip10, $mpport10, $totag1, $fromtag1); +print("mediaproxy: tell A to send to $mpport11 instead of $mpip10:$mpport10\n"); +brk(); + +if (1) { + ###### error trigger + send_rcv($client1, $mpip11, $mpport11, $client4); +} + +print("A tells B: send RTP to $lp1\n"); +my ($mpip12, $mpport12) = sim_lk($callid1, $local_ip, $lp1, $totag1, $fromtag1); +print("mediaproxy: tell B to send to $mpport12 instead of $lp1\n"); +brk(); + +send_rcv4($client4, $mpip9, $mpport9, $client1, $mpip11, $mpport11); diff --git a/tests/blist.pl b/tests/blist.pl new file mode 100755 index 000000000..6b821f077 --- /dev/null +++ b/tests/blist.pl @@ -0,0 +1,32 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Socket; +use Socket6; + +my $t = $ARGV[0] || "0"; + +my $format = 'SS ia16SS ia16SS ia16SS CCCC LLLLLL'; +my $len = length(pack($format, (0) x 100)); + +open(X, "<", "/proc/mediaproxy/$t/blist") or die; +my $buf; +while (sysread(X, $buf, $len)) { + my @b = unpack($format, $buf); + for (2,6,10) { + if ($b[$_] == AF_INET) { + $b[$_ + 1] = inet_ntoa($b[$_ + 1]); + } + elsif ($b[$_] == AF_INET6) { + $b[$_ + 1] = inet_ntop(AF_INET6, $b[$_ + 1]); + } + elsif ($b[$_] == 0) { + $b[$_ + 1] = '---'; + } + } + for (18, 20, 22) { + $b[$_] += $b[$_ + 1] * 2**32; + } + printf("%5u %15s:%-5u -> %15s:%-5u (-> %15s:%-5u) [%u] [%llu %llu %llu]\n", @b[0,3,4,7,8,11,12,14,18,20,22]); +} diff --git a/tests/kernel-module-test.pl b/tests/kernel-module-test.pl new file mode 100755 index 000000000..1ba61a960 --- /dev/null +++ b/tests/kernel-module-test.pl @@ -0,0 +1,92 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Socket; +use Socket6; + +my %cmds = (noop => 1, add => 2, delete => 3, update => 4); +$| = 1; + +open(F, "> /proc/mediaproxy/0/control") or die; +{ + my $x = select(F); + $| = 1; + select($x); +} + +sub mp_address { + my ($fam, $addr, $port) = @_; + + if ($fam eq 'inet') { + return pack('i a4 a12 S S', 2, inet_aton($addr), '', $port, 0); + } + if ($fam eq 'inet6') { + return pack('i a16 S S', 10, inet_pton(AF_INET6, $addr), $port, 0); + } + if ($fam eq '') { + return pack('i a16 S S', 0, '', 0, 0); + } + + die; +} +sub mediaproxy_message { + my ($cmd, $target_port, + $src_addr_family, $src_addr_addr, $src_addr_port, + $dst_addr_family, $dst_addr_addr, $dst_addr_port, + $mirror_addr_family, $mirror_addr_addr, $mirror_addr_port, + $tos) = @_; + + my $ret = ''; + + $ret .= pack('I SS', $cmds{$cmd}, $target_port, 0); + $ret .= mp_address($src_addr_family, $src_addr_addr, $src_addr_port); + $ret .= mp_address($dst_addr_family, $dst_addr_addr, $dst_addr_port); + $ret .= mp_address($mirror_addr_family, $mirror_addr_addr, $mirror_addr_port); + $ret .= pack('C CS', $tos, 0, 0); +} + +my $sleep = 5; +#my @src = qw(inet 10.15.20.61); +#my @dst = qw(inet 10.15.20.58); +my @src = qw(inet6 2a00:4600:1:0:a00:27ff:feb0:f7fe); +my @dst = qw(inet6 2a00:4600:1:0:6884:adff:fe98:6ac5); +my @nul = ('', '', ''); + +print("add 9876 -> 1234/6543\n"); +syswrite(F, mediaproxy_message('add', 9876, @src, 1234, @dst, 6543, @nul, 184)); +sleep($sleep); + +print("add fail\n"); +syswrite(F, mediaproxy_message('add', 9876, @src, 1234, @dst, 6543, @dst, 6789, 184)); +sleep($sleep); + +print("update 9876 -> 1234/6543 & 6789\n"); +syswrite(F, mediaproxy_message('update', 9876, @src, 1234, @dst, 6543, @dst, 6789, 184)); +sleep($sleep); + +print("update 9876 -> 2345/7890 & 4321\n"); +syswrite(F, mediaproxy_message('update', 9876, @src, 2345, @dst, 7890, @dst, 4321, 184)); +sleep($sleep); + +print("add fail\n"); +syswrite(F, mediaproxy_message('add', 9876, @src, 1234, @dst, 6543, @dst, 6789, 184)); +sleep($sleep); + +print("update 9876 -> 1234/6543\n"); +syswrite(F, mediaproxy_message('update', 9876, @src, 1234, @dst, 6543, @nul, 184)); +sleep($sleep); + +print("delete\n"); +syswrite(F, mediaproxy_message('delete', 9876, @nul, @nul, @nul, 0)); +sleep($sleep); + +print("delete fail\n"); +syswrite(F, mediaproxy_message('delete', 9876, @nul, @nul, @nul, 0)); +sleep($sleep); + +print("update fail\n"); +syswrite(F, mediaproxy_message('update', 9876, @src, 1234, @dst, 6543, @nul, 184)); +sleep($sleep); + +close(F); diff --git a/tests/reinvite-simulator b/tests/reinvite-simulator new file mode 100755 index 000000000..733de1e52 --- /dev/null +++ b/tests/reinvite-simulator @@ -0,0 +1,370 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use Socket; + +$| = 1; + + + + + + +my $local_ip = '192.168.1.90'; + + + + + + +sub brk { + sleep(1); +} + +sub mp_msg { + my ($cmd) = @_; + + my $fd; + socket($fd, AF_INET, SOCK_STREAM, 0) or die; + connect($fd, sockaddr_in(25060, inet_aton('127.0.0.1'))) or die; + my $old = select($fd); + $| = 1; + print("$cmd\n"); + my $ret = <$fd>; + select($old); + close($fd); + chomp($ret); + return $ret; +} + +sub udp_sock { + while (1) { + my $fd; + socket($fd, AF_INET, SOCK_DGRAM, 0) or die; + bind($fd, sockaddr_in(0, inet_aton('0.0.0.0'))) or die; + my $sa = getsockname($fd); + my ($port, $addr) = sockaddr_in($sa); + (($port % 2) == 0) or next; + return ($fd, $port); + } +} + +sub send_rcv { + my ($sendfd, $sendtoip, $sendtoport, $recvfd) = @_; + my $laddr = getsockname($sendfd); + my ($lport, $lip) = sockaddr_in($laddr); + print("local port $lport sending to $sendtoip:$sendtoport... "); + my $pkt = join('',map(rand,1..10)); + send($sendfd, $pkt, 0, sockaddr_in($sendtoport, inet_aton($sendtoip))) or die; + my ($inc, $addr); + { + local $SIG{ALRM} = sub { + print("timeout!\n"); + return; + }; + alarm(1); + $addr = recv($recvfd, $inc, length($pkt), 0); + alarm(0); + } + $inc eq $pkt or print("did NOT receive packet\n"), return; + my ($port, $ip) = sockaddr_in($addr); + $laddr = getsockname($recvfd); + ($lport, $lip) = sockaddr_in($laddr); + print("received packet ok on port $lport, from port $port\n"); +} + +sub send_rcv_brk { + send_rcv(@_); + brk(); +} + +sub send_rcv4 { + for (1 .. 4) { + send_rcv(@_[0,1,2,3]); + sleep(1); + send_rcv(@_[3,4,5,0]); + sleep(1); + } +} + +sub sim_req_lk { + my ($method, $callid, $ip, $port, $fromtag, $totag) = @_; + + $totag or $totag = ''; + + my $ret = mp_msg("$method $callid $ip:$port:audio 192.168.101.11 80.110.1.48 remote 212.41.253.181 remote CS2000_NGSS/9.0 info=domain:voip.sipwise.local,from:431960681661\@80.110.1.48:5060,totag:$totag,to:43720890289\@77.244.249.84:5060,fromtag:$fromtag"); + + return split(/ /, $ret); +} +sub sim_rq { + return sim_req_lk("request", @_); +} +sub sim_lk { + return sim_req_lk("lookup", @_); +} + + + + + + + +my $callid = join('',map(rand,1..2)); +my $fromtag = join('',map(rand,1..4)); +my $totag = join('',map(rand,1..4)); + +my ($client1, $lp1) = udp_sock(); +my ($client2, $lp2) = udp_sock(); + +print("call-id: $callid\n"); +print("opened ports $lp1 [A->B], $lp2 [B->A] as RTP clients\n"); +brk(); + +{ + print("A tells B: send RTP to $lp1\n"); + brk(); + + my ($mpip1, $mpport1) = sim_rq($callid, $local_ip, $lp1, $fromtag); + print("mediaproxy: tell B to send to $mpport1 instead of $lp1\n"); + brk(); + + print("B tells A: send RTP to $lp2\n"); + brk(); + + my ($mpip2, $mpport2) = sim_lk($callid, $local_ip, $lp2, $fromtag, $totag); + print("mediaproxy: tell A to send to $mpport2 instead of $lp2\n"); + brk(); + + send_rcv4($client2, $mpip1, $mpport1, $client1, $mpip2, $mpport2); + + brk(); +} + + + + + + + + + + +for my $case (0 .. 3) { + + + + + my $sub; + + if ($case == 0) { + $sub = sub { ; }; + } + elsif ($case == 1) { + $sub = sub { + print("\tchanging ports on client 1\n"); + ($client1, $lp1) = udp_sock(); + }; + } + elsif ($case == 2) { + $sub = sub { + print("\tchanging ports on client 2\n"); + ($client2, $lp2) = udp_sock(); + }; + } + elsif ($case == 3) { + $sub = sub { + print("\tchanging ports on client 1 and 2\n"); + ($client1, $lp1) = udp_sock(); + ($client2, $lp2) = udp_sock(); + }; + } + + + + + my @forward = ('A', 'B', \$lp1, \$lp2, \$client1, \$client2, $fromtag, $totag); + my @backward = ('B', 'A', \$lp2, \$lp1, \$client2, \$client1, $totag, $fromtag); + my ($src, $dst, $p1, $p2, $c1, $c2, $ft, $tt); + + + + + + + for my $tuple (\@forward, \@backward) { + ($src, $dst, $p1, $p2, $c1, $c2, $ft, $tt) = @$tuple; + + $sub->(); + print("\n\n\n"); + print("re-invite coming from $src with no intermediate traffic\n"); + + print("$src tells $dst: send RTP to $$p1\n"); + brk(); + + my ($mpip1, $mpport1) = sim_rq($callid, $local_ip, $$p1, $ft); + print("mediaproxy: tell $dst to send to $mpport1 instead of $$p1\n"); + brk(); + + print("$dst tells $src: send RTP to $$p2\n"); + brk(); + + my ($mpip2, $mpport2) = sim_lk($callid, $local_ip, $$p2, $ft, $tt); + print("mediaproxy: tell $src to send to $mpport2 instead of $$p2\n"); + brk(); + + send_rcv4($$c2, $mpip1, $mpport1, $$c1, $mpip2, $mpport2); + + brk(); + } + + + + + + + + + + + for my $tuple (\@forward, \@backward) { + ($src, $dst, $p1, $p2, $c1, $c2, $ft, $tt) = @$tuple; + + $sub->(); + print("\n\n\n"); + print("re-invite coming from $src with intermediate traffic from $dst to the new port only\n"); + + print("$src tells $dst: send RTP to $$p1\n"); + brk(); + + my ($mpip1, $mpport1) = sim_rq($callid, $local_ip, $$p1, $ft); + print("mediaproxy: tell $dst to send to $mpport1 instead of $$p1\n"); + brk(); + + for (1 .. 4) { + send_rcv_brk($$c2, $mpip1, $mpport1, $$c1); + } + + print("$dst tells $src: send RTP to $$p2\n"); + brk(); + + my ($mpip2, $mpport2) = sim_lk($callid, $local_ip, $$p2, $ft, $tt); + print("mediaproxy: tell $src to send to $mpport2 instead of $$p2\n"); + brk(); + + send_rcv4($$c2, $mpip1, $mpport1, $$c1, $mpip2, $mpport2); + + brk(); + + + + + + + $sub->(); + print("\n\n\n"); + print("re-invite coming from $src with intermediate traffic from both sides to both old and new ports\n"); + + print("$src tells $dst: send RTP to $$p1\n"); + brk(); + + my ($mpip3, $mpport3) = sim_rq($callid, $local_ip, $$p1, $ft); + print("mediaproxy: tell $dst to send to $mpport3 instead of $$p1\n"); + brk(); + + send_rcv4($$c2, $mpip1, $mpport1, $$c1, $mpip2, $mpport2); + print("switching to new port...\n"); + send_rcv4($$c2, $mpip3, $mpport3, $$c1, $mpip2, $mpport2); + + print("$dst tells $src: send RTP to $$p2\n"); + brk(); + + my ($mpip4, $mpport4) = sim_lk($callid, $local_ip, $$p2, $ft, $tt); + print("mediaproxy: tell $src to send to $mpport4 instead of $$p2\n"); + brk(); + + send_rcv4($$c2, $mpip3, $mpport3, $$c1, $mpip2, $mpport2); + print("switching to new port...\n"); + send_rcv4($$c2, $mpip3, $mpport3, $$c1, $mpip4, $mpport4); + + brk(); + + + + + + + $sub->(); + print("\n\n\n"); + print("re-invite coming from $src with intermediate traffic from $dst only to both old and new ports\n"); + + print("$src tells $dst: send RTP to $$p1\n"); + brk(); + + my ($mpip5, $mpport5) = sim_rq($callid, $local_ip, $$p1, $ft); + print("mediaproxy: tell $dst to send to $mpport5 instead of $$p1\n"); + brk(); + + for (1 .. 4) { + send_rcv_brk($$c2, $mpip3, $mpport3, $$c1); + } + print("switching to new port...\n"); + for (1 .. 4) { + send_rcv_brk($$c2, $mpip5, $mpport5, $$c1); + } + + print("$dst tells $src: send RTP to $$p2\n"); + brk(); + + my ($mpip6, $mpport6) = sim_lk($callid, $local_ip, $$p2, $ft, $tt); + print("mediaproxy: tell $src to send to $mpport6 instead of $$p2\n"); + brk(); + + for (1 .. 4) { + send_rcv_brk($$c2, $mpip5, $mpport5, $$c1); + } + + send_rcv4($$c2, $mpip5, $mpport5, $$c1, $mpip6, $mpport6); + + brk(); + + + + + + + $sub->(); + print("\n\n\n"); + print("re-invite coming from $src with intermediate traffic from $src only to both old and new ports\n"); + + print("$src tells $dst: send RTP to $$p1\n"); + brk(); + + my ($mpip7, $mpport7) = sim_rq($callid, $local_ip, $$p1, $ft); + print("mediaproxy: tell $dst to send to $mpport7 instead of $$p1\n"); + brk(); + + for (1 .. 4) { + send_rcv_brk($$c1, $mpip6, $mpport6, $$c2); + } + + print("$dst tells $src: send RTP to $$p2\n"); + brk(); + + my ($mpip8, $mpport8) = sim_lk($callid, $local_ip, $$p2, $ft, $tt); + print("mediaproxy: tell $src to send to $mpport8 instead of $$p2\n"); + brk(); + + for (1 .. 4) { + send_rcv_brk($$c1, $mpip6, $mpport6, $$c2); + } + print("switching to new port...\n"); + for (1 .. 4) { + send_rcv_brk($$c1, $mpip8, $mpport8, $$c2); + } + + send_rcv4($$c2, $mpip7, $mpport7, $$c1, $mpip8, $mpport8); + + brk(); + } +} diff --git a/tests/simulator.sh b/tests/simulator.sh new file mode 100755 index 000000000..183f9bba6 --- /dev/null +++ b/tests/simulator.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# # G_SLICE=always-malloc valgrind --leak-check=full --track-origins=yes --show-possibly-lost=yes ./mediaproxy-ng -t 0 -i $IP -l 25060 -f + +pipe_o() { + nc localhost 25060 +} +pipe() { + pipe_o > /dev/null +} +ip() { + echo $(($RANDOM % 254 + 1)).$(($RANDOM % 254 + 1)).$(($RANDOM % 254 + 1)).$(($RANDOM % 254 + 1)) +} +port() { + echo $(($RANDOM % 64000 + 1024)) +} + +ids="" +ports="" +for i in $(seq 1 1000); do + callid=`uuid` + test -z "$callid" && exit 1 + src=`ip`:`port` + dst=`ip`:`port` + gw=`ip` + fromtag=`uuid` + totag=`uuid` + + src_rel=`echo "request $callid $src:audio $gw voip.inode.at local unknown unknown unknown-agent info=domain:voip.sipwise.local,from:number@voip.inode.at,totag:,to:othernumber@voip.inode.at,fromtag:$fromtag" | pipe_o` + dst_rel=`echo "lookup $callid $dst:audio $gw voip.inode.at local unknown unknown unknown-agent info=domain:voip.sipwise.local,from:number@voip.inode.at,totag:$totag,to:othernumber@voip.inode.at,fromtag:$fromtag" | pipe_o` + echo "lookup $callid $dst:audio $gw voip.inode.at local unknown unknown unknown-agent info=domain:voip.sipwise.local,from:number@voip.inode.at,totag:$totag,to:othernumber@voip.inode.at,fromtag:$fromtag" | pipe + echo version | pipe + echo status | pipe + + src_path=${src_rel/ //} + dst_path=${dst_rel/ //} + ports="$ports $src_path $dst_path" + for port in $ports; do + echo foobar > /dev/udp/$port + done + + ids="$ids $callid" +done + +sleep 10 + +for id in $ids; do + echo "delete $id info=" | pipe +done diff --git a/utils/patch-kernel b/utils/patch-kernel new file mode 100755 index 000000000..5d9e79cfd --- /dev/null +++ b/utils/patch-kernel @@ -0,0 +1,52 @@ +#!/bin/bash + +if test "$1" != magic; then + echo + echo "Don't run this script manually, instead run:" + echo " make patch KERNEL=/path/to/kernel/sources" + echo + exit 1 +fi + +if test -z "$3"; then + echo + echo "Usage:" + echo " make patch KERNEL=/path/to/kernel/sources" + echo + exit 1 +fi + +KERN=$3 + +for x in . include/linux/netfilter/ net/netfilter/Kconfig net/netfilter/Makefile; do + if ! test -e "$KERN"/"$x"; then + echo "I don't recognize $KERN as a kernel source tree" + exit 1 + fi +done + +set -e + +cp -v xt_MEDIAPROXY.h "$KERN"/include/linux/netfilter/ +cp -v xt_MEDIAPROXY.c "$KERN"/net/netfilter/ + +if ! grep -q CONFIG_NETFILTER_XT_TARGET_MEDIAPROXY "$KERN"/net/netfilter/Makefile; then + ( + echo + echo "EXTRA_CFLAGS += -DMEDIAPROXY_VERSION=\"\\\"$4\\\"\"" + echo 'obj-$(CONFIG_NETFILTER_XT_TARGET_MEDIAPROXY) += xt_MEDIAPROXY.o' + ) >> "$KERN"/net/netfilter/Makefile +fi + +if ! grep -q Kconfig\\.mediaproxy-ng "$KERN"/net/netfilter/Kconfig; then + cat >> "$KERN"/net/netfilter/Kconfig.mediaproxy-ng << \__EOF +config NETFILTER_XT_TARGET_MEDIAPROXY + tristate "Sipwise NGCP MEDIAPROXY target support" + depends on IP_NF_FILTER + help + Sipwise NGCP mediaproxy-ng kernel support + + To compile it as a module, choose M here. If unsure, say N. +__EOF + echo 'source "net/netfilter/Kconfig.mediaproxy-ng"' >> "$KERN"/net/netfilter/Kconfig +fi