TT#5003 implement kernel-side call recording

Squashed commit of the following:

commit 1af3efd464d9c86eb428aa29ca084ead67ba4cdc
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Mon Nov 7 11:10:22 2016 -0500

    save and restore call recording status to/from redis

    closes #254

    Change-Id: If3cd34fcfd64fa8164521a86eb1d1aa0eb327f3b

commit 460053a2316ac77ebf2609af4e5bf73beac57643
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Fri Nov 4 14:09:08 2016 -0400

    restore libcurl build dependency

    Change-Id: Ia853f928caf9e443bb69c4015cbba805e6d24153

commit 5f5fd88fde67ffcd5a83f1a18e9134702f995686
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Fri Nov 4 11:22:27 2016 -0400

    ensure we are always decrypting SRTP when recording

    Change-Id: I2b75afefcadc55ebf1bf6a19a983134c87c41602

commit 69b4e9fa886c673bed6168c2092864fdff2619aa
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Fri Nov 4 09:56:47 2016 -0400

    remove redundant ng result=ok logic

    Change-Id: I84d7245f52dc12a4002b4dd2b736afea9ae733fa

commit 411213dd3d17c1e54eeb6813cb0473a706a10cee
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Nov 3 16:04:54 2016 -0400

    support the `start recording` command message

    Change-Id: I316e90fd3fe34f01b20826936ef620bcff785397

commit ae1910c68d90d0c1f913bd09f9ecdee5579f53f4
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Nov 3 15:01:57 2016 -0400

    move logic for `record call` flag to where it belongs

    Change-Id: I65a3b5d62f4360df4251faea1339b5a6355c0e6d

commit 217008d572d103ab4146cdcfb9c292610c869643
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Nov 3 12:49:48 2016 -0400

    support combining streams into one pcap in reference scripts

    Change-Id: Id82936ae7ef1b406acf3b4d3429ecc1cb6e2356e

commit 3fcafd92750d20e07b9c522cd15df0f1745bd412
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Nov 3 11:40:00 2016 -0400

    output additional details to metafiles

    Change-Id: I0539a0617aff51c9c499bbd9586d61c7e7ca7e7c

commit e81f413f3ba228f548931a234a1c18c13c62b135
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Nov 2 16:18:28 2016 -0400

    globalize kernel access variables

    Change-Id: Ie07e0ebb8705189c8b1e49f596080ff8bcaef64f

commit 5e74609c7c3e5237fbd9995b90b29b2c456d2c04
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Nov 2 12:20:55 2016 -0400

    duplicate error messages to stderr when in foreground

    Change-Id: Iaf9d8d1392946046846cb6f2c0fb928a96893911

commit 032077bed4a4b3610e88deab905f6a41616393e6
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Nov 2 11:38:22 2016 -0400

    eliminate dummy virtual function

    Change-Id: I7cc2596f31350bf0d39253b3bd7e9fe1cec2b92d

commit 9177effc4e56b0fc26f44da525fe88d8aa3a6f05
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Nov 2 11:30:58 2016 -0400

    clean up recording code related to ng interface

    Change-Id: I69c6d953f8467e5154f000979c94f2dc3a79918a

commit ac0ec6a5e7b4927af5294a6e86a129a8ffc90e7a
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Nov 2 10:57:15 2016 -0400

    output before/after sdp to metafiles

    Change-Id: I1d3177e40591d6b4c4789eb6e18132743a9eda45

commit 68b27fa964455eef4c4acaf9540d19013b3df3e7
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Nov 1 15:40:20 2016 -0400

    fix header length problems

    Change-Id: I896453f72df3ba146251f5d82ca1d02dd0ad08e9

commit ebbd942ad00b3a4eb74fbfcd915e82a263243d02
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Nov 1 14:14:08 2016 -0400

    support writing pcap files in reference intercept script

    Change-Id: I53502ba416426e9012a68c35cdf4b457d7c3eb69

commit fe82efa40a5b0fee4bed2601bab7c1afa09b0ce1
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Nov 1 13:35:47 2016 -0400

    ensure we never make any calls that might sleep when holding locks

    Change-Id: I1ce6aeced1f61715374b80f6fb1fbeafc987ae7f

commit be5316e804368fad28c2a0012ac2d60cb476b740
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Nov 1 13:08:19 2016 -0400

    fix math in auto_array_find_free_index

    Change-Id: I39c786b03dbafe59b88a1945ac27964c3852c9eb

commit 183d4939ca8e1fc0f39271713435b016c055bfff
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Nov 1 11:41:47 2016 -0400

    adding rudimentary lock debugging

    Change-Id: Iff541c58e5ea4c3fc2ec16e93396148f935bc4f3

commit 51a75c5022f96a23b8fff6738b5fa4ad7095a746
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Mon Oct 31 14:35:17 2016 -0400

    handle read errors in reference intercept script

    Change-Id: I047d763dbd498026a0d8db24c5532155c75fd6e6

commit 69a460de08c6dfe163f9d90dee478c72ea56a2fc
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Mon Oct 31 14:34:54 2016 -0400

    changed locking semantics to avoid proc_clear deadlock

    Change-Id: I70dbc07aa8af7b9860beca86b4b82d8180d8a0ae

commit 62eae1459a8dc116a0f965d014dd2962d9f2ece2
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Mon Oct 31 12:50:26 2016 -0400

    additional debugging

    Change-Id: I81c93c2dd9007ed6a0d6e0b147de0deb44b7b023

commit fd367947404c25f7122479dc805ca895346e3568
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Fri Oct 28 12:07:25 2016 -0400

    adding reference implementation using inotify

    Change-Id: Ibec73bdc4c7a576e4beaf5e749567dd2508be4df

commit f8fde6cc7308faf9aea5016d1b325fe54ace4f73
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Oct 27 15:55:10 2016 -0400

    add stream info to metadata files

    Change-Id: I200df14a3e35c2c0077866444b96692de4303761

commit 5a8b4dd156d014756681c644549f481af941b294
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Oct 27 14:47:49 2016 -0400

    pass intercepted packets to kernel and tie in kernel idx

    Change-Id: I33ff297c4a66276b05d0e5d537b0281f27116243

commit 0c0b97ea3940a34843375c58255d40cf4280bd5c
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Oct 27 12:29:02 2016 -0400

    abstractize fake packet header and support ipv6

    Change-Id: I6c1ad1cccad306f3d306fb3387efcb033a3574b2

commit e1b648b9baea609cbe9836c81a5a2cd41ef464cb
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Oct 27 09:32:40 2016 -0400

    support creating kernel intercept streams

    Change-Id: I1f9bdda61af52814f07f765a8b558a7491cfc0b9

commit b6737ec9635d2d7a8d0245bab7c51a077cd814d2
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Oct 26 14:46:13 2016 -0400

    get rid of call->record_call flag

    Change-Id: I04dcba49b07fc669b0bb4bcea8ccc2b52cb76e02

commit 6ed362c120995d1e3fdff71f7e440e73737e7fcb
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Oct 26 12:53:04 2016 -0400

    add recorded call to /proc

    Change-Id: I1fe64b9fb8fe3604bb7d432899c43e3e37ea6a4f

commit 62d2508ecf69fbdf9ba1faf3e2433fa6f717740a
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Oct 26 10:10:05 2016 -0400

    simplify some code

    Change-Id: I2a27400e91f58aa8ea20d3e610a7509d2e9a0dfc

commit 0c811a8e91ded41487bf045e6df55fb31cce7671
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Oct 25 16:12:59 2016 -0400

    create separate ../tmp recording dir and clean up duplicate code

    Change-Id: I94e0c19a1e8fed5a30212b79930987333f5e6786

commit 16592a92300b856084ebd5769ecae35788f0ad63
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Oct 25 11:26:54 2016 -0400

    remove explicit libcurl dependency

    finally properly fixes #251

    Change-Id: I3feafe2d0086f6dd789175e6ec0079c54edd487a

commit c78ac5bbb7e65609175753933f7e75d54ea21c30
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Oct 25 10:48:50 2016 -0400

    abstractize recording cleanup routine

    Change-Id: Ib9fc46542f273bab53f611a1456f02d67edfc966

commit d8358c9a2c6cbd66e17d4bb08dba96a3b2e2d41a
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Oct 25 10:07:44 2016 -0400

    use shorthand for function pointers

    Change-Id: Idf43949e20281a10317e22a5b68a6d133e398bd4

commit 9417437c1092770e36bde22b508f9d71078d4a1b
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Mon Oct 24 15:31:56 2016 -0400

    abstractize individual recording functions

    Change-Id: I0974696b6bb361fce39b24d6be91b5c052ee2b14

commit eb631cd876a0f941ed4fb6d47412a6892e019f1b
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Mon Oct 24 11:54:42 2016 -0400

    abstractize call recording mechanism

    Change-Id: If8672051227944544d9cf916d359c5db67235e3e

commit 70797ceb8f36c45593688652df98f19a5c1dd9f4
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Jul 14 11:25:05 2016 -0400

    combine two mallocs into one for user-generated packets

    Change-Id: I585d129f10d379a5cb853382773f91a7cec9a98d

commit f62f71cee404893b3f5076489d6a3eb1ad6f69c0
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Jul 13 14:22:18 2016 -0400

    check for and avoid /proc file name collisions

    Change-Id: Ie9eb9ceef8f32de8aba816f0121e768c57fa7402

commit ec6b3d22fcce8e7e2edd35704a559fe392406561
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Jul 13 11:02:04 2016 -0400

    implement free list

    Change-Id: I3e7dc2325c937923d19ce6000f2cf1c011e51037

commit bd75a1cf254c484878d07ecceca7220cdbf1ddb3
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Jul 13 09:30:36 2016 -0400

    make number of packets retained per stream configurable

    Change-Id: If1c87f80dd7367cbc274d13c15f94836ef9c8cb1

commit de259c3d646526bd200f619371668798ddae86b2
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Jul 12 15:15:01 2016 -0400

    facilitate passing packets from kernel module out to userspace

    Change-Id: I2317a007084a1718e185ba04632c53a9ebe5f29d

commit 3aa88716fb86c62f96b6dfc7d0d9c9755fa1f389
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Mon Jul 11 10:45:10 2016 -0400

    restructure calls and streams to global arrays

    Change-Id: Icfca615b21f2e51b1abda4422c4eeb8f4ac70a9b

commit 6cf9980f3e61f1f1d44b5ce883298ca2726af1b2
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Fri Jul 8 11:53:32 2016 -0400

    implement poll/select mechanism

    Change-Id: Ic10c017250f0991f691a887b078e80f694bba853

commit d95829e07b2bf4d3352806c70b3393ae3a8609fb
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Fri Jul 8 10:44:38 2016 -0400

    discard new/old packets as required

    Change-Id: If73ce77dcbc24addb6ce0931b90de0f5efae9f51

commit be1d769e4454f1d98f0b75cc8301c128a40e980c
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Fri Jul 8 10:25:38 2016 -0400

    implement EOF for stream readers

    Change-Id: I858dc1fdd7df3b65283e1d96457d87e7452840f5

commit ed2d98d55a87ce428cdc66d9336035f3dc0cb5ae
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Jul 7 15:02:58 2016 -0400

    blocking and nonblocking reads - incomplete

    Change-Id: I7cbfb09507ad8726773d6a28ddb98d5981decd04

commit 246709c7f4741e669c4a78b48c382497bf57c6be
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Jul 7 13:55:41 2016 -0400

    rudimentary packet reading/dequeuing

    Change-Id: I1a924e5cb2ef4e4f16aeff1f1dd90d0746f91da5

commit 427749394995d2e6fb8ad7ddc23c876b1c8e11f1
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Jul 7 12:40:31 2016 -0400

    use kzalloc where appropriate

    Change-Id: Icd6a109a69ab4f6dc9f7d35fd9e8fc9127f8e7e0

commit 5b07819217dfb47201a373e941aab185789484a9
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Jul 7 09:07:54 2016 -0400

    more module referencing

    Change-Id: I2e34fe74f2edef9170a4558f6a24394240966d79

commit 1d8268f636c26ec21c3cfa2308bad91262e0768a
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Jul 6 14:56:42 2016 -0400

    store delivered packets in queue

    Change-Id: Id349b75e06f9dd77c884196b7726027ac5cab7ae

commit 23c6a53f94c46ead7093b5402486799fc631e432
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Jul 6 14:21:27 2016 -0400

    implement call for packet delivery

    Change-Id: Ibeb815bf2fedfdd644d324c65b58a24871d47d4a

commit c8fd855f325128b78f4e276bfb26b479ef189b11
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Jul 6 10:28:18 2016 -0400

    automatic cleanup of objects upon free/delete

    Change-Id: If244905e2d074f491229316f3305c9b0b1451792

commit 282ef603a83637c9a8e33ed31d09268fa45a6301
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Jul 6 09:47:29 2016 -0400

    unify read/write functions

    Change-Id: I78b0dd05cd730e16655034994c74cbe23be23fce

commit e74c62cc6f0dce85fea6534d8d72e2f51e0947d6
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Jul 5 15:36:00 2016 -0400

    rename _push to _put and _hold to _get

    Change-Id: I9b0ff5038b541bd3cfb961657c15a26f26ccdfb2

commit d71ce17529d1938ce2439b122fb6017e29dd079b
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Jul 5 15:33:07 2016 -0400

    support creation and deletion of streams

    Change-Id: I7df05d232b5971c54ca50adce8144b5f1646fba0

commit 8be4e2c7c49dd7b893335f14ca98776a1f3fb12f
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Tue Jul 5 11:54:39 2016 -0400

    create functions for redundant code

    Change-Id: Id3772f12294ee9891d22d833274e5935814cae0b

commit 47ce4ca8f5d694e50c98f73147ed2eb81f14ea8a
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Thu Jun 30 12:12:19 2016 -0400

    support creation and deletion of calls objects

    Change-Id: Ie5f9aa978bac21fc30909f14d6a438494848dfd5

commit 8dab54209d3e52d41f78d8f54956a4ed3dc15da3
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Jun 29 14:20:23 2016 -0400

    create /proc/.../calls directory

    Change-Id: I682a4bf23edbb72772d64963e3ba2cab2a521ff4

commit 1401ae8db5f9e2a973c670f3bf72e9019c451276
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Jun 29 14:02:11 2016 -0400

    rename kernel message enum

    Change-Id: I45d7aeae43df1fe6ecd6b6965dbd6ba7e7b715d8

commit ce44ff0dbe67a6687d5fb1bea9d31e21c9464907
Author: Richard Fuchs <rfuchs@sipwise.com>
Date:   Wed Jun 29 14:00:32 2016 -0400

    convert kernel message data to union

    Change-Id: I7cfd9fe81623efae0a828ba457aa0a4b1380ff03

Change-Id: I4bb0b7f6d29c1c7a2144e4735ed59f3dc0e37094
changes/33/9333/5
Richard Fuchs 9 years ago
parent ce461490c3
commit 984585a32c

@ -42,13 +42,13 @@ the following additional features are available:
RTP/SAVP, RTP/SAVPF)
- RTP/RTCP multiplexing (RFC 5761) and demultiplexing
- Breaking of BUNDLE'd media streams (draft-ietf-mmusic-sdp-bundle-negotiation)
- Recording of media streams, decrypted if possible
Rtpengine does not (yet) support:
*Rtpengine* does not (yet) support:
* Repacketization or transcoding
* Playback of pre-recorded streams/announcements
* Recording of media streams
* ZRTP
* ZRTP, although ZRTP passes through *rtpengine* just fine
Compiling and Installing
=========================
@ -100,7 +100,7 @@ The generated files are (with version 2.3.0 being built on an amd64 system):
Manual Compilation
------------------
There's 3 parts to rtpengine, which can be found in the respective subdirectories.
There's 3 parts to *rtpengine*, which can be found in the respective subdirectories.
* `daemon`
@ -113,7 +113,6 @@ There's 3 parts to rtpengine, which can be found in the respective subdirectorie
- *zlib*
- *OpenSSL*
- *PCRE* library
- *libcurl*
- *XMLRPC-C* version 1.16.08 or higher
- *hiredis* library
@ -194,6 +193,7 @@ option and which are reproduced below:
--homer-protocol=udp|tcp Transport protocol for Homer (default udp)
--homer-id=INT 'Capture ID' to use within the HEP protocol
--recording-dir=FILE Spool directory where PCAP call recording data goes
--recording-method=pcap|proc Strategy for call recording
Most of these options are indeed optional, with two exceptions. It's mandatory to specify at least one local
IP address through `--interface`, and at least one of the `--listen-...` options must be given.
@ -472,6 +472,11 @@ The options are described in more detail below.
An optional argument to specify a path to a directory where PCAP recording
files and recording metadata files should be stored. If not specified, support
for call recording will be disabled.
*Rtpengine* supports multiple mechanisms for recording calls. See `recording-method`
below for a list. The default recording method `pcap` is described in
this section.
PCAP files will be stored within a "pcap" subdirectory and metadata
within a "metadata" subdirectory.
@ -510,6 +515,30 @@ The options are described in more detail below.
completes. PCAP files will be written to the subdirectory as the call is
being recorded.
Since call recording via this method happens entirely in userspace, in-kernel
packet forwarding cannot be used for calls that are currently being recorded and
packet forwarding will thus be done in userspace only.
* --recording-method
Multiple methods of call recording are supported and this option can be used to select one.
Currently supported are the method `pcap` and `proc`.
The default method is `pcap` and is the one described above.
The recording method `proc` works by writing metadata files directly into the
`recording-dir` (i.e. not into a subdirectory) and instead of recording RTP packet data
into pcap files, the packet data is exposed via a special interface in the `/proc` filesystem.
Packets must then be retrieved from this interface by a dedicated 3rd party userspace component
(usually a daemon).
Packet data is held in kernel memory until retrieved by the userspace component, but only a limited
number of packets (default 10) per media stream. If packets are not retrieved in time, they will
be simply discarded. This makes it possible to flag all calls to be recorded and then leave it
to the userspace component to decided whether to use the packet data for any purpose or not.
In-kernel packet forwarding is fully supported with this recording method even for calls being
recorded.
A typical command line (enabling both UDP and NG protocols) thus may look like:
/usr/sbin/rtpengine --table=0 --interface=10.64.73.31 --interface=2001:db8::4f3:3d \
@ -829,6 +858,10 @@ Optionally included keys are:
Forces *rtpengine* to retain its local ports during a signalling exchange even when the
remote endpoint changes its port.
- `record call`
Identical to setting `record call` to `on` (see below).
* `replace`
@ -1002,21 +1035,20 @@ Optionally included keys are:
Negates the respective option. This is useful if one of the session parameters was offered by
an SDES endpoint, but it should not be offered on the far side if this endpoint also speaks SDES.
* `record-call`
* `record call`
Contains either the string "yes" or the string "no". This tells the rtpengine
Contains one of the strings `yes`, `no`, `on` or `off`. This tells the rtpengine
whether or not to record the call to PCAP files. If the call is recorded, it
will generate PCAP files for each stream and a metadata file for each call.
Note that rtpengine *will not* force itself into the media path, and other
flags like `ICE=force` may be necessary to ensure the call is recorded.
See the `--recording-dir` option above.
Note that this is not a duplication of the `start_recording` message. If calls
are being kernelized, then they cannot be recorded. The `start_recording`
message does not have a way to prevent a call from being kernelized, so we need
to use this flag when we send an `offer` or `answer` message.
Enabling call recording via this option has the same effect as doing it separately
via the `start recording` message, except that this option guarantees that the
entirety of the call gets recorded, including all details such as SDP bodies
passing through *rtpengine*.
* `metadata`
@ -1364,4 +1396,9 @@ A complete response message might look like this (formatted for readability):
The `start recording` message must contain at least the key `call-id` and may optionally include `from-tag`,
`to-tag` and `via-branch`, as defined above. The reply dictionary contains no additional keys.
This is not implemented by *rtpengine*.
Enables call recording for the call, either for the entire call or for only the specified call leg. Currently
*rtpengine* always enables recording for the entire call and does not support recording only individual
call legs, therefore all keys other than `call-id` are currently ignored.
If the chosen recording method doesn't support in-kernel packet forwarding, enabling call recording
via this messages will force packet forwarding to happen in userspace only.

@ -54,7 +54,6 @@ LDFLAGS+= -lpcap
LDFLAGS+= `pcre-config --libs`
LDFLAGS+= `xmlrpc-c-config client --libs`
LDFLAGS+= -lhiredis
LDFLAGS+= `pkg-config --libs libcurl`
ifneq ($(DBG),yes)
DPKG_BLDFLGS= $(shell which dpkg-buildflags 2>/dev/null)

@ -123,6 +123,10 @@ static const char * const __tag_type_texts[] = {
[FROM_TAG] = "FROM_TAG",
[TO_TAG] = "TO_TAG",
};
static const char *const __opmode_texts[] = {
[OP_OFFER] = "offer",
[OP_ANSWER] = "answer",
};
static const char * get_term_reason_text(enum termination_reason t) {
return get_enum_array_text(__term_reason_texts, t, "UNKNOWN");
@ -130,6 +134,9 @@ static const char * get_term_reason_text(enum termination_reason t) {
const char * get_tag_type_text(enum tag_type t) {
return get_enum_array_text(__tag_type_texts, t, "UNKNOWN");
}
const char *get_opmode_text(enum call_opmode m) {
return get_enum_array_text(__opmode_texts, m, "other");
}
/* ********** */
@ -504,7 +511,7 @@ static void callmaster_timer(void *ptr) {
atomic64_set(&m->stats.packets, atomic64_get_na(&tmpstats.packets));
atomic64_set(&m->stats.errors, atomic64_get_na(&tmpstats.errors));
i = (m->conf.kernelid >= 0) ? kernel_list(m->conf.kernelid) : NULL;
i = kernel_list();
while (i) {
ke = i->data;
@ -851,6 +858,7 @@ struct packet_stream *__packet_stream_new(struct call *call) {
stream->call = call;
atomic64_set_na(&stream->last_packet, poller_now);
stream->rtp_stats = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, __rtp_stats_free);
recording_init_stream(stream);
return stream;
}
@ -1083,6 +1091,9 @@ static int __init_streams(struct call_media *A, struct call_media *B, const stru
if (__init_stream(a))
return -1;
recording_setup_stream(ax); // RTP
recording_setup_stream(a); // RTCP
la = la->next;
lb = lb->next;
@ -2249,12 +2260,7 @@ void call_destroy(struct call *c) {
obj_put(sfd);
}
if (c->recording != NULL) {
recording_finish_file(c->recording);
meta_finish_file(c);
g_slice_free1(sizeof(*(c->recording)), c->recording);
}
recording_finish(c);
rwlock_unlock_w(&c->master_lock);
}

@ -14,10 +14,10 @@
#include <pcre.h>
#include <openssl/x509.h>
#include <limits.h>
#include <pcap.h>
#include "compat.h"
#include "socket.h"
#include "media_socket.h"
#include "recording.h"
#define UNDEFINED ((unsigned int) -1)
#define TRUNCATED " ... Output truncated. Increase Output Buffer ... \n"
@ -65,6 +65,8 @@ enum transport_protocol_index {
PROTO_UDP_TLS_RTP_SAVP,
PROTO_UDP_TLS_RTP_SAVPF,
PROTO_UDPTL,
__PROTO_LAST,
};
enum xmlrpc_format {
@ -331,6 +333,7 @@ struct packet_stream {
struct call *call; /* RO */
unsigned int component; /* RO, starts with 1 */
unsigned int unique_id; /* RO */
struct recording_stream recording; /* LOCK: call->master_lock */
GQueue sfds; /* LOCK: call->master_lock */
struct stream_fd * volatile selected_sfd;
@ -439,14 +442,10 @@ struct call {
unsigned int redis_hosted_db;
unsigned int foreign_call; // created_via_redis_notify call
int record_call;
struct recording *recording;
};
struct callmaster_config {
int kernelfd;
int kernelid;
/* everything below protected by config_lock */
rwlock_t config_lock;
int max_sessions;
@ -544,6 +543,9 @@ void add_total_calls_duration_in_interval(struct callmaster *cm, struct timeval
void __payload_type_free(void *p);
void __rtp_stats_update(GHashTable *dst, GHashTable *src);
const char *get_tag_type_text(enum tag_type t);
const char *get_opmode_text(enum call_opmode);
#include "str.h"
@ -608,6 +610,5 @@ INLINE struct packet_stream *packet_stream_sink(struct packet_stream *ps) {
return ret;
}
const char * get_tag_type_text(enum tag_type t);
#endif

@ -554,6 +554,8 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *inpu
ng_sdes_option(out, it, 5);
else if (!bencode_strcmp(it, "port-latching"))
out->port_latching = 1;
else if (!bencode_strcmp(it, "record-call"))
out->record_call = 1;
else
ilog(LOG_WARN, "Unknown flag encountered: '"BENCODE_FORMAT"'",
BENCODE_FMT(it));
@ -640,13 +642,15 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *inpu
if (bencode_get_alt(input, "address-family", "address family", &out->address_family_str))
out->address_family = get_socket_family_rfc(&out->address_family_str);
out->tos = bencode_dictionary_get_integer(input, "TOS", 256);
bencode_get_alt(input, "record-call", "record call", &out->record_call_str);
bencode_dictionary_get_str(input, "metadata", &out->metadata);
}
static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster *m,
bencode_item_t *output, enum call_opmode opmode, const char* addr,
const endpoint_t *sin)
{
str sdp, fromtag, totag = STR_NULL, callid, viabranch, recordcall = STR_NULL, metadata = STR_NULL;
str sdp, fromtag, totag = STR_NULL, callid, viabranch;
char *errstr;
GQueue parsed = G_QUEUE_INIT;
GQueue streams = G_QUEUE_INIT;
@ -732,34 +736,28 @@ static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster
chopper = sdp_chopper_new(&sdp);
bencode_buffer_destroy_add(output->buffer, (free_func_t) sdp_chopper_destroy, chopper);
bencode_dictionary_get_str(input, "record-call", &recordcall);
if (recordcall.s) {
detect_setup_recording(call, recordcall);
}
detect_setup_recording(call, &flags.record_call_str);
if (flags.record_call)
recording_start(call, NULL);
ret = monologue_offer_answer(monologue, &streams, &flags);
if (!ret)
ret = sdp_replace(chopper, &parsed, monologue->active_dialogue, &flags);
struct iovec *sdp_iov = &g_array_index(chopper->iov, struct iovec, 0);
struct recording *recording = call->recording;
if (call->record_call && recording != NULL && recording->meta_fp != NULL) {
struct iovec *iov = &g_array_index(chopper->iov, struct iovec, 0);
int iovcnt = chopper->iov_num;
meta_write_sdp(recording->meta_fp, iov, iovcnt,
call->recording->packet_num, opmode);
}
bencode_dictionary_get_str(input, "metadata", &metadata);
if (metadata.len > 0 && call->recording != NULL) {
if (call->recording->metadata != NULL) {
free(call->recording->metadata);
call->recording->metadata = NULL;
if (recording != NULL) {
meta_write_sdp_before(recording, &sdp, monologue, opmode);
meta_write_sdp_after(recording, sdp_iov, chopper->iov_num, chopper->str_len,
monologue, opmode);
if (flags.metadata.len) {
call_str_cpy(call, &recording->metadata, &flags.metadata);
recording_meta_chunk(recording, "METADATA", &flags.metadata);
}
call->recording->metadata = str_dup(&metadata);
}
bencode_item_t *recordings = bencode_dictionary_add_list(output, "recordings");
if (call->recording != NULL && call->recording->recording_path != NULL) {
char *recording_path = call->recording->recording_path->s;
bencode_list_add_string(recordings, recording_path);
recording_response(recording, output);
}
rwlock_unlock_w(&call->master_lock);
@ -778,9 +776,8 @@ static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster
if (ret)
goto out;
bencode_dictionary_add_iovec(output, "sdp", &g_array_index(chopper->iov, struct iovec, 0),
bencode_dictionary_add_iovec(output, "sdp", sdp_iov,
chopper->iov_num, chopper->str_len);
bencode_dictionary_add_string(output, "result", "ok");
errstr = NULL;
out:
@ -855,7 +852,6 @@ const char *call_delete_ng(bencode_item_t *input, struct callmaster *m, bencode_
bencode_dictionary_add_string(output, "warning", "Call-ID not found or tags didn't match");
}
bencode_dictionary_add_string(output, "result", "ok");
return NULL;
}
@ -1075,7 +1071,6 @@ const char *call_query_ng(bencode_item_t *input, struct callmaster *m, bencode_i
bencode_dictionary_get_str(input, "from-tag", &fromtag);
bencode_dictionary_get_str(input, "to-tag", &totag);
bencode_dictionary_add_string(output, "result", "ok");
ng_call_stats(call, &fromtag, &totag, output, NULL);
rwlock_unlock_w(&call->master_lock);
obj_put(call);
@ -1093,10 +1088,28 @@ const char *call_list_ng(bencode_item_t *input, struct callmaster *m, bencode_it
if (limit < 0) {
return "invalid limit, must be >= 0";
}
bencode_dictionary_add_string(output, "result", "ok");
calls = bencode_dictionary_add_list(output, "calls");
ng_list_calls(m, calls, limit);
return NULL;
}
const char *call_start_recording_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output) {
str callid;
struct call *call;
if (!bencode_dictionary_get_str(input, "call-id", &callid))
return "No call-id in message";
call = call_get_opmode(&callid, m, OP_OTHER);
if (!call)
return "Unknown call-id";
recording_start(call, NULL);
rwlock_unlock_w(&call->master_lock);
obj_put(call);
return NULL;
}

@ -30,6 +30,8 @@ struct sdp_ng_flags {
str direction[2];
sockfamily_t *address_family;
int tos;
str record_call_str;
str metadata;
int asymmetric:1,
unidirectional:1,
trust_address:1,
@ -47,6 +49,7 @@ struct sdp_ng_flags {
media_handover:1,
dtls_passive:1,
reset:1,
record_call:1,
dtls_off:1,
sdes_off:1,
sdes_unencrypted_srtp:1,
@ -77,6 +80,7 @@ const char *call_answer_ng(bencode_item_t *, struct callmaster *, bencode_item_t
const char *call_delete_ng(bencode_item_t *, struct callmaster *, bencode_item_t *);
const char *call_query_ng(bencode_item_t *, struct callmaster *, bencode_item_t *);
const char *call_list_ng(bencode_item_t *, struct callmaster *, bencode_item_t *);
const char *call_start_recording_ng(bencode_item_t *, struct callmaster *, bencode_item_t *);
void ng_call_stats(struct call *call, const str *fromtag, const str *totag, bencode_item_t *output,
struct call_stats *totals);

@ -171,8 +171,8 @@ static void cli_incoming_list_totals(char* buffer, int len, struct callmaster* m
printlen = snprintf(replybuffer,(outbufend-replybuffer), "Control statistics:\n\n");
ADJUSTLEN(printlen,outbufend,replybuffer);
printlen = snprintf(replybuffer,(outbufend-replybuffer), " %20s | %10s | %10s | %10s | %10s | %10s | %10s | %10s \n",
"Proxy", "Offer", "Answer", "Delete", "Ping", "List", "Query", "Errors");
printlen = snprintf(replybuffer,(outbufend-replybuffer), " %20s | %10s | %10s | %10s | %10s | %10s | %10s | %10s | %10s \n",
"Proxy", "Offer", "Answer", "Delete", "Ping", "List", "Query", "StartRec", "Errors");
ADJUSTLEN(printlen,outbufend,replybuffer);
mutex_lock(&m->cngs_lock);
@ -184,7 +184,7 @@ static void cli_incoming_list_totals(char* buffer, int len, struct callmaster* m
}
for (GList *l = list; l; l = l->next) {
struct control_ng_stats* cur = l->data;
printlen = snprintf(replybuffer,(outbufend-replybuffer), " %20s | %10u | %10u | %10u | %10u | %10u | %10u | %10u \n",
printlen = snprintf(replybuffer,(outbufend-replybuffer), " %20s | %10u | %10u | %10u | %10u | %10u | %10u | %10u | %10u \n",
sockaddr_print_buf(&cur->proxy),
cur->offer,
cur->answer,
@ -192,6 +192,7 @@ static void cli_incoming_list_totals(char* buffer, int len, struct callmaster* m
cur->ping,
cur->list,
cur->query,
cur->start_recording,
cur->errors);
ADJUSTLEN(printlen,outbufend,replybuffer);
}

@ -109,7 +109,7 @@ static void control_ng_incoming(struct obj *obj, str *buf, const endpoint_t *sin
bencode_buffer_t bencbuf;
bencode_item_t *dict, *resp;
str cmd, cookie, data, reply, *to_send, callid;
const char *errstr;
const char *errstr, *resultstr;
struct iovec iov[3];
unsigned int iovlen;
GString *log_str;
@ -168,8 +168,9 @@ static void control_ng_incoming(struct obj *obj, str *buf, const endpoint_t *sin
}
errstr = NULL;
resultstr = "ok";
if (!str_cmp(&cmd, "ping")) {
bencode_dictionary_add_string(resp, "result", "pong");
resultstr = "pong";
g_atomic_int_inc(&cur->ping);
}
else if (!str_cmp(&cmd, "offer")) {
@ -222,12 +223,18 @@ static void control_ng_incoming(struct obj *obj, str *buf, const endpoint_t *sin
errstr = call_list_ng(dict, c->callmaster, resp);
g_atomic_int_inc(&cur->list);
}
else if (!str_cmp(&cmd, "start recording")) {
errstr = call_start_recording_ng(dict, c->callmaster, resp);
g_atomic_int_inc(&cur->start_recording);
}
else
errstr = "Unrecognized command";
if (errstr)
goto err_send;
bencode_dictionary_add_string(resp, "result", resultstr);
// update interval statistics
if (!str_cmp(&cmd, "offer")) {
timeval_update_request_time(&c->callmaster->totalstats_interval.offer, &offer_stop);

@ -18,6 +18,7 @@ struct control_ng_stats {
int delete;
int query;
int list;
int start_recording;
int errors;
};

@ -22,8 +22,13 @@
struct kernel_interface kernel;
int kernel_create_table(unsigned int id) {
static int kernel_create_table(unsigned int id) {
char str[64];
int fd;
int i;
@ -44,20 +49,19 @@ fail:
return -1;
}
int kernel_open_table(unsigned int id) {
static int kernel_open_table(unsigned int id) {
char str[64];
int fd;
struct rtpengine_message msg;
int i;
sprintf(str, PREFIX "/%u/control", id);
fd = open(str, O_WRONLY | O_TRUNC);
fd = open(str, O_RDWR | O_TRUNC);
if (fd == -1)
return -1;
ZERO(msg);
msg.cmd = MMG_NOOP;
msg.cmd = REMG_NOOP;
i = write(fd, &msg, sizeof(msg));
if (i <= 0)
goto fail;
@ -69,15 +73,43 @@ fail:
return -1;
}
int kernel_setup_table(unsigned int id) {
if (kernel.is_wanted)
abort();
kernel.is_wanted = 1;
if (kernel_create_table(id)) {
ilog(LOG_ERR, "FAILED TO CREATE KERNEL TABLE %i (%s), KERNEL FORWARDING DISABLED",
id, strerror(errno));
return -1;
}
int fd = kernel_open_table(id);
if (fd == -1) {
ilog(LOG_ERR, "FAILED TO OPEN KERNEL TABLE %i (%s), KERNEL FORWARDING DISABLED",
id, strerror(errno));
return -1;
}
kernel.fd = fd;
kernel.table = id;
kernel.is_open = 1;
return 0;
}
int kernel_add_stream(int fd, struct rtpengine_target_info *mti, int update) {
int kernel_add_stream(struct rtpengine_target_info *mti, int update) {
struct rtpengine_message msg;
int ret;
msg.cmd = update ? MMG_UPDATE : MMG_ADD;
msg.target = *mti;
if (!kernel.is_open)
return -1;
msg.cmd = update ? REMG_UPDATE : REMG_ADD;
msg.u.target = *mti;
ret = write(fd, &msg, sizeof(msg));
ret = write(kernel.fd, &msg, sizeof(msg));
if (ret > 0)
return 0;
@ -86,15 +118,18 @@ int kernel_add_stream(int fd, struct rtpengine_target_info *mti, int update) {
}
int kernel_del_stream(int fd, const struct re_address *a) {
int kernel_del_stream(const struct re_address *a) {
struct rtpengine_message msg;
int ret;
if (!kernel.is_open)
return -1;
ZERO(msg);
msg.cmd = MMG_DEL;
msg.target.local = *a;
msg.cmd = REMG_DEL;
msg.u.target.local = *a;
ret = write(fd, &msg, sizeof(msg));
ret = write(kernel.fd, &msg, sizeof(msg));
if (ret > 0)
return 0;
@ -102,14 +137,17 @@ int kernel_del_stream(int fd, const struct re_address *a) {
return -1;
}
GList *kernel_list(unsigned int id) {
GList *kernel_list() {
char str[64];
int fd;
struct rtpengine_list_entry *buf;
GList *li = NULL;
int ret;
sprintf(str, PREFIX "/%u/blist", id);
if (!kernel.is_open)
return NULL;
sprintf(str, PREFIX "/%u/blist", kernel.table);
fd = open(str, O_RDONLY);
if (fd == -1)
return NULL;
@ -128,3 +166,55 @@ GList *kernel_list(unsigned int id) {
return li;
}
unsigned int kernel_add_call(const char *id) {
struct rtpengine_message msg;
int ret;
if (!kernel.is_open)
return UNINIT_IDX;
ZERO(msg);
msg.cmd = REMG_ADD_CALL;
snprintf(msg.u.call.call_id, sizeof(msg.u.call.call_id), "%s", id);
ret = read(kernel.fd, &msg, sizeof(msg));
if (ret != sizeof(msg))
return UNINIT_IDX;
return msg.u.call.call_idx;
}
int kernel_del_call(unsigned int idx) {
struct rtpengine_message msg;
int ret;
if (!kernel.is_open)
return -1;
ZERO(msg);
msg.cmd = REMG_DEL_CALL;
msg.u.call.call_idx = idx;
ret = write(kernel.fd, &msg, sizeof(msg));
if (ret != sizeof(msg))
return -1;
return 0;
}
unsigned int kernel_add_intercept_stream(unsigned int call_idx, const char *id) {
struct rtpengine_message msg;
int ret;
if (!kernel.is_open)
return UNINIT_IDX;
ZERO(msg);
msg.cmd = REMG_ADD_STREAM;
msg.u.stream.call_idx = call_idx;
snprintf(msg.u.stream.stream_name, sizeof(msg.u.stream.stream_name), "%s", id);
ret = read(kernel.fd, &msg, sizeof(msg));
if (ret != sizeof(msg))
return UNINIT_IDX;
return msg.u.stream.stream_idx;
}

@ -10,17 +10,36 @@
#define UNINIT_IDX ((unsigned int) -1)
struct rtpengine_target_info;
struct re_address;
int kernel_create_table(unsigned int);
int kernel_open_table(unsigned int);
struct kernel_interface {
unsigned int table;
int fd;
int is_open;
int is_wanted;
};
extern struct kernel_interface kernel;
int kernel_setup_table(unsigned int);
int kernel_add_stream(struct rtpengine_target_info *, int);
int kernel_del_stream(const struct re_address *);
GList *kernel_list();
unsigned int kernel_add_call(const char *id);
int kernel_del_call(unsigned int);
int kernel_add_stream(int, struct rtpengine_target_info *, int);
int kernel_del_stream(int, const struct re_address *);
GList *kernel_list(unsigned int);
unsigned int kernel_add_intercept_stream(unsigned int call_idx, const char *id);

@ -25,8 +25,11 @@ volatile gint log_level = LOG_INFO;
volatile gint log_level = LOG_DEBUG;
#endif
static write_log_t log_both;
unsigned int max_log_line_length = 500;
write_log_t write_log = (write_log_t) syslog;
write_log_t *write_log = (write_log_t *) log_both;
const _fac_code_t _facilitynames[] =
{
@ -76,25 +79,48 @@ static GStringChunk *__log_limiter_strings;
static unsigned int __log_limiter_count;
void log_to_stderr(int facility_priority, char *format, ...) {
static void vlog_to_stderr(int facility_priority, char *format, va_list ap) {
char *msg;
int ret;
va_list ap;
va_start(ap, format);
ret = vasprintf(&msg, format, ap);
va_end(ap);
if (ret < 0) {
fprintf(stderr,"ERR: Failed to print log message - message dropped\n");
return;
}
fprintf(stderr, "[%lu.%06lu] %s\n", (unsigned long) g_now.tv_sec, (unsigned long) g_now.tv_usec, msg);
if (G_LIKELY(g_now.tv_sec))
fprintf(stderr, "[%lu.%06lu] %s\n", (unsigned long) g_now.tv_sec,
(unsigned long) g_now.tv_usec, msg);
else
fprintf(stderr, "%s\n", msg);
free(msg);
}
void log_to_stderr(int facility_priority, char *format, ...) {
va_list ap;
va_start(ap, format);
vlog_to_stderr(facility_priority, format, ap);
va_end(ap);
}
static void log_both(int facility_priority, char *format, ...) {
va_list ap;
va_start(ap, format);
vsyslog(facility_priority, format, ap);
va_end(ap);
if (LOG_LEVEL_MASK(facility_priority) <= LOG_WARN) {
va_start(ap, format);
vlog_to_stderr(facility_priority, format, ap);
va_end(ap);
}
}
void __ilog(int prio, const char *fmt, ...) {
char prefix[300];
char *msg, *piece;

@ -45,8 +45,8 @@ typedef struct _fac_code {
extern const _fac_code_t _facilitynames[];
typedef void (* write_log_t) (int facility_priority, char *format, ...) __attribute__ ((format (printf, 2, 3)));
extern write_log_t write_log;
typedef void write_log_t(int facility_priority, char *format, ...) __attribute__ ((format (printf, 2, 3)));
extern write_log_t *write_log;
void log_to_stderr(int facility_priority, char *format, ...) __attribute__ ((format (printf, 2, 3)));

@ -35,8 +35,6 @@
#define die(x...) do { \
fprintf(stderr, x); \
fprintf(stderr, "\n"); \
ilog(LOG_CRIT, x); \
exit(-1); \
} while(0)
@ -89,6 +87,7 @@ static int num_threads;
static int delete_delay = 30;
static int graphite_interval = 0;
static char *spooldir;
static char *rec_method = "pcap";
static void sighandler(gpointer x) {
sigset_t ss;
@ -329,6 +328,7 @@ static void options(int *argc, char ***argv) {
{ "homer-protocol",0,0,G_OPTION_ARG_STRING, &homerproto, "Transport protocol for Homer (default udp)", "udp|tcp" },
{ "homer-id", 0, 0, G_OPTION_ARG_STRING, &homer_id, "'Capture ID' to use within the HEP protocol", "INT" },
{ "recording-dir", 0, 0, G_OPTION_ARG_STRING, &spooldir, "Directory for storing pcap and metadata files", "FILE" },
{ "recording-method",0, 0, G_OPTION_ARG_STRING, &rec_method, "Strategy for call recording", "pcap|proc" },
{ NULL, }
};
@ -470,6 +470,7 @@ static void options(int *argc, char ***argv) {
static void daemonize(void) {
if (fork())
_exit(0);
write_log = (write_log_t *) syslog;
stdin = freopen("/dev/null", "r", stdin);
stdout = freopen("/dev/null", "w", stdout);
stderr = freopen("/dev/null", "w", stderr);
@ -528,7 +529,7 @@ static void init_everything() {
struct timespec ts;
log_init();
recording_fs_init(spooldir);
recording_fs_init(spooldir, rec_method);
clock_gettime(CLOCK_REALTIME, &ts);
srandom(ts.tv_sec ^ ts.tv_nsec);
SSL_library_init();
@ -555,26 +556,17 @@ static void create_everything(struct main_context *ctx) {
struct control_udp *cu;
struct control_ng *cn;
struct cli *cl;
int kfd = -1;
struct timeval tmp_tv;
struct timeval redis_start, redis_stop;
double redis_diff = 0;
if (table < 0)
goto no_kernel;
if (kernel_create_table(table)) {
fprintf(stderr, "FAILED TO CREATE KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table);
ilog(LOG_CRIT, "FAILED TO CREATE KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table);
if (no_fallback)
if (kernel_setup_table(table)) {
if (no_fallback) {
ilog(LOG_CRIT, "Userspace fallback disallowed - exiting");
exit(-1);
goto no_kernel;
}
kfd = kernel_open_table(table);
if (kfd == -1) {
fprintf(stderr, "FAILED TO OPEN KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table);
ilog(LOG_CRIT, "FAILED TO OPEN KERNEL TABLE %i, KERNEL FORWARDING DISABLED\n", table);
if (no_fallback)
exit(-1);
goto no_kernel;
}
@ -591,8 +583,6 @@ no_kernel:
ZERO(mc);
rwlock_init(&mc.config_lock);
mc.kernelfd = kfd;
mc.kernelid = table;
if (max_sessions < -1) {
max_sessions = -1;
}

@ -124,7 +124,7 @@ static const struct streamhandler __sh_savpf2savp = {
/* ********** */
static const struct streamhandler *__sh_matrix_in_rtp_avp[] = {
static const struct streamhandler * const __sh_matrix_in_rtp_avp[__PROTO_LAST] = {
[PROTO_RTP_AVP] = &__sh_noop,
[PROTO_RTP_AVPF] = &__sh_noop,
[PROTO_RTP_SAVP] = &__sh_avp2savp,
@ -133,7 +133,7 @@ static const struct streamhandler *__sh_matrix_in_rtp_avp[] = {
[PROTO_UDP_TLS_RTP_SAVPF] = &__sh_avp2savp,
[PROTO_UDPTL] = &__sh_noop,
};
static const struct streamhandler *__sh_matrix_in_rtp_avpf[] = {
static const struct streamhandler * const __sh_matrix_in_rtp_avpf[__PROTO_LAST] = {
[PROTO_RTP_AVP] = &__sh_avpf2avp,
[PROTO_RTP_AVPF] = &__sh_noop,
[PROTO_RTP_SAVP] = &__sh_avpf2savp,
@ -142,7 +142,7 @@ static const struct streamhandler *__sh_matrix_in_rtp_avpf[] = {
[PROTO_UDP_TLS_RTP_SAVPF] = &__sh_avp2savp,
[PROTO_UDPTL] = &__sh_noop,
};
static const struct streamhandler *__sh_matrix_in_rtp_savp[] = {
static const struct streamhandler * const __sh_matrix_in_rtp_savp[__PROTO_LAST] = {
[PROTO_RTP_AVP] = &__sh_savp2avp,
[PROTO_RTP_AVPF] = &__sh_savp2avp,
[PROTO_RTP_SAVP] = &__sh_noop,
@ -151,7 +151,7 @@ static const struct streamhandler *__sh_matrix_in_rtp_savp[] = {
[PROTO_UDP_TLS_RTP_SAVPF] = &__sh_noop,
[PROTO_UDPTL] = &__sh_noop,
};
static const struct streamhandler *__sh_matrix_in_rtp_savpf[] = {
static const struct streamhandler * const __sh_matrix_in_rtp_savpf[__PROTO_LAST] = {
[PROTO_RTP_AVP] = &__sh_savpf2avp,
[PROTO_RTP_AVPF] = &__sh_savp2avp,
[PROTO_RTP_SAVP] = &__sh_savpf2savp,
@ -160,7 +160,7 @@ static const struct streamhandler *__sh_matrix_in_rtp_savpf[] = {
[PROTO_UDP_TLS_RTP_SAVPF] = &__sh_noop,
[PROTO_UDPTL] = &__sh_noop,
};
static const struct streamhandler *__sh_matrix_in_rtp_savp_recrypt[] = {
static const struct streamhandler * const __sh_matrix_in_rtp_savp_recrypt[__PROTO_LAST] = {
[PROTO_RTP_AVP] = &__sh_savp2avp,
[PROTO_RTP_AVPF] = &__sh_savp2avp,
[PROTO_RTP_SAVP] = &__sh_savp2savp,
@ -169,7 +169,7 @@ static const struct streamhandler *__sh_matrix_in_rtp_savp_recrypt[] = {
[PROTO_UDP_TLS_RTP_SAVPF] = &__sh_savp2savp,
[PROTO_UDPTL] = &__sh_noop,
};
static const struct streamhandler *__sh_matrix_in_rtp_savpf_recrypt[] = {
static const struct streamhandler * const __sh_matrix_in_rtp_savpf_recrypt[__PROTO_LAST] = {
[PROTO_RTP_AVP] = &__sh_savpf2avp,
[PROTO_RTP_AVPF] = &__sh_savp2avp,
[PROTO_RTP_SAVP] = &__sh_savpf2savp,
@ -178,7 +178,7 @@ static const struct streamhandler *__sh_matrix_in_rtp_savpf_recrypt[] = {
[PROTO_UDP_TLS_RTP_SAVPF] = &__sh_savp2savp,
[PROTO_UDPTL] = &__sh_noop,
};
static const struct streamhandler *__sh_matrix_noop[] = {
static const struct streamhandler * const __sh_matrix_noop[__PROTO_LAST] = {
[PROTO_RTP_AVP] = &__sh_noop,
[PROTO_RTP_AVPF] = &__sh_noop,
[PROTO_RTP_SAVP] = &__sh_noop,
@ -190,7 +190,7 @@ static const struct streamhandler *__sh_matrix_noop[] = {
/* ********** */
static const struct streamhandler **__sh_matrix[] = {
static const struct streamhandler * const * const __sh_matrix[__PROTO_LAST] = {
[PROTO_RTP_AVP] = __sh_matrix_in_rtp_avp,
[PROTO_RTP_AVPF] = __sh_matrix_in_rtp_avpf,
[PROTO_RTP_SAVP] = __sh_matrix_in_rtp_savp,
@ -200,7 +200,7 @@ static const struct streamhandler **__sh_matrix[] = {
[PROTO_UDPTL] = __sh_matrix_noop,
};
/* special case for DTLS as we can't pass through SRTP<>SRTP */
static const struct streamhandler **__sh_matrix_recrypt[] = {
static const struct streamhandler * const * const __sh_matrix_recrypt[__PROTO_LAST] = {
[PROTO_RTP_AVP] = __sh_matrix_in_rtp_avp,
[PROTO_RTP_AVPF] = __sh_matrix_in_rtp_avpf,
[PROTO_RTP_SAVP] = __sh_matrix_in_rtp_savp_recrypt,
@ -859,16 +859,17 @@ static int __rtp_stats_pt_sort(const void *ap, const void *bp) {
void kernelize(struct packet_stream *stream) {
struct rtpengine_target_info reti;
struct call *call = stream->call;
struct callmaster *cm = call->callmaster;
struct packet_stream *sink = NULL;
const char *nk_warn_msg;
if (PS_ISSET(stream, KERNELIZED) || call->recording != NULL)
if (PS_ISSET(stream, KERNELIZED))
return;
if (cm->conf.kernelid < 0)
if (call->recording != NULL && !selected_recording_method->kernel_support)
return;
if (!kernel.is_wanted)
goto no_kernel;
nk_warn_msg = "interface to kernel module not open";
if (cm->conf.kernelfd < 0)
if (!kernel.is_open)
goto no_kernel_warn;
if (!PS_ISSET(stream, RTP))
goto no_kernel;
@ -949,7 +950,9 @@ void kernelize(struct packet_stream *stream) {
g_list_free(values);
}
kernel_add_stream(cm->conf.kernelfd, &reti, 0);
recording_stream_kernel_info(stream, &reti);
kernel_add_stream(&reti, 0);
PS_SET(stream, KERNELIZED);
return;
@ -970,9 +973,9 @@ void __unkernelize(struct packet_stream *p) {
if (PS_ISSET(p, NO_KERNEL_SUPPORT))
return;
if (p->call->callmaster->conf.kernelfd >= 0) {
if (kernel.is_open) {
__re_address_translate_ep(&rea, &p->selected_sfd->socket.local);
kernel_del_stream(p->call->callmaster->conf.kernelfd, &rea);
kernel_del_stream(&rea);
}
PS_CLEAR(p, KERNELIZED);
@ -1003,8 +1006,8 @@ void unkernelize(struct packet_stream *ps) {
/* must be called with call->master_lock held in R, and in->in_lock held */
static void determine_handler(struct packet_stream *in, const struct packet_stream *out) {
const struct streamhandler **sh_pp, *sh;
const struct streamhandler ***matrix;
const struct streamhandler * const *sh_pp, *sh;
const struct streamhandler * const * const *matrix;
if (in->handler)
return;
@ -1019,6 +1022,8 @@ static void determine_handler(struct packet_stream *in, const struct packet_stre
matrix = __sh_matrix;
if (MEDIA_ISSET(in->media, DTLS) || MEDIA_ISSET(out->media, DTLS))
matrix = __sh_matrix_recrypt;
else if (in->call->recording)
matrix = __sh_matrix_recrypt;
else if (in->media->protocol->srtp && out->media->protocol->srtp
&& in->selected_sfd && out->selected_sfd
&& (crypto_params_cmp(&in->crypto.params, &out->selected_sfd->crypto.params)
@ -1074,7 +1079,6 @@ static int stream_packet(struct stream_fd *sfd, str *s, const endpoint_t *fsin,
int ret = 0, update = 0, stun_ret = 0, handler_ret = 0, muxed_rtcp = 0, rtcp = 0,
unk = 0;
int i;
pcap_dumper_t *recording_pdumper;
struct call *call;
struct callmaster *cm;
/*unsigned char cc;*/
@ -1085,7 +1089,6 @@ static int stream_packet(struct stream_fd *sfd, str *s, const endpoint_t *fsin,
struct rtp_stats *rtp_s;
call = sfd->call;
recording_pdumper = call->recording != NULL ? call->recording->recording_pdumper : NULL;
cm = call->callmaster;
rwlock_lock_r(&call->master_lock);
@ -1231,11 +1234,8 @@ loop_ok:
}
// If recording pcap dumper is set, then we record the call.
if (recording_pdumper != NULL && call->record_call) {
mutex_lock(&call->recording->recording_lock);
stream_pcap_dump(recording_pdumper, stream, s);
call->recording->packet_num++;
mutex_unlock(&call->recording->recording_lock);
if (call->recording) {
dump_packet(call->recording, stream, s);
}
if (handler_ret >= 0) {

@ -7,47 +7,142 @@
#include <netinet/in.h>
#include <time.h>
#include <pcap.h>
#include <curl/curl.h>
#include <inttypes.h>
#include "call.h"
#include <errno.h>
#include <unistd.h>
#include <assert.h>
#include <stdarg.h>
#include "xt_RTPENGINE.h"
#include "call.h"
#include "kernel.h"
#include "bencode.h"
static int check_main_spool_dir(const char *spoolpath);
static char *recording_setup_file(struct recording *recording);
static char *meta_setup_file(struct recording *recording);
// pcap methods
static int pcap_create_spool_dir(const char *dirpath);
static void pcap_init(struct call *);
static void sdp_after_pcap(struct recording *, struct iovec *sdp_iov, int iovcnt,
unsigned int str_len, struct call_monologue *, enum call_opmode opmode);
static void dump_packet_pcap(struct recording *recording, struct packet_stream *sink, const str *s);
static void finish_pcap(struct call *);
static void response_pcap(struct recording *, bencode_item_t *);
// proc methods
static void proc_init(struct call *);
static void sdp_before_proc(struct recording *, const str *, struct call_monologue *, enum call_opmode);
static void sdp_after_proc(struct recording *, struct iovec *sdp_iov, int iovcnt,
unsigned int str_len, struct call_monologue *, enum call_opmode opmode);
static void meta_chunk_proc(struct recording *, const char *, const str *);
static void finish_proc(struct call *);
static void dump_packet_proc(struct recording *recording, struct packet_stream *sink, const str *s);
static void init_stream_proc(struct packet_stream *);
static void setup_stream_proc(struct packet_stream *);
static void kernel_info_proc(struct packet_stream *, struct rtpengine_target_info *);
static const struct recording_method methods[] = {
{
.name = "pcap",
.kernel_support = 0,
.create_spool_dir = pcap_create_spool_dir,
.init_struct = pcap_init,
.sdp_after = sdp_after_pcap,
.dump_packet = dump_packet_pcap,
.finish = finish_pcap,
.response = response_pcap,
},
{
.name = "proc",
.kernel_support = 1,
.create_spool_dir = check_main_spool_dir,
.init_struct = proc_init,
.sdp_before = sdp_before_proc,
.sdp_after = sdp_after_proc,
.meta_chunk = meta_chunk_proc,
.dump_packet = dump_packet_proc,
.finish = finish_proc,
.init_stream_struct = init_stream_proc,
.setup_stream = setup_stream_proc,
.stream_kernel_info = kernel_info_proc,
},
};
int maybe_create_spool_dir(char *dirpath);
int set_record_call(struct call *call, str recordcall);
str *init_write_pcap_file(struct call *call);
// Global file reference to the spool directory.
static char *spooldir = NULL;
// Used for URL encoding functions
CURL *curl;
const struct recording_method *selected_recording_method;
/**
* Initialize RTP Engine filesystem settings and structure.
* Check for or create the RTP Engine spool directory.
*/
void recording_fs_init(char *spoolpath) {
curl = curl_easy_init();
void recording_fs_init(const char *spoolpath, const char *method_str) {
int i;
// Whether or not to fail if the spool directory does not exist.
int dne_fail;
if (spoolpath == NULL || spoolpath[0] == '\0')
return;
dne_fail = TRUE;
int path_len = strlen(spoolpath);
for (i = 0; i < G_N_ELEMENTS(methods); i++) {
if (!strcmp(methods[i].name, method_str)) {
selected_recording_method = &methods[i];
goto found;
}
}
ilog(LOG_ERROR, "Recording method '%s' not supported", method_str);
return;
found:
spooldir = strdup(spoolpath);
int path_len = strlen(spooldir);
// Get rid of trailing "/" if it exists. Other code adds that in when needed.
if (spoolpath[path_len-1] == '/') {
spoolpath[path_len-1] = '\0';
if (spooldir[path_len-1] == '/') {
spooldir[path_len-1] = '\0';
}
if (!maybe_create_spool_dir(spoolpath)) {
fprintf(stderr, "Error while setting up spool directory \"%s\".\n", spoolpath);
if (dne_fail) {
fprintf(stderr, "Please run `mkdir %s` and start rtpengine again.\n", spoolpath);
if (!_rm_ret(create_spool_dir, spooldir)) {
ilog(LOG_ERR, "Error while setting up spool directory \"%s\".", spooldir);
ilog(LOG_ERR, "Please run `mkdir %s` and start rtpengine again.", spooldir);
exit(-1);
}
} else {
spooldir = strdup(spoolpath);
}
static int check_create_dir(const char *dir, const char *desc, int creat) {
struct stat info;
if (stat(dir, &info) != 0) {
if (!creat) {
ilog(LOG_WARN, "%s directory \"%s\" does not exist.", desc, dir);
return FALSE;
}
ilog(LOG_INFO, "Creating %s directory \"%s\".", desc, dir);
if (mkdir(dir, 0777) == 0)
return TRUE;
ilog(LOG_ERR, "Failed to create %s directory \"%s\": %s", desc, dir, strerror(errno));
return FALSE;
}
if(!S_ISDIR(info.st_mode)) {
ilog(LOG_ERR, "%s file exists, but \"%s\" is not a directory.", desc, dir);
return FALSE;
}
return TRUE;
}
static int check_main_spool_dir(const char *spoolpath) {
return check_create_dir(spoolpath, "spool", 0);
}
/**
@ -59,42 +154,75 @@ void recording_fs_init(char *spoolpath) {
*
* Create the "metadata" and "pcaps" directories if they are not there.
*/
int maybe_create_spool_dir(char *spoolpath) {
struct stat info;
static int pcap_create_spool_dir(const char *spoolpath) {
int spool_good = TRUE;
if (stat(spoolpath, &info) != 0) {
fprintf(stderr, "Spool directory \"%s\" does not exist.\n", spoolpath);
spool_good = FALSE;
} else if (!S_ISDIR(info.st_mode)) {
fprintf(stderr, "Spool file exists, but \"%s\" is not a directory.\n", spoolpath);
spool_good = FALSE;
} else {
if (!check_main_spool_dir(spoolpath))
return FALSE;
// Spool directory exists. Make sure it has inner directories.
int path_len = strlen(spoolpath);
char meta_path[path_len + 10];
char rec_path[path_len + 7];
snprintf(meta_path, path_len + 10, "%s/metadata", spoolpath);
snprintf(rec_path, path_len + 7, "%s/pcaps", spoolpath);
if (stat(meta_path, &info) != 0) {
fprintf(stdout, "Creating metadata directory \"%s\".\n", meta_path);
mkdir(meta_path, 0777);
} else if(!S_ISDIR(info.st_mode)) {
fprintf(stderr, "metadata file exists, but \"%s\" is not a directory.\n", meta_path);
char tmp_path[path_len + 5];
snprintf(meta_path, sizeof(meta_path), "%s/metadata", spoolpath);
snprintf(rec_path, sizeof(rec_path), "%s/pcaps", spoolpath);
snprintf(tmp_path, sizeof(tmp_path), "%s/tmp", spoolpath);
if (!check_create_dir(meta_path, "metadata", 1))
spool_good = FALSE;
if (!check_create_dir(rec_path, "pcaps", 1))
spool_good = FALSE;
if (!check_create_dir(tmp_path, "tmp", 1))
spool_good = FALSE;
return spool_good;
}
// lock must be held
void recording_start(struct call *call, const char *prefix) {
if (call->recording) // already active
return;
if (!spooldir) {
ilog(LOG_ERR, "Call recording requested, but no spool directory configured");
return;
}
ilog(LOG_NOTICE, "Turning on call recording.");
if (stat(rec_path, &info) != 0) {
fprintf(stdout, "Creating pcaps directory \"%s\".\n", rec_path);
mkdir(rec_path, 0777);
} else if(!S_ISDIR(info.st_mode)) {
fprintf(stderr, "pcaps file exists, but \"%s\" is not a directory.\n", rec_path);
spool_good = FALSE;
call->recording = g_slice_alloc0(sizeof(struct recording));
struct recording *recording = call->recording;
recording->escaped_callid = g_uri_escape_string(call->callid.s, NULL, 0);
if (!prefix) {
const int rand_bytes = 8;
char rand_str[rand_bytes * 2 + 1];
rand_hex_str(rand_str, rand_bytes);
if (asprintf(&recording->meta_prefix, "%s-%s", recording->escaped_callid, rand_str) < 0)
abort();
}
else
recording->meta_prefix = strdup(prefix);
_rm(init_struct, call);
// if recording has been turned on after initial call setup, we must walk
// through all related objects and initialize the recording stuff. if this
// function is called right at the start of the call, all of the following
// is essentially a no-op
GList *l;
for (l = call->streams.head; l; l = l->next) {
struct packet_stream *ps = l->data;
recording_setup_stream(ps);
__unkernelize(ps);
ps->handler = NULL;
}
}
void recording_stop(struct call *call) {
if (!call->recording)
return;
return spool_good;
ilog(LOG_NOTICE, "Turning off call recording.");
recording_finish(call);
}
/**
@ -107,137 +235,99 @@ int maybe_create_spool_dir(char *spoolpath) {
*
* Returns a boolean for whether or not the call is being recorded.
*/
int detect_setup_recording(struct call *call, str recordcall) {
int is_recording = set_record_call(call, recordcall);
struct recording *recording = call->recording;
if (is_recording && recording != NULL && recording->recording_pdumper == NULL) {
// We haven't set up the PCAP file, so set it up and write the URL to metadata
init_write_pcap_file(call);
}
return is_recording;
void detect_setup_recording(struct call *call, const str *recordcall) {
if (!recordcall || !recordcall->s)
return;
if (!str_cmp(recordcall, "yes") || !str_cmp(recordcall, "on"))
recording_start(call, NULL);
else if (!str_cmp(recordcall, "no") || !str_cmp(recordcall, "off"))
recording_stop(call);
else
ilog(LOG_INFO, "\"record-call\" flag "STR_FORMAT" is invalid flag.", STR_FMT(recordcall));
}
/**
* Controls the setting of recording variables on a `struct call *`.
* Sets the `record_call` value on the `struct call`, initializing the
* recording struct if necessary.
*
* Returns a boolean for whether or not the call is being recorded.
*/
int set_record_call(struct call *call, str recordcall) {
if (!str_cmp(&recordcall, "yes")) {
if (call->record_call == FALSE) {
if (!spooldir) {
ilog(LOG_ERR, "Call recording requested, but no spool directory configured");
return FALSE;
}
ilog(LOG_NOTICE, "Turning on call recording.");
}
call->record_call = TRUE;
if (call->recording == NULL) {
call->recording = g_slice_alloc0(sizeof(struct recording));
call->recording->recording_pd = NULL;
call->recording->recording_pdumper = NULL;
static void pcap_init(struct call *call) {
struct recording *recording = call->recording;
// Wireshark starts at packet index 1, so we start there, too
call->recording->packet_num = 1;
mutex_init(&call->recording->recording_lock);
meta_setup_file(call->recording, call->callid);
}
} else if (!str_cmp(&recordcall, "no")) {
if (call->record_call == TRUE) {
ilog(LOG_NOTICE, "Turning off call recording.");
}
call->record_call = FALSE;
} else {
ilog(LOG_INFO, "\"record-call\" flag %s is invalid flag.", recordcall.s);
recording->pcap.packet_num = 1;
mutex_init(&recording->pcap.recording_lock);
meta_setup_file(recording);
// set up pcap file
char *pcap_path = recording_setup_file(recording);
if (pcap_path != NULL && recording->pcap.recording_pdumper != NULL
&& recording->pcap.meta_fp) {
// Write the location of the PCAP file to the metadata file
fprintf(recording->pcap.meta_fp, "%s\n\n", pcap_path);
}
return call->record_call;
}
/**
* Checks if we have a PCAP file for the call yet.
* If not, create it and write its location to the metadata file.
*/
str *init_write_pcap_file(struct call *call) {
str *pcap_path = recording_setup_file(call->recording, call->callid);
if (pcap_path != NULL && call->recording->recording_pdumper != NULL
&& call->recording->meta_fp) {
// Write the location of the PCAP file to the metadata file
fprintf(call->recording->meta_fp, "%s\n\n", pcap_path->s);
}
return pcap_path;
static char *file_path_str(const char *id, const char *prefix, const char *suffix) {
char *ret;
if (asprintf(&ret, "%s%s%s%s", spooldir, prefix, id, suffix) < 0)
abort();
return ret;
}
/**
* Create a call metadata file in a temporary location.
* Attaches the filepath and the file pointer to the call struct.
*/
str *meta_setup_file(struct recording *recording, str callid) {
static char *meta_setup_file(struct recording *recording) {
if (spooldir == NULL) {
// No spool directory was created, so we cannot have metadata files.
return NULL;
}
else {
int rand_bytes = 8;
str *meta_filepath = malloc(sizeof(str));
// We don't want weird characters like ":" or "@" showing up in filenames
char *escaped_callid = curl_easy_escape(curl, callid.s, callid.len);
int escaped_callid_len = strlen(escaped_callid);
// Length for spool directory path + "/tmp/rtpengine-meta-${CALLID}-"
int mid_len = 20 + escaped_callid_len + 1 + 1;
char suffix_chars[mid_len];
snprintf(suffix_chars, mid_len, "/tmp/rtpengine-meta-%s-", escaped_callid);
curl_free(escaped_callid);
// Initially file extension is ".tmp". When call is over, it changes to ".txt".
char *path_chars = rand_affixed_str(suffix_chars, rand_bytes, ".tmp");
meta_filepath = str_init(meta_filepath, path_chars);
char *meta_filepath = file_path_str(recording->meta_prefix, "/tmp/rtpengine-meta-", ".tmp");
recording->meta_filepath = meta_filepath;
FILE *mfp = fopen(meta_filepath->s, "w");
chmod(meta_filepath->s, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
FILE *mfp = fopen(meta_filepath, "w");
chmod(meta_filepath, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
if (mfp == NULL) {
ilog(LOG_ERROR, "Could not open metadata file: %s", meta_filepath->s);
free(recording->meta_filepath->s);
free(recording->meta_filepath);
ilog(LOG_ERROR, "Could not open metadata file: %s", meta_filepath);
free(meta_filepath);
recording->meta_filepath = NULL;
return NULL;
}
recording->meta_fp = mfp;
ilog(LOG_DEBUG, "Wrote metadata file to temporary path: %s", meta_filepath->s);
recording->pcap.meta_fp = mfp;
ilog(LOG_DEBUG, "Wrote metadata file to temporary path: %s", meta_filepath);
return meta_filepath;
}
}
/**
* Write out a block of SDP to the metadata file.
*/
ssize_t meta_write_sdp(FILE *meta_fp, struct iovec *sdp_iov, int iovcnt,
uint64_t packet_num, enum call_opmode opmode) {
static void sdp_after_pcap(struct recording *recording, struct iovec *sdp_iov, int iovcnt,
unsigned int str_len, struct call_monologue *ml, enum call_opmode opmode)
{
FILE *meta_fp = recording->pcap.meta_fp;
if (!meta_fp)
return;
int meta_fd = fileno(meta_fp);
// File pointers buffer data, whereas direct writing using the file
// descriptor does not. Make sure to flush any unwritten contents
// so the file contents appear in order.
fprintf(meta_fp, "\nSDP mode: ");
if (opmode == OP_ANSWER) {
fprintf(meta_fp, "answer");
} else if (opmode == OP_OFFER) {
fprintf(meta_fp, "offer");
} else {
fprintf(meta_fp, "other");
}
fprintf(meta_fp, "\nSDP before RTP packet: %" PRIu64 "\n\n", packet_num);
fprintf(meta_fp, "%s", get_opmode_text(opmode));
fprintf(meta_fp, "\nSDP before RTP packet: %" PRIu64 "\n\n", recording->pcap.packet_num);
fflush(meta_fp);
return writev(meta_fd, sdp_iov, iovcnt);
if (writev(meta_fd, sdp_iov, iovcnt) <= 0)
ilog(LOG_WARN, "Error writing SDP body to metadata file: %s", strerror(errno));
}
/**
* Writes metadata to metafile, closes file, and renames it to finished location.
* Returns non-zero for failure.
*/
int meta_finish_file(struct call *call) {
static int pcap_meta_finish_file(struct call *call) {
// This should usually be called from a place that has the call->master_lock
struct recording *recording = call->recording;
int return_code = 0;
if (recording != NULL && recording->meta_fp != NULL) {
if (recording != NULL && recording->pcap.meta_fp != NULL) {
// Print start timestamp and end timestamp
// YYYY-MM-DDThh:mm:ss
time_t start = call->created;
@ -246,26 +336,24 @@ int meta_finish_file(struct call *call) {
struct tm *timeinfo;
timeinfo = localtime(&start);
strftime(timebuffer, 20, "%FT%T", timeinfo);
fprintf(recording->meta_fp, "\n\ncall start time: %s\n", timebuffer);
fprintf(recording->pcap.meta_fp, "\n\ncall start time: %s\n", timebuffer);
timeinfo = localtime(&end);
strftime(timebuffer, 20, "%FT%T", timeinfo);
fprintf(recording->meta_fp, "call end time: %s\n", timebuffer);
fprintf(recording->pcap.meta_fp, "call end time: %s\n", timebuffer);
// Print metadata
if (recording->metadata)
fprintf(recording->meta_fp, "\n\n%s\n", recording->metadata->s);
free(recording->metadata);
recording->metadata = NULL;
fclose(recording->meta_fp);
if (recording->metadata.len)
fprintf(recording->pcap.meta_fp, "\n\n"STR_FORMAT"\n", STR_FMT(&recording->metadata));
fclose(recording->pcap.meta_fp);
// Get the filename (in between its directory and the file extension)
// and move it to the finished file location.
// Rename extension to ".txt".
int fn_len;
char *meta_filename = strrchr(recording->meta_filepath->s, '/');
char *meta_filename = strrchr(recording->meta_filepath, '/');
char *meta_ext = NULL;
if (meta_filename == NULL) {
meta_filename = recording->meta_filepath->s;
meta_filename = recording->meta_filepath;
}
else {
meta_filename = meta_filename + 1;
@ -278,22 +366,18 @@ int meta_finish_file(struct call *call) {
char new_metapath[prefix_len + fn_len + ext_len + 1];
snprintf(new_metapath, prefix_len+fn_len+1, "%s/metadata/%s", spooldir, meta_filename);
snprintf(new_metapath + prefix_len+fn_len, ext_len+1, ".txt");
return_code = return_code || rename(recording->meta_filepath->s, new_metapath);
return_code = return_code || rename(recording->meta_filepath, new_metapath);
if (return_code != 0) {
ilog(LOG_ERROR, "Could not move metadata file \"%s\" to \"%s/metadata/\"",
recording->meta_filepath->s, spooldir);
recording->meta_filepath, spooldir);
} else {
ilog(LOG_INFO, "Moved metadata file \"%s\" to \"%s/metadata\"",
recording->meta_filepath->s, spooldir);
recording->meta_filepath, spooldir);
}
} else {
ilog(LOG_INFO, "Trying to clean up recording meta file without a file pointer opened.");
}
if (recording != NULL && recording->meta_filepath != NULL) {
free(recording->meta_filepath->s);
free(recording->meta_filepath);
}
mutex_destroy(&recording->recording_lock);
mutex_destroy(&recording->pcap.recording_lock);
return return_code;
}
@ -302,39 +386,25 @@ int meta_finish_file(struct call *call) {
* Generate a random PCAP filepath to write recorded RTP stream.
* Returns path to created file.
*/
str *recording_setup_file(struct recording *recording, str callid) {
str *recording_path = NULL;
if (spooldir != NULL
&& recording != NULL
&& recording->recording_pd == NULL && recording->recording_pdumper == NULL) {
int rand_bytes = 8;
// We don't want weird characters like ":" or "@" showing up in filenames
char *escaped_callid = curl_easy_escape(curl, callid.s, callid.len);
int escaped_callid_len = strlen(escaped_callid);
// Length for spool directory path + "/pcaps/${CALLID}-"
int rec_path_len = strlen(spooldir) + 7 + escaped_callid_len + 1 + 1;
char rec_path[rec_path_len];
snprintf(rec_path, rec_path_len, "%s/pcaps/%s-", spooldir, escaped_callid);
curl_free(escaped_callid);
char *path_chars = rand_affixed_str(rec_path, rand_bytes, ".pcap");
recording_path = malloc(sizeof(str));
recording_path = str_init(recording_path, path_chars);
recording->recording_path = recording_path;
recording->recording_pd = pcap_open_dead(DLT_RAW, 65535);
recording->recording_pdumper = pcap_dump_open(recording->recording_pd, path_chars);
if (recording->recording_pdumper == NULL) {
pcap_close(recording->recording_pd);
recording->recording_pd = NULL;
ilog(LOG_INFO, "Failed to write recording file: %s", recording_path->s);
static char *recording_setup_file(struct recording *recording) {
char *recording_path = NULL;
if (!spooldir)
return NULL;
if (recording->pcap.recording_pd || recording->pcap.recording_pdumper)
return NULL;
recording_path = file_path_str(recording->meta_prefix, "/pcaps/", ".pcap");
recording->pcap.recording_path = recording_path;
recording->pcap.recording_pd = pcap_open_dead(DLT_RAW, 65535);
recording->pcap.recording_pdumper = pcap_dump_open(recording->pcap.recording_pd, recording_path);
if (recording->pcap.recording_pdumper == NULL) {
pcap_close(recording->pcap.recording_pd);
recording->pcap.recording_pd = NULL;
ilog(LOG_INFO, "Failed to write recording file: %s", recording_path);
} else {
ilog(LOG_INFO, "Writing recording file: %s", recording_path->s);
}
} else if (recording != NULL) {
recording->recording_path = NULL;
recording->recording_pd = NULL;
recording->recording_pdumper = NULL;
ilog(LOG_INFO, "Writing recording file: %s", recording_path);
}
return recording_path;
@ -343,75 +413,296 @@ str *recording_setup_file(struct recording *recording, str callid) {
/**
* Flushes PCAP file, closes the dumper and descriptors, and frees object memory.
*/
void recording_finish_file(struct recording *recording) {
if (recording->recording_pdumper != NULL) {
pcap_dump_flush(recording->recording_pdumper);
pcap_dump_close(recording->recording_pdumper);
free(recording->recording_path->s);
free(recording->recording_path);
static void pcap_recording_finish_file(struct recording *recording) {
if (recording->pcap.recording_pdumper != NULL) {
pcap_dump_flush(recording->pcap.recording_pdumper);
pcap_dump_close(recording->pcap.recording_pdumper);
free(recording->pcap.recording_path);
}
if (recording->recording_pd != NULL) {
pcap_close(recording->recording_pd);
if (recording->pcap.recording_pd != NULL) {
pcap_close(recording->pcap.recording_pd);
}
}
// "out" must be at least inp->len + MAX_PACKET_HEADER_LEN bytes
static unsigned int fake_ip_header(unsigned char *out, struct packet_stream *stream, const str *inp) {
endpoint_t *src_endpoint = &stream->advertised_endpoint;
endpoint_t *dst_endpoint = &stream->selected_sfd->socket.local;
unsigned int hdr_len =
endpoint_packet_header(out, src_endpoint, dst_endpoint, inp->len);
assert(hdr_len <= MAX_PACKET_HEADER_LEN);
// payload
memcpy(out + hdr_len, inp->s, inp->len);
return hdr_len + inp->len;
}
/**
* Write out a PCAP packet with payload string.
* A fair amount extraneous of packet data is spoofed.
*/
void stream_pcap_dump(pcap_dumper_t *pdumper, struct packet_stream *stream, str *s) {
endpoint_t src_endpoint = stream->advertised_endpoint;
endpoint_t dst_endpoint = stream->selected_sfd->socket.local;
// Wrap RTP in fake UDP packet header
// Right now, we spoof it all
u_int16_t udp_len = ((u_int16_t)s->len) + 8;
u_int16_t udp_header[4];
u_int16_t src_port = (u_int16_t) src_endpoint.port;
u_int16_t dst_port = (u_int16_t) dst_endpoint.port;
udp_header[0] = htons(src_port); // source port
udp_header[1] = htons(dst_port); // destination port
udp_header[2] = htons(udp_len); // packet length
udp_header[3] = 0; // checksum
// Wrap RTP in fake IP packet header
u_int8_t ip_header[20];
u_int16_t ip_total_length = udp_len + 20;
u_int16_t *ip_total_length_ptr = (u_int16_t*)(ip_header + 2);
u_int32_t *ip_src_addr = (u_int32_t*)(ip_header + 12);
u_int32_t *ip_dst_addr = (u_int32_t*)(ip_header + 16);
unsigned long src_ip = src_endpoint.address.u.ipv4.s_addr;
unsigned long dst_ip = dst_endpoint.address.u.ipv4.s_addr;
memset(ip_header, 0, 20);
ip_header[0] = 4 << 4; // IP version - 4 bits
ip_header[0] = ip_header[0] | 5; // Internet Header Length (IHL) - 4 bits
ip_header[1] = 0; // DSCP - 6 bits
ip_header[1] = 0; // ECN - 2 bits
*ip_total_length_ptr = htons(ip_total_length);
ip_header[4] = 0; ip_header[5] = 0 ; // Identification - 2 bytes
ip_header[6] = 0; // Flags - 3 bits
ip_header[7] = 0; // Fragment Offset - 13 bits
ip_header[8] = 64; // TTL - 1 byte
ip_header[9] = 17; // Protocol (defines protocol in data portion) - 1 byte
ip_header[10] = 0; ip_header[11] = 0; // Header Checksum - 2 bytes
*ip_src_addr = src_ip; // Source IP (set to localhost) - 4 bytes
*ip_dst_addr = dst_ip; // Destination IP (set to localhost) - 4 bytes
static void stream_pcap_dump(pcap_dumper_t *pdumper, struct packet_stream *stream, const str *s) {
if (!pdumper)
return;
unsigned char pkt[s->len + MAX_PACKET_HEADER_LEN];
unsigned int pkt_len = fake_ip_header(pkt, stream, s);
// Set up PCAP packet header
struct pcap_pkthdr header;
ZERO(header);
header.ts = g_now;
header.caplen = s->len + 28;
// This must be the same value we use in `pcap_open_dead`
header.len = s->len + 28;
// Copy all the headers and payload into a new string
unsigned char pkt_s[ip_total_length];
memcpy(pkt_s, ip_header, 20);
memcpy(pkt_s + 20, udp_header, 8);
memcpy(pkt_s + 28, s->s, s->len);
header.caplen = pkt_len;
header.len = pkt_len;
// Write the packet to the PCAP file
// Casting quiets compiler warning.
pcap_dump((unsigned char *)pdumper, &header, (unsigned char *)pkt_s);
pcap_dump((unsigned char *)pdumper, &header, pkt);
}
static void dump_packet_pcap(struct recording *recording, struct packet_stream *stream, const str *s) {
mutex_lock(&recording->pcap.recording_lock);
stream_pcap_dump(recording->pcap.recording_pdumper, stream, s);
recording->pcap.packet_num++;
mutex_unlock(&recording->pcap.recording_lock);
}
static void finish_pcap(struct call *call) {
pcap_recording_finish_file(call->recording);
pcap_meta_finish_file(call);
}
static void response_pcap(struct recording *recording, bencode_item_t *output) {
if (!recording->pcap.recording_path)
return;
bencode_item_t *recordings = bencode_dictionary_add_list(output, "recordings");
bencode_list_add_string(recordings, recording->pcap.recording_path);
}
void recording_finish(struct call *call) {
if (!call || !call->recording)
return;
struct recording *recording = call->recording;
_rm(finish, call);
free(recording->meta_prefix);
free(recording->escaped_callid);
free(recording->meta_filepath);
g_slice_free1(sizeof(*(recording)), recording);
call->recording = NULL;
}
static int open_proc_meta_file(struct recording *recording) {
int fd;
fd = open(recording->meta_filepath, O_WRONLY | O_APPEND | O_CREAT, 0666);
if (fd == -1) {
ilog(LOG_ERR, "Failed to open recording metadata file '%s' for writing: %s",
recording->meta_filepath, strerror(errno));
return -1;
}
return fd;
}
static int vappend_meta_chunk_iov(struct recording *recording, struct iovec *in_iov, int iovcnt,
unsigned int str_len, const char *label_fmt, va_list ap)
{
int fd = open_proc_meta_file(recording);
if (fd == -1)
return -1;
char label[128];
int lablen = vsnprintf(label, sizeof(label), label_fmt, ap);
char infix[128];
int inflen = snprintf(infix, sizeof(infix), "\n%u:\n", str_len);
// use writev for an atomic write
struct iovec iov[iovcnt + 3];
iov[0].iov_base = label;
iov[0].iov_len = lablen;
iov[1].iov_base = infix;
iov[1].iov_len = inflen;
memcpy(&iov[2], in_iov, iovcnt * sizeof(*iov));
iov[iovcnt + 2].iov_base = "\n\n";
iov[iovcnt + 2].iov_len = 2;
if (writev(fd, iov, iovcnt + 3) != (str_len + lablen + inflen + 2))
ilog(LOG_WARN, "writev return value incorrect");
close(fd); // this triggers the inotify
return 0;
}
static int append_meta_chunk_iov(struct recording *recording, struct iovec *iov, int iovcnt,
unsigned int str_len, const char *label_fmt, ...)
__attribute__((format(printf,5,6)));
static int append_meta_chunk_iov(struct recording *recording, struct iovec *iov, int iovcnt,
unsigned int str_len, const char *label_fmt, ...)
{
va_list ap;
va_start(ap, label_fmt);
int ret = vappend_meta_chunk_iov(recording, iov, iovcnt, str_len, label_fmt, ap);
va_end(ap);
return ret;
}
static int append_meta_chunk(struct recording *recording, const char *buf, unsigned int buflen,
const char *label_fmt, ...)
__attribute__((format(printf,4,5)));
static int append_meta_chunk(struct recording *recording, const char *buf, unsigned int buflen,
const char *label_fmt, ...)
{
struct iovec iov;
iov.iov_base = (void *) buf;
iov.iov_len = buflen;
va_list ap;
va_start(ap, label_fmt);
int ret = vappend_meta_chunk_iov(recording, &iov, 1, buflen, label_fmt, ap);
va_end(ap);
return ret;
}
#define append_meta_chunk_str(r, str, f...) append_meta_chunk(r, (str)->s, (str)->len, f)
#define append_meta_chunk_s(r, str, f...) append_meta_chunk(r, (str), strlen(str), f)
static void proc_init(struct call *call) {
struct recording *recording = call->recording;
recording->proc.call_idx = UNINIT_IDX;
if (!kernel.is_open) {
ilog(LOG_WARN, "Call recording through /proc interface requested, but kernel table not open");
return;
}
recording->proc.call_idx = kernel_add_call(recording->meta_prefix);
if (recording->proc.call_idx == UNINIT_IDX) {
ilog(LOG_ERR, "Failed to add call to kernel recording interface: %s", strerror(errno));
return;
}
ilog(LOG_DEBUG, "kernel call idx is %u", recording->proc.call_idx);
recording->meta_filepath = file_path_str(recording->meta_prefix, "/", ".meta");
unlink(recording->meta_filepath); // start fresh XXX good idea?
append_meta_chunk_str(recording, &call->callid, "CALL-ID");
append_meta_chunk_s(recording, recording->meta_prefix, "PARENT");
}
static void sdp_before_proc(struct recording *recording, const str *sdp, struct call_monologue *ml,
enum call_opmode opmode)
{
append_meta_chunk_str(recording, &ml->tag, "TAG %u", ml->unique_id);
append_meta_chunk_str(recording, sdp,
"SDP from %u before %s", ml->unique_id, get_opmode_text(opmode));
}
static void sdp_after_proc(struct recording *recording, struct iovec *sdp_iov, int iovcnt,
unsigned int str_len, struct call_monologue *ml, enum call_opmode opmode)
{
append_meta_chunk_iov(recording, sdp_iov, iovcnt, str_len,
"SDP from %u after %s", ml->unique_id, get_opmode_text(opmode));
}
static void finish_proc(struct call *call) {
struct recording *recording = call->recording;
if (!kernel.is_open)
return;
if (recording->proc.call_idx != UNINIT_IDX)
kernel_del_call(recording->proc.call_idx);
unlink(recording->meta_filepath);
}
static void init_stream_proc(struct packet_stream *stream) {
stream->recording.proc.stream_idx = UNINIT_IDX;
}
static void setup_stream_proc(struct packet_stream *stream) {
struct call_media *media = stream->media;
struct call_monologue *ml = media->monologue;
struct call *call = stream->call;
struct recording *recording = call->recording;
char buf[128];
int len;
if (!recording)
return;
if (!kernel.is_open)
return;
if (stream->recording.proc.stream_idx != UNINIT_IDX)
return;
len = snprintf(buf, sizeof(buf), "TAG %u MEDIA %u COMPONENT %u FLAGS %u",
ml->unique_id, media->index, stream->component,
stream->ps_flags);
append_meta_chunk(recording, buf, len, "STREAM %u details", stream->unique_id);
len = snprintf(buf, sizeof(buf), "tag-%u-media-%u-component-%u-%s-id-%u",
ml->unique_id, media->index, stream->component,
(PS_ISSET(stream, RTCP) && !PS_ISSET(stream, RTP)) ? "RTCP" : "RTP",
stream->unique_id);
stream->recording.proc.stream_idx = kernel_add_intercept_stream(recording->proc.call_idx, buf);
if (stream->recording.proc.stream_idx == UNINIT_IDX) {
ilog(LOG_ERR, "Failed to add stream to kernel recording interface: %s", strerror(errno));
return;
}
ilog(LOG_DEBUG, "kernel stream idx is %u", stream->recording.proc.stream_idx);
append_meta_chunk(recording, buf, len, "STREAM %u interface", stream->unique_id);
}
static void dump_packet_proc(struct recording *recording, struct packet_stream *stream, const str *s) {
if (stream->recording.proc.stream_idx == UNINIT_IDX)
return;
struct rtpengine_message *remsg;
unsigned char pkt[sizeof(*remsg) + s->len + MAX_PACKET_HEADER_LEN];
remsg = (void *) pkt;
ZERO(*remsg);
remsg->cmd = REMG_PACKET;
//remsg->u.packet.call_idx = stream->call->recording->proc.call_idx; // unused
remsg->u.packet.stream_idx = stream->recording.proc.stream_idx;
unsigned int pkt_len = fake_ip_header(remsg->data, stream, s);
pkt_len += sizeof(*remsg);
int ret = write(kernel.fd, pkt, pkt_len);
if (ret < 0)
ilog(LOG_ERR, "Failed to submit packet to kernel intercepted stream: %s", strerror(errno));
}
static void kernel_info_proc(struct packet_stream *stream, struct rtpengine_target_info *reti) {
if (!stream->call->recording)
return;
if (stream->recording.proc.stream_idx == UNINIT_IDX)
return;
ilog(LOG_DEBUG, "enabling kernel intercept with stream idx %u", stream->recording.proc.stream_idx);
reti->do_intercept = 1;
reti->intercept_stream_idx = stream->recording.proc.stream_idx;
}
static void meta_chunk_proc(struct recording *recording, const char *label, const str *data) {
append_meta_chunk_str(recording, data, "%s", label);
}

@ -11,27 +11,95 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include "call.h"
#include <pcap.h>
#include "str.h"
#include "aux.h"
#include "bencode.h"
struct recording {
str *meta_filepath;
struct packet_stream;
struct call;
enum call_opmode;
struct rtpengine_target_info;
struct call_monologue;
struct recording_pcap {
FILE *meta_fp;
str *metadata;
pcap_t *recording_pd;
pcap_dumper_t *recording_pdumper;
uint64_t packet_num;
str *recording_path;
char *recording_path;
mutex_t recording_lock;
};
struct recording_proc {
unsigned int call_idx;
};
struct recording_stream_proc {
unsigned int stream_idx;
};
struct recording {
union {
struct recording_pcap pcap;
struct recording_proc proc;
};
str metadata; // from controlling daemon
char *escaped_callid; // call-id with dangerous characters escaped
char *meta_prefix; // escaped call-id plus random suffix
char *meta_filepath; // full file path
};
struct recording_stream {
union {
struct recording_stream_proc proc;
};
};
struct recording_method {
const char *name;
int kernel_support;
int (*create_spool_dir)(const char *);
void (*init_struct)(struct call *);
void (*sdp_before)(struct recording *, const str *, struct call_monologue *, enum call_opmode);
void (*sdp_after)(struct recording *, struct iovec *, int, unsigned int, struct call_monologue *,
enum call_opmode);
void (*meta_chunk)(struct recording *, const char *, const str *);
void (*dump_packet)(struct recording *, struct packet_stream *sink, const str *s);
void (*finish)(struct call *);
void (*response)(struct recording *, bencode_item_t *);
void (*init_stream_struct)(struct packet_stream *);
void (*setup_stream)(struct packet_stream *);
void (*stream_kernel_info)(struct packet_stream *, struct rtpengine_target_info *);
};
extern const struct recording_method *selected_recording_method;
#define _rm_ret(call, args...) selected_recording_method->call(args)
#define _rm(call, args...) do { \
if (selected_recording_method->call) \
selected_recording_method->call(args); \
} while (0)
#define _rm_chk(call, recording, ...) do { \
if (recording) \
_rm(call, recording, ##__VA_ARGS__); \
} while (0)
/**
* Initialize RTP Engine filesystem settings and structure.
* Check for or create the RTP Engine spool directory.
*/
void recording_fs_init(char *spooldir);
void recording_fs_init(const char *spooldir, const char *method);
/**
*
@ -43,7 +111,11 @@ void recording_fs_init(char *spooldir);
*
* Returns a boolean for whether or not the call is being recorded.
*/
int detect_setup_recording(struct call *call, str recordcall);
void detect_setup_recording(struct call *call, const str *recordcall);
void recording_start(struct call *call, const char *);
void recording_stop(struct call *call);
/**
* Create a call metadata file in a temporary location.
@ -77,13 +149,15 @@ int detect_setup_recording(struct call *call, str recordcall);
* ${CALL_ID}-${RAND-HEX}.pcap
*
*/
str *meta_setup_file(struct recording *recording, str callid);
//str *meta_setup_file(struct recording *recording, str callid);
/**
* Write out a block of SDP to the metadata file.
*/
ssize_t meta_write_sdp(FILE *meta_fp, struct iovec *sdp_iov, int iovcnt,
uint64_t packet_num, enum call_opmode opmode);
//ssize_t meta_write_sdp(struct recording *, struct iovec *sdp_iov, int iovcnt,
// enum call_opmode opmode);
#define meta_write_sdp_before(args...) _rm(sdp_before, args)
#define meta_write_sdp_after(args...) _rm(sdp_after, args)
/**
* Writes metadata to metafile, closes file, and moves it to finished location.
@ -91,26 +165,29 @@ ssize_t meta_write_sdp(FILE *meta_fp, struct iovec *sdp_iov, int iovcnt,
*
* Metadata files are moved to ${RECORDING_DIR}/metadata/
*/
int meta_finish_file(struct call *call);
/**
* Generate a random PCAP filepath to write recorded RTP stream.
* Returns path to created file.
*
* Files go in ${RECORDING_DIR}/pcaps, and are named like:
* ${CALL_ID}-${RAND-HEX}.pcap
*/
str *recording_setup_file(struct recording *recording, str callid);
// int meta_finish_file(struct call *call);
/**
* Flushes PCAP file, closes the dumper and descriptors, and frees object memory.
*/
void recording_finish_file(struct recording *recording);
// void recording_finish_file(struct recording *recording);
// combines the two calls above
void recording_finish(struct call *);
/**
* Write out a PCAP packet with payload string.
* A fair amount extraneous of packet data is spoofed.
*/
void stream_pcap_dump(pcap_dumper_t *pdumper, struct packet_stream *sink, str *s);
// void dump_packet(struct recording *, struct packet_stream *, str *s);
#define dump_packet(args...) _rm_chk(dump_packet, args)
#define recording_setup_stream(args...) _rm(setup_stream, args)
#define recording_init_stream(args...) _rm(init_stream_struct, args)
#define recording_stream_kernel_info(args...) _rm(stream_kernel_info, args)
#define recording_meta_chunk(args...) _rm(meta_chunk, args)
#define recording_response(args...) _rm(response, args)
#endif

@ -17,6 +17,7 @@
#include "str.h"
#include "crypto.h"
#include "dtls.h"
#include "recording.h"
#include "hiredis/hiredis.h"
#include "hiredis/async.h"
#include "hiredis/adapters/libevent.h"
@ -1390,6 +1391,20 @@ static int redis_link_maps(struct redis *r, struct call *c, struct redis_list *m
}
static void redis_restore_recording(struct call *c, struct redis_hash *call) {
str s;
// presence of this key determines whether we were recording at all
if (redis_hash_get_str(&s, call, "recording_meta_prefix"))
return;
recording_start(c, s.s);
if (!redis_hash_get_str(&s, call, "recording_metadata"))
call_str_cpy(c, &c->recording->metadata, &s);
}
static void redis_restore_call(struct redis *r, struct callmaster *m, const redisReply *id, enum call_type type) {
struct redis_hash call;
struct redis_list tags, sfds, streams, medias, maps;
@ -1478,6 +1493,8 @@ static void redis_restore_call(struct redis *r, struct callmaster *m, const redi
if (redis_link_maps(r, c, &maps, &sfds))
goto err6;
redis_restore_recording(c, &call);
err = NULL;
obj_put(c);
@ -1654,6 +1671,17 @@ static void redis_update_dtls_fingerprint(struct redis *r, const char *pref, con
S_LEN(f->digest, sizeof(f->digest)));
}
static void redis_update_recording(struct redis *r, struct call *c) {
struct recording *rec;
if (!(rec = c->recording))
return;
redis_pipe(r, "HMSET call-"PB" recording_metadata "PB" recording_meta_prefix %s ",
STR(&c->callid),
STR(&rec->metadata), rec->meta_prefix);
}
/*
@ -1729,6 +1757,8 @@ void redis_update(struct call *c, struct redis *r) {
c->redis_hosted_db);
/* XXX DTLS cert?? */
redis_update_recording(r, c);
redis_pipe(r, "DEL sfd-"PB"-0", STR(&c->callid));
for (l = c->stream_fds.head; l; l = l->next) {

@ -2,6 +2,9 @@
#include <glib.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/udp.h>
#include "str.h"
#include "media_socket.h"
#include "xt_RTPENGINE.h"
@ -38,6 +41,10 @@ static void __ip4_endpoint2kernel(struct re_address *, const endpoint_t *);
static void __ip6_endpoint2kernel(struct re_address *, const endpoint_t *);
static void __ip4_kernel2endpoint(endpoint_t *ep, const struct re_address *ra);
static void __ip6_kernel2endpoint(endpoint_t *ep, const struct re_address *ra);
static unsigned int __ip4_packet_header(unsigned char *, const endpoint_t *, const endpoint_t *,
unsigned int);
static unsigned int __ip6_packet_header(unsigned char *, const endpoint_t *, const endpoint_t *,
unsigned int);
@ -75,6 +82,7 @@ static struct socket_family __socket_families[__SF_LAST] = {
.error = __ip_error,
.endpoint2kernel = __ip4_endpoint2kernel,
.kernel2endpoint = __ip4_kernel2endpoint,
.packet_header = __ip4_packet_header,
},
[SF_IP6] = {
.af = AF_INET6,
@ -102,6 +110,7 @@ static struct socket_family __socket_families[__SF_LAST] = {
.error = __ip_error,
.endpoint2kernel = __ip6_endpoint2kernel,
.kernel2endpoint = __ip6_kernel2endpoint,
.packet_header = __ip6_packet_header,
},
};
@ -361,6 +370,55 @@ static void __ip4_kernel2endpoint(endpoint_t *ep, const struct re_address *ra) {
static void __ip6_kernel2endpoint(endpoint_t *ep, const struct re_address *ra) {
memcpy(&ep->address.u.ipv6, ra->u.ipv6, sizeof(ep->address.u.ipv6));
}
static unsigned int __udp_packet_header(unsigned char *out, unsigned int src, unsigned int dst,
unsigned int payload_len)
{
struct udphdr *udp = (void *) out;
ZERO(*udp);
udp->source = htons(src);
udp->dest = htons(dst);
udp->len = htons(sizeof(*udp) + payload_len);
return sizeof(*udp);
}
static unsigned int __ip4_packet_header(unsigned char *out, const endpoint_t *src, const endpoint_t *dst,
unsigned int payload_len)
{
struct iphdr *iph = (void *) out;
unsigned char *nxt = (void *) out + sizeof(*iph);
unsigned int udp_len = __udp_packet_header(nxt, src->port, dst->port, payload_len);
ZERO(*iph);
iph->ihl = sizeof(*iph) >> 2; // normally 5 ~ 20 bytes
iph->version = 4;
iph->tot_len = htons(sizeof(*iph) + udp_len + payload_len);
iph->ttl = 64;
iph->protocol = 17; // UDP
iph->saddr = src->address.u.ipv4.s_addr;
iph->daddr = dst->address.u.ipv4.s_addr;
return sizeof(*iph) + udp_len;
}
static unsigned int __ip6_packet_header(unsigned char *out, const endpoint_t *src, const endpoint_t *dst,
unsigned int payload_len)
{
struct ip6_hdr *iph = (void *) out;
unsigned char *nxt = (void *) out + sizeof(*iph);
unsigned int udp_len = __udp_packet_header(nxt, src->port, dst->port, payload_len);
ZERO(*iph);
iph->ip6_vfc = 0x60; // version 6;
//iph->ip6_flow = htonl(0x60000000); // version 6
iph->ip6_plen = htons(udp_len + payload_len);
iph->ip6_nxt = 17; // UDP
iph->ip6_hlim = 64;
iph->ip6_src = src->address.u.ipv6;
iph->ip6_dst = dst->address.u.ipv6;
return sizeof(*iph) + udp_len;
}

@ -34,6 +34,10 @@ typedef const struct socket_family sockfamily_t;
#define MAX_PACKET_HEADER_LEN 48 // 40 bytes IPv6 + 8 bytes UDP
struct local_intf;
@ -68,6 +72,8 @@ struct socket_family {
int (*error)(socket_t *);
void (*endpoint2kernel)(struct re_address *, const endpoint_t *);
void (*kernel2endpoint)(endpoint_t *, const struct re_address *);
unsigned int (*packet_header)(unsigned char *, const endpoint_t *, const endpoint_t *,
unsigned int);
};
struct socket_address {
sockfamily_t *family;
@ -238,6 +244,8 @@ INLINE int ipv46_any_convert(endpoint_t *ep) {
return 1;
}
#define endpoint_packet_header(o, src, args...) (src)->address.family->packet_header(o, src, args)
socktype_t *get_socket_type(const str *s);

@ -1,4 +1,5 @@
#include "str.h"
#include "aux.h"
#include <assert.h>
#include <stdarg.h>
@ -35,46 +36,15 @@ void str_slice_free(void *p) {
}
/**
* Generates a random string sandwiched between affixes.
* Will create the char string for you. Don't forget to clean up!
*/
char *rand_affixed_str(char *prefix, int num_bytes, char *suffix) {
int rand_len = num_bytes*2 + 1;
char rand_affix[rand_len];
int prefix_len = strlen(prefix);
int suffix_len = strlen(suffix);
char *full_path = calloc(rand_len + prefix_len + suffix_len, sizeof(char));
rand_hex_str(rand_affix, num_bytes);
snprintf(full_path, rand_len+prefix_len, "%s%s", prefix, rand_affix);
snprintf(full_path + rand_len+prefix_len-1, suffix_len+1, "%s", suffix);
return full_path;
}
/**
* Generates a random hexadecimal string representing n random bytes.
* rand_str length must be 2*num_bytes + 1.
*/
char *rand_hex_str(char *rand_str, int num_bytes) {
char rand_tmp[3];
u_int8_t rand_byte;
int i, n;
// We might convert an int to a hex string shorter than 2 digits.
// This causes those strings to have leading '0' characters.
for (i=0; i<num_bytes*2 + 1; i++) {
rand_str[i] = '0';
}
for (i=0; i<num_bytes; i++) {
// Determine the length of the hex byte string.
// If less than two, offset by 2-len to pad with prefix zeroes.
rand_byte = (u_int8_t)rand();
snprintf(rand_tmp, 3, "%x", rand_byte);
n = strlen(rand_tmp);
snprintf(rand_str + i*2 + (2-n), 3, "%s", rand_tmp);
rand_str[i*2 + 2] = '0';
unsigned char rand_tmp[num_bytes];
random_string(rand_tmp, num_bytes);
for (int i = 0; i < num_bytes; i++) {
sprintf(&rand_str[i * 2], "%02x", rand_tmp[i]);
}
rand_str[num_bytes*2] = '\0';
return rand_str;
}

@ -293,8 +293,6 @@ INLINE int str_token(str *new_token, str *ori_and_remainder, int sep) {
}
/* Generates a random string sandwiched between affixes. */
char *rand_affixed_str(char *prefix, int num_bytes, char *suffix);
/* Generates a hex string representing n random bytes. len(rand_str) = 2*num_bytes + 1 */
char *rand_hex_str(char *rand_str, int num_bytes);

@ -41,5 +41,6 @@ TABLE=0
# HOMER_PROTOCOL=udp
# HOMER_ID=2001
# RECORDING_DIR=/var/spool/rtpengine/
# RECORDING_METHOD=proc
# LOG_STDERR="no"
# DTLS_PASSIVE="no"

@ -94,7 +94,14 @@ OPTIONS="$OPTIONS --table=$TABLE"
[ -z "$HOMER" ] || OPTIONS="$OPTIONS --homer=$HOMER"
[ -z "$HOMER_PROTOCOL" ] || OPTIONS="$OPTIONS --homer-protocol=$HOMER_PROTOCOL"
[ -z "$HOMER_ID" ] || OPTIONS="$OPTIONS --homer-id=$HOMER_ID"
[ -z "$RECORDING_DIR" ] || OPTIONS="$OPTIONS --recording-dir=$RECORDING_DIR"
if [ ! -z "$RECORDING_DIR" ]; then
OPTIONS="$OPTIONS --recording-dir=$RECORDING_DIR"
if [ ! -d "$RECORDING_DIR" ]; then
mkdir "$RECORDING_DIR" 2> /dev/null
chmod 700 "$RECORDING_DIR" 2> /dev/null
fi
fi
[ -z "$RECORDING_METHOD" ] || OPTIONS="$OPTIONS --recording-method=$RECORDING_METHOD"
[ -z "$DTLS_PASSIVE" -o \( "$DTLS_PASSIVE" != "yes" -a "$DTLS_PASSIVE" != "1" \) ] || OPTIONS="$OPTIONS --dtls-passive"
if test "$FORK" = "no" ; then

File diff suppressed because it is too large Load Diff

@ -8,7 +8,7 @@
struct xt_rtpengine_info {
u_int32_t id;
unsigned int id;
};
struct rtpengine_stats {
@ -83,6 +83,7 @@ struct rtpengine_target_info {
struct re_address dst_addr;
struct re_address mirror_addr;
unsigned int intercept_stream_idx;
struct rtpengine_srtp decrypt;
struct rtpengine_srtp encrypt;
@ -96,18 +97,58 @@ struct rtpengine_target_info {
dtls:1,
stun:1,
rtp:1,
rtp_only:1;
rtp_only:1,
do_intercept:1;
};
struct rtpengine_call_info {
unsigned int call_idx;
char call_id[256];
};
struct rtpengine_stream_info {
unsigned int call_idx;
unsigned int stream_idx;
unsigned int max_packets;
char stream_name[256];
};
struct rtpengine_packet_info {
unsigned int call_idx;
unsigned int stream_idx;
};
struct rtpengine_message {
enum {
MMG_NOOP = 1,
MMG_ADD,
MMG_DEL,
MMG_UPDATE
REMG_NOOP = 1,
/* target_info: */
REMG_ADD,
REMG_DEL,
REMG_UPDATE,
/* call_info: */
REMG_ADD_CALL,
REMG_DEL_CALL,
/* stream_info: */
REMG_ADD_STREAM,
REMG_DEL_STREAM,
/* packet_info: */
REMG_PACKET,
__REMG_LAST
} cmd;
union {
struct rtpengine_target_info target;
struct rtpengine_call_info call;
struct rtpengine_stream_info stream;
struct rtpengine_packet_info packet;
} u;
unsigned char data[];
};
struct rtpengine_list_entry {

@ -5,22 +5,26 @@ use warnings;
use Socket;
use Socket6;
my %cmds = (noop => 1, add => 2, delete => 3, update => 4);
my %cmds = (noop => 1, add => 2, delete => 3, update => 4, add_call => 5, del_call => 6, add_stream => 7, del_stream => 8, packet => 9);
my %ciphers = ('null' => 1, 'aes-cm' => 2, 'aes-f8' => 3);
my %hmacs = ('null' => 1, 'hmac-sha1' => 2);
$| = 1;
open(F, "> /proc/rtpengine/0/control") or die;
open(F, "+> /proc/rtpengine/0/control") or die;
{
my $x = select(F);
$| = 1;
select($x);
}
sub mp_address {
sub re_address {
my ($fam, $addr, $port) = @_;
if ($fam eq 'inet') {
$fam //= '';
$addr //= '';
$port //= 0;
if ($fam eq 'inet' || $fam eq 'inet4') {
return pack('V a4 a12 v v', 2, inet_aton($addr), '', $port, 0);
}
if ($fam eq 'inet6') {
@ -32,76 +36,424 @@ sub mp_address {
die;
}
sub mp_srtp {
sub re_srtp {
my ($h) = @_;
no warnings;
return pack('VV a16 a16 QQ VV', $ciphers{$$h{cipher}}, $hmacs{$$h{hmac}},
return pack('VV a16 a16 a256 Q VV', $ciphers{$$h{cipher}}, $hmacs{$$h{hmac}},
@$h{qw(master_key master_salt mki last_index auth_tag_len mki_len)});
use warnings;
}
sub rtpengine_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, $decrypt, $encrypt) = @_;
my ($cmd, %args) = @_;
my $ret = '';
# amd64 alignment
$ret .= pack('VV vv', $cmds{$cmd}, 0, $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('V', 0);
$ret .= mp_srtp($decrypt);
$ret .= mp_srtp($encrypt);
$ret .= pack('C CSI', $tos, 0, 0, 0);
$ret .= pack('VV', $cmds{$cmd}, 0);
#print(length($ret) . "\n");
$ret .= re_address(@{$args{local_addr}}, $args{local_port});
#print(length($ret) . "\n");
$ret .= re_address(@{$args{expected_addr}}, $args{expected_port});
#print(length($ret) . "\n");
$ret .= pack('V', $args{mismatch} // 0);
#print(length($ret) . "\n");
$ret .= re_address(@{$args{src_addr}}, $args{src_port});
#print(length($ret) . "\n");
$ret .= re_address(@{$args{dst_addr}}, $args{dst_port});
#print(length($ret) . "\n");
$ret .= re_address(@{$args{mirror_addr}}, $args{mirror_port});
#print(length($ret) . "\n");
$ret .= pack('V', $args{stream_idx} // 0);
#print(length($ret) . "\n");
$ret .= re_srtp($args{decrypt});
#print(length($ret) . "\n");
$ret .= re_srtp($args{encrypt});
#print(length($ret) . "\n");
$ret .= pack('V', $args{ssrc} // 0);
#print(length($ret) . "\n");
$ret .= pack('CCCCCCCCCCCCCCCC V', 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0);
#print(length($ret) . "\n");
$ret .= pack('C CvV', $args{tos} // 0, $args{flags} // 0, 0, 0);
#print(length($ret) . "\n");
return $ret;
}
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 = ('', '', '');
sub rtpengine_message_call {
my ($cmd, $idx, $callid) = @_;
my $ret = '';
# amd64 alignment
$ret .= pack('VV V a256', $cmds{$cmd}, 0, $idx, $callid // '');
while (length($ret) < 792) {
$ret .= pack('v', 0);
}
return $ret;
}
sub rtpengine_message_stream {
my ($cmd, $call_idx, $stream_idx, $stream_name, $max_packets) = @_;
my $ret = '';
# amd64 alignment
$ret .= pack('VV VVV a256', $cmds{$cmd}, 0, $call_idx, $stream_idx, $max_packets // 0, $stream_name // '');
while (length($ret) < 792) {
$ret .= pack('v', 0);
}
return $ret;
}
sub rtpengine_message_packet {
my ($cmd, $call_idx, $stream_idx, $data) = @_;
my $ret = '';
# amd64 alignment
$ret .= pack('VV VV', $cmds{$cmd}, 0, $call_idx, $stream_idx);
while (length($ret) < 792) {
$ret .= pack('v', 0);
}
$ret .= $data;
return $ret;
}
my $sleep = 2;
my @local = qw(inet4 192.168.1.194);
my @src = qw(inet 192.168.1.194);
my @dst = qw(inet 192.168.1.90);
#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 $dec = {cipher => 'null', hmac => 'null'};
my $enc = {cipher => 'null', hmac => 'null'};
print("add 9876 -> 1234/6543\n");
syswrite(F, rtpengine_message('add', 9876, @src, 1234, @dst, 6543, @nul, 184, $dec, $enc));
sleep($sleep);
my $ret;
my $msg;
print("add fail\n");
syswrite(F, rtpengine_message('add', 9876, @src, 1234, @dst, 6543, @dst, 6789, 184, $dec, $enc));
sleep($sleep);
# print("add 9876 -> 1234/6543\n");
# $ret = syswrite(F, rtpengine_message('add', local_addr => \@local, local_port => 9876, src_addr => \@src, src_port => 1234, dst_addr => \@dst, dst_port => 6543, tos => 184, decrypt => $dec, encrypt => $enc)) // '-';
# print("ret = $ret, code = $!\n");
# sleep($sleep);
print("update 9876 -> 1234/6543 & 6789\n");
syswrite(F, rtpengine_message('update', 9876, @src, 1234, @dst, 6543, @dst, 6789, 184, $dec, $enc));
sleep($sleep);
# print("add fail\n");
# $ret = syswrite(F, rtpengine_message('add', local_addr => \@local, local_port => 9876, src_addr => \@src, src_port => 1234, dst_addr => \@dst, dst_port => 6543, mirror_addr => \@dst, mirror_port => 6789, tos => 184, decrypt => $dec, encrypt => $enc)) // '-';
# print("ret = $ret, code = $!\n");
# sleep($sleep);
print("update 9876 -> 2345/7890 & 4321\n");
syswrite(F, rtpengine_message('update', 9876, @src, 2345, @dst, 7890, @dst, 4321, 184, $dec, $enc));
sleep($sleep);
# print("update 9876 -> 1234/6543 & 6789\n");
# $ret = syswrite(F, rtpengine_message('update', local_addr => \@local, local_port => 9876, src_addr => \@src, src_port => 1234, dst_addr => \@dst, dst_port => 6543, mirror_addr => \@dst, mirror_port => 6789, tos => 184, decrypt => $dec, encrypt => $enc)) // '-';
# print("ret = $ret, code = $!\n");
# sleep($sleep);
print("add fail\n");
syswrite(F, rtpengine_message('add', 9876, @src, 1234, @dst, 6543, @dst, 6789, 184, $dec, $enc));
sleep($sleep);
# print("update 9876 -> 2345/7890 & 4321\n");
# $ret = syswrite(F, rtpengine_message('update', local_addr => \@local, local_port => 9876, src_addr => \@src, src_port => 2345, dst_addr => \@dst, dst_port => 7890, mirror_addr => \@dst, mirror_port => 4321, tos => 184, decrypt => $dec, encrypt => $enc)) // '-';
# print("ret = $ret, code = $!\n");
# sleep($sleep);
print("update 9876 -> 1234/6543\n");
syswrite(F, rtpengine_message('update', 9876, @src, 1234, @dst, 6543, @nul, 184, $dec, $enc));
sleep($sleep);
# print("add fail\n");
# $ret = syswrite(F, rtpengine_message('add', local_addr => \@local, local_port => 9876, src_addr => \@src, src_port => 1234, dst_addr => \@dst, dst_port => 6543, mirror_addr => \@dst, mirror_port => 6789, tos => 184, decrypt => $dec, encrypt => $enc)) // '-';
# print("ret = $ret, code = $!\n");
# sleep($sleep);
print("delete\n");
syswrite(F, rtpengine_message('delete', 9876, @nul, @nul, @nul, 0, $dec, $enc));
sleep($sleep);
# print("update 9876 -> 1234/6543\n");
# $ret = syswrite(F, rtpengine_message('update', local_addr => \@local, local_port => 9876, src_addr => \@src, src_port => 1234, dst_addr => \@dst, dst_port => 6543, tos => 184, decrypt => $dec, encrypt => $enc)) // '-';
# print("ret = $ret, code = $!\n");
# sleep($sleep);
print("delete fail\n");
syswrite(F, rtpengine_message('delete', 9876, @nul, @nul, @nul, 0, $dec, $enc));
sleep($sleep);
# print("delete\n");
# $ret = syswrite(F, rtpengine_message('delete', local_addr => \@local, local_port => 9876, decrypt => $dec, encrypt => $enc)) // '-';
# print("ret = $ret, code = $!\n");
# sleep($sleep);
# print("delete fail\n");
# $ret = syswrite(F, rtpengine_message('delete', local_addr => \@local, local_port => 9876, decrypt => $dec, encrypt => $enc)) // '-';
# print("ret = $ret, code = $!\n");
# sleep($sleep);
# print("update fail\n");
# $ret = syswrite(F, rtpengine_message('update', local_addr => \@local, local_port => 9876, src_addr => \@src, src_port => 1234, dst_addr => \@dst, dst_port => 6543, tos => 184, decrypt => $dec, encrypt => $enc)) // '-';
# print("ret = $ret, code = $!\n");
# sleep($sleep);
if (0) {
my (@calls, @streams);
my $runs = 100;
while ($runs >= 0 || @calls || @streams) {
print("$runs to go...\n");
my $op = rand() > .3 ? 'add' : 'del';
$runs < 0 and $op = 'del';
my $which = rand() > .6 ? 'call' : 'stream';
if ($op eq 'add' && $which eq 'stream' && !@calls) {
# can't add stream without call
$which = 'call';
}
if ($op eq 'del' && $which eq 'stream' && !@streams) {
# can't del stream if there aren't any
$which = 'call';
}
if ($op eq 'del' && $which eq 'call' && !@calls) {
# can't del call if there aren't any
$op = 'add';
}
if ($op eq 'add' && $which eq 'call') {
my $name = rand();
print("creating call $name\n");
$msg = rtpengine_message_call('add_call', 0, $name);
$ret = sysread(F, $msg, length($msg)) // '-';
#print("reply: " . unpack("H*", $msg) . "\n");
print("ret = $ret, code = $!\n");
my (undef, undef, $idx) = unpack("VV V a256", $msg);
print("index is $idx\n");
push(@calls, $idx);
sleep($sleep);
}
if ($op eq 'add' && $which eq 'stream') {
my $call = $calls[rand(@calls)];
my $name = rand();
print("creating stream $name under call idx $call\n");
$msg = rtpengine_message_stream('add_stream', $call, 0, $name);
$ret = sysread(F, $msg, length($msg)) // '-';
#print("reply: " . unpack("H*", $msg) . "\n");
print("ret = $ret, code = $!\n");
my (undef, undef, undef, $idx) = unpack("VV VV a256", $msg);
print("index is $idx\n");
push(@streams, [$call, $idx]);
sleep($sleep);
}
if ($op eq 'del' && $which eq 'call') {
my $arridx = int(rand(@calls));
my $call = $calls[$arridx];
print("deleting call idx $call\n");
$msg = rtpengine_message_call('del_call', $call);
$ret = syswrite(F, $msg) // '-';
#print("ret = $ret, code = $!, reply: " . unpack("H*", $msg) . "\n");
print("ret = $ret, code = $!\n");
splice(@calls, $arridx, 1);
# kill streams linked to call
my @to_del;
for my $sidx (0 .. $#streams) {
my $s = $streams[$sidx];
$s->[0] == $call or next;
print("stream idx $s->[1] got nuked\n");
push(@to_del, $sidx);
}
my $offset = 0;
while (@to_del) {
my $i = shift(@to_del);
splice(@streams, $i - $offset, 1);
$offset++;
}
sleep($sleep);
}
if ($op eq 'del' && $which eq 'stream') {
my $arridx = int(rand(@streams));
my $stream = $streams[$arridx];
print("deleting stream idx $stream->[1] (call $stream->[0])\n");
$msg = rtpengine_message_stream('del_stream', $stream->[0], $stream->[1]);
$ret = syswrite(F, $msg) // '-';
#print("ret = $ret, code = $!, reply: " . unpack("H*", $msg) . "\n");
print("ret = $ret, code = $!\n");
splice(@streams, $arridx, 1);
sleep($sleep);
}
for (1 .. rand(30)) {
@streams or last;
my $idx = $streams[rand(@streams)];
$idx = $idx->[1];
print("delivering a packet to $idx\n");
$msg = rtpengine_message_packet('packet', 0, $idx, 'packet data bla bla ' . rand() . "\n");
$ret = syswrite(F, $msg) // '-';
print("ret = $ret, code = $!\n");
sleep($sleep);
}
$runs--;
}
}
print("creating call\n");
$msg = rtpengine_message_call('add_call', 0, 'test call');
$ret = sysread(F, $msg, length($msg)) // '-';
#print("reply: " . unpack("H*", $msg) . "\n");
print("ret = $ret, code = $!\n");
my (undef, undef, $idx1) = unpack("VV V a256", $msg);
print("index is $idx1\n");
print("update fail\n");
syswrite(F, rtpengine_message('update', 9876, @src, 1234, @dst, 6543, @nul, 184, $dec, $enc));
sleep($sleep);
# print("creating identical call\n");
#
# $msg = rtpengine_message_call('add_call', 0, 'test call');
# $ret = sysread(F, $msg, length($msg)) // '-';
# #print("reply: " . unpack("H*", $msg) . "\n");
# print("ret = $ret, code = $!\n");
#
# my (undef, undef, $idx2) = unpack("VV V a256", $msg);
# print("index is $idx2\n");
#
# sleep($sleep);
# print("creating other call\n");
#
# $msg = rtpengine_message_call('add_call', 0, 'another test call');
# $ret = sysread(F, $msg, length($msg)) // '-';
# #print("reply: " . unpack("H*", $msg) . "\n");
# print("ret = $ret, code = $!\n");
#
# my (undef, undef, $idx3) = unpack("VV V a256", $msg);
# print("index is $idx3\n");
#
# sleep($sleep);
for my $exp (0 .. 1000) {
print("creating a stream\n");
$msg = rtpengine_message_stream('add_stream', $idx1, 0, 'test stream ' . rand());
$ret = sysread(F, $msg, length($msg)) // '-';
#print("reply: " . unpack("H*", $msg) . "\n");
print("ret = $ret, code = $!\n");
my (undef, undef, undef, $sidx1) = unpack("VV VV a256", $msg);
print("index is $sidx1\n");
$sidx1 == $exp or die;
}
# print("creating a stream\n");
#
# $msg = rtpengine_message_stream('add_stream', $idx1, 0, 'test stream');
# $ret = sysread(F, $msg, length($msg)) // '-';
# #print("reply: " . unpack("H*", $msg) . "\n");
# print("ret = $ret, code = $!\n");
#
# my (undef, undef, undef, $sidx1) = unpack("VV VV a256", $msg);
# print("index is $sidx1\n");
#
# sleep($sleep);
# print("creating identical stream\n");
#
# $msg = rtpengine_message_stream('add_stream', $idx1, 0, 'test stream');
# $ret = sysread(F, $msg, length($msg)) // '-';
# #print("reply: " . unpack("H*", $msg) . "\n");
# print("ret = $ret, code = $!\n");
#
# my (undef, undef, undef, $sidx2) = unpack("VV VV a256", $msg);
# print("index is $sidx2\n");
#
# sleep($sleep);
# print("creating different stream\n");
#
# $msg = rtpengine_message_stream('add_stream', $idx3, 0, 'test stream');
# $ret = sysread(F, $msg, length($msg)) // '-';
# #print("reply: " . unpack("H*", $msg) . "\n");
# print("ret = $ret, code = $!\n");
#
# my (undef, undef, undef, $sidx3) = unpack("VV VV a256", $msg);
# print("index is $sidx3\n");
# sleep($sleep);
# print("add 9876 -> 1234/6543\n");
# $ret = syswrite(F, rtpengine_message('add', local_addr => \@local, local_port => 9876, src_addr => \@src, src_port => 1234, dst_addr => \@dst, dst_port => 6543, tos => 184, decrypt => $dec, encrypt => $enc, stream_idx => $sidx1, flags => 0x20)) // '-';
# print("ret = $ret, code = $!\n");
# sleep($sleep);
# for (1 .. 50) {
# print("delivering a packet\n");
#
# $msg = rtpengine_message_packet('packet', $idx1, $sidx1, 'packet data bla bla ' . rand() . "\n");
# $ret = syswrite(F, $msg) // '-';
# #print("reply: " . unpack("H*", $msg) . "\n");
# print("ret = $ret, code = $!\n");
#
# sleep($sleep);
# }
# print("deleting stream\n");
#
# $msg = rtpengine_message_stream('del_stream', $idx1, $sidx1, '');
# $ret = syswrite(F, $msg) // '-';
# #print("ret = $ret, code = $!, reply: " . unpack("H*", $msg) . "\n");
# print("ret = $ret, code = $!\n");
#
# sleep($sleep);
# print("deleting call\n");
#
# $msg = rtpengine_message_call('del_call', $idx1, '');
# $ret = syswrite(F, $msg) // '-';
# #print("ret = $ret, code = $!, reply: " . unpack("H*", $msg) . "\n");
# print("ret = $ret, code = $!\n");
#
# sleep($sleep);
close(F);

@ -653,15 +653,16 @@ a=rtpmap:111 opus/48000/2
#print(Dumper($op, $A, $B, $sdp) . "\n\n\n\n");
#print("sdp $op in:\n$sdp\n\n");
my @flags = ('trust address');
my $dict = {sdp => $sdp, command => $op, 'call-id' => $$c{callid},
flags => [ 'trust address' ],
flags => \@flags,
replace => [ 'origin', 'session connection' ],
#direction => [ $$pr{direction}, $$pr_o{direction} ],
'received from' => [ qw(IP4 127.0.0.1) ],
'rtcp-mux' => ['demux'],
};
$PORT_LATCHING and push(@{$dict->{flags}}, 'port latching');
$RECORD and $dict->{'record-call'} = 'yes';
$PORT_LATCHING and push(@flags, 'port latching');
$RECORD and push(@flags, 'record call');
#$viabranch and $dict->{'via-branch'} = $viabranch;
if ($op eq 'offer') {
$dict->{'from-tag'} = $$A{tag};

@ -0,0 +1,260 @@
#!/usr/bin/perl
use strict;
use warnings;
use Linux::Inotify2;
use AnyEvent::Loop;
use AnyEvent;
use Fcntl;
use Errno qw(EINTR EIO EAGAIN EWOULDBLOCK :POSIX);
use Net::Pcap;
use Time::HiRes;
my $COMBINE = 1;
# possible values:
# 0: don't combine any streams. each stream gets written to its own pcap file
# 1: combine all streams of one call into one pcap file
my $i = new Linux::Inotify2 or die;
$i->blocking(0);
$i->watch('/var/spool/rtpengine', IN_CLOSE_WRITE | IN_DELETE, \&handle_inotify) or die;
my $i_w = AnyEvent->io(fh => $i->fileno, poll => 'r', cb => sub { $i->poll });
setup();
AnyEvent::Loop::run();
exit;
my %metafiles;
my %callbacks;
sub handle_inotify {
my ($e) = @_;
my $fn = $e->{w}->{name} . '/' . $e->{name};
my $mf = ($metafiles{$fn} //= { name => $fn });
if ($e->IN_DELETE) {
handle_delete($e, $fn, $mf);
}
elsif ($e->IN_CLOSE_WRITE) {
handle_change($e, $fn, $mf);
}
else {
print("unhandled inotify event on $fn\n");
}
}
sub handle_change {
my ($e, $fn, $mf) = @_;
print("handling change on $fn\n");
my $fd;
open($fd, '<', $fn) or return;
# resume from where we left of
my $pos = $mf->{pos} // 0;
seek($fd, $pos, 0);
# read as much as we can
my $buf;
read($fd, $buf, 100000) or return;
$mf->{pos} = tell($fd);
close($fd);
# read contents section by section
while ($buf =~ s/^(.*?)\n//s) {
my $key = $1;
$buf =~ s/^(\d+):\n//s or die $buf;
my $len = $1;
my $val = substr($buf, 0, $len, '');
$buf =~ s/^\n\n//s or die;
if ($key =~ /^(CALL-ID|PARENT)$/) {
$mf->{$key} = $val;
}
elsif ($key =~ /^STREAM (\d+) interface$/) {
open_stream($mf, $val, $1);
}
elsif ($key =~ /^STREAM (\d+) details$/) {
stream_details($mf, $val, $1);
}
}
cb('call_setup', $mf);
}
sub handle_delete {
my ($e, $fn, $mf) = @_;
print("handling delete on $fn\n");
cb('call_close', $mf);
for my $sn (keys(%{$mf->{streams}})) {
my $ref = $mf->{streams}->{$sn};
close_stream($ref);
}
delete($mf->{streams});
delete($mf->{streams_id});
delete($metafiles{$fn});
}
sub get_stream_by_id {
my ($mf, $id) = @_;
my $ref = ($mf->{streams_id}->[$id] //= { metafile => $mf, id => $id });
return $ref;
}
sub open_stream {
my ($mf, $stream, $id) = @_;
print("opening $stream for $mf->{'CALL-ID'}\n");
my $fd;
sysopen($fd, '/proc/rtpengine/0/calls/' . $mf->{PARENT} . '/' . $stream, O_RDONLY | O_NONBLOCK) or return;
my $ref = get_stream_by_id($mf, $id);
$ref->{name} = $stream;
$ref->{fh} = $fd;
$ref->{watcher} = AnyEvent->io(fh => $fd, poll => 'r', cb => sub { stream_read($mf, $ref) });
cb('stream_setup', $ref, $mf);
$mf->{streams}->{$stream} = $ref;
$mf->{streams_id}->[$id] = $ref;
print("opened for reading $stream for $mf->{'CALL-ID'}\n");
}
sub stream_details {
my ($mf, $val, $id) = @_;
my $ref = get_stream_by_id($mf, $id);
my @details = $val =~ /(\w+) (\d+)/g;
while (@details) {
my $k = shift(@details);
my $v = shift(@details);
$ref->{$k} = $v;
}
}
sub close_stream {
my ($ref) = @_;
# this needs to be done explicitly, otherwise the closure would keep
# the object from being freed
delete($ref->{watcher});
my $mf = $ref->{metafile};
delete($mf->{streams}->{$ref->{name}});
cb('stream_close', $ref);
print("closed $ref->{name}\n");
}
sub stream_read {
my ($mf, $ref) = @_;
#print("handling read event for $mf->{name} / $ref->{name}\n");
while (1) {
my $buf;
my $ret = sysread($ref->{fh}, $buf, 65535);
if (!defined($ret)) {
if ($!{EAGAIN} || $!{EWOULDBLOCK}) {
return;
}
print("read error on $ref->{name} for $mf->{'CALL-ID'}: $!\n");
# fall through
}
elsif ($ret == 0) {
print("eof on $ref->{name} for $mf->{'CALL-ID'}\n");
# fall through
}
else {
# $ret > 0
#print("$ret bytes read from $ref->{name} for $mf->{'CALL-ID'}\n");
cb('packet', $ref, $mf, $buf, $ret);
next;
}
# some kind of error
close_stream($ref);
return;
}
}
sub tvsec_now {
my ($h) = @_;
my @now = Time::HiRes::gettimeofday();
$h->{tv_sec} = $now[0];
$h->{tv_usec} = $now[1];
}
sub setup {
if ($COMBINE == 0) {
$callbacks{stream_setup} = \&stream_pcap;
$callbacks{stream_close} = \&stream_pcap_close;
$callbacks{packet} = \&stream_packet,
}
elsif ($COMBINE == 1) {
$callbacks{call_setup} = \&call_pcap;
$callbacks{call_close} = \&call_pcap_close;
$callbacks{packet} = \&call_packet,
}
}
sub cb {
my ($name, @args) = @_;
my $fn = $callbacks{$name};
$fn or return;
return $fn->(@args);
}
sub dump_open {
my ($hash, $name) = @_;
$hash->{pcap} = pcap_open_dead(DLT_RAW, 65535);
$hash->{dumper} = pcap_dump_open($hash->{pcap}, $name);
}
sub dump_close {
my ($hash) = @_;
pcap_dump_close($hash->{dumper});
pcap_close($hash->{pcap});
delete($hash->{dumper});
delete($hash->{pcap});
}
sub dump_packet {
my ($hash, $buf, $len) = @_;
if (!$hash->{dumper}) {
print("discarding packet (dumper not open) - $hash->{name}\n");
return;
}
my $hdr = { len => $len, caplen => $len };
tvsec_now($hdr);
pcap_dump($hash->{dumper}, $hdr, $buf);
}
# COMBINE 0 functions
sub stream_pcap {
my ($ref, $mf) = @_;
dump_open($ref, $mf->{PARENT} . '-' . $ref->{name} . '.pcap');
}
sub stream_pcap_close {
my ($ref) = @_;
dump_close($ref);
}
sub stream_packet {
my ($ref, $mf, $buf, $ret) = @_;
dump_packet($ref, $buf, $ret);
}
# COMBINE 1 functions
sub call_pcap {
my ($mf) = @_;
$mf->{pcap} and return;
$mf->{PARENT} or return;
print("opening pcap for $mf->{PARENT}\n");
dump_open($mf, $mf->{PARENT} . '.pcap');
}
sub call_pcap_close {
my ($mf) = @_;
dump_close($mf);
}
sub call_packet {
my ($ref, $mf, $buf, $ret) = @_;
dump_packet($mf, $buf, $ret);
}
Loading…
Cancel
Save