From 984585a32c481ecdbf474f080031b61fed66363a Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Fri, 4 Nov 2016 14:09:36 -0400 Subject: [PATCH] TT#5003 implement kernel-side call recording Squashed commit of the following: commit 1af3efd464d9c86eb428aa29ca084ead67ba4cdc Author: Richard Fuchs 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 Date: Fri Nov 4 14:09:08 2016 -0400 restore libcurl build dependency Change-Id: Ia853f928caf9e443bb69c4015cbba805e6d24153 commit 5f5fd88fde67ffcd5a83f1a18e9134702f995686 Author: Richard Fuchs 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 Date: Fri Nov 4 09:56:47 2016 -0400 remove redundant ng result=ok logic Change-Id: I84d7245f52dc12a4002b4dd2b736afea9ae733fa commit 411213dd3d17c1e54eeb6813cb0473a706a10cee Author: Richard Fuchs Date: Thu Nov 3 16:04:54 2016 -0400 support the `start recording` command message Change-Id: I316e90fd3fe34f01b20826936ef620bcff785397 commit ae1910c68d90d0c1f913bd09f9ecdee5579f53f4 Author: Richard Fuchs 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 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 Date: Thu Nov 3 11:40:00 2016 -0400 output additional details to metafiles Change-Id: I0539a0617aff51c9c499bbd9586d61c7e7ca7e7c commit e81f413f3ba228f548931a234a1c18c13c62b135 Author: Richard Fuchs Date: Wed Nov 2 16:18:28 2016 -0400 globalize kernel access variables Change-Id: Ie07e0ebb8705189c8b1e49f596080ff8bcaef64f commit 5e74609c7c3e5237fbd9995b90b29b2c456d2c04 Author: Richard Fuchs 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 Date: Wed Nov 2 11:38:22 2016 -0400 eliminate dummy virtual function Change-Id: I7cc2596f31350bf0d39253b3bd7e9fe1cec2b92d commit 9177effc4e56b0fc26f44da525fe88d8aa3a6f05 Author: Richard Fuchs 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 Date: Wed Nov 2 10:57:15 2016 -0400 output before/after sdp to metafiles Change-Id: I1d3177e40591d6b4c4789eb6e18132743a9eda45 commit 68b27fa964455eef4c4acaf9540d19013b3df3e7 Author: Richard Fuchs Date: Tue Nov 1 15:40:20 2016 -0400 fix header length problems Change-Id: I896453f72df3ba146251f5d82ca1d02dd0ad08e9 commit ebbd942ad00b3a4eb74fbfcd915e82a263243d02 Author: Richard Fuchs 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 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 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 Date: Tue Nov 1 11:41:47 2016 -0400 adding rudimentary lock debugging Change-Id: Iff541c58e5ea4c3fc2ec16e93396148f935bc4f3 commit 51a75c5022f96a23b8fff6738b5fa4ad7095a746 Author: Richard Fuchs Date: Mon Oct 31 14:35:17 2016 -0400 handle read errors in reference intercept script Change-Id: I047d763dbd498026a0d8db24c5532155c75fd6e6 commit 69a460de08c6dfe163f9d90dee478c72ea56a2fc Author: Richard Fuchs 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 Date: Mon Oct 31 12:50:26 2016 -0400 additional debugging Change-Id: I81c93c2dd9007ed6a0d6e0b147de0deb44b7b023 commit fd367947404c25f7122479dc805ca895346e3568 Author: Richard Fuchs Date: Fri Oct 28 12:07:25 2016 -0400 adding reference implementation using inotify Change-Id: Ibec73bdc4c7a576e4beaf5e749567dd2508be4df commit f8fde6cc7308faf9aea5016d1b325fe54ace4f73 Author: Richard Fuchs Date: Thu Oct 27 15:55:10 2016 -0400 add stream info to metadata files Change-Id: I200df14a3e35c2c0077866444b96692de4303761 commit 5a8b4dd156d014756681c644549f481af941b294 Author: Richard Fuchs 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 Date: Thu Oct 27 12:29:02 2016 -0400 abstractize fake packet header and support ipv6 Change-Id: I6c1ad1cccad306f3d306fb3387efcb033a3574b2 commit e1b648b9baea609cbe9836c81a5a2cd41ef464cb Author: Richard Fuchs Date: Thu Oct 27 09:32:40 2016 -0400 support creating kernel intercept streams Change-Id: I1f9bdda61af52814f07f765a8b558a7491cfc0b9 commit b6737ec9635d2d7a8d0245bab7c51a077cd814d2 Author: Richard Fuchs Date: Wed Oct 26 14:46:13 2016 -0400 get rid of call->record_call flag Change-Id: I04dcba49b07fc669b0bb4bcea8ccc2b52cb76e02 commit 6ed362c120995d1e3fdff71f7e440e73737e7fcb Author: Richard Fuchs Date: Wed Oct 26 12:53:04 2016 -0400 add recorded call to /proc Change-Id: I1fe64b9fb8fe3604bb7d432899c43e3e37ea6a4f commit 62d2508ecf69fbdf9ba1faf3e2433fa6f717740a Author: Richard Fuchs Date: Wed Oct 26 10:10:05 2016 -0400 simplify some code Change-Id: I2a27400e91f58aa8ea20d3e610a7509d2e9a0dfc commit 0c811a8e91ded41487bf045e6df55fb31cce7671 Author: Richard Fuchs 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 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 Date: Tue Oct 25 10:48:50 2016 -0400 abstractize recording cleanup routine Change-Id: Ib9fc46542f273bab53f611a1456f02d67edfc966 commit d8358c9a2c6cbd66e17d4bb08dba96a3b2e2d41a Author: Richard Fuchs Date: Tue Oct 25 10:07:44 2016 -0400 use shorthand for function pointers Change-Id: Idf43949e20281a10317e22a5b68a6d133e398bd4 commit 9417437c1092770e36bde22b508f9d71078d4a1b Author: Richard Fuchs Date: Mon Oct 24 15:31:56 2016 -0400 abstractize individual recording functions Change-Id: I0974696b6bb361fce39b24d6be91b5c052ee2b14 commit eb631cd876a0f941ed4fb6d47412a6892e019f1b Author: Richard Fuchs Date: Mon Oct 24 11:54:42 2016 -0400 abstractize call recording mechanism Change-Id: If8672051227944544d9cf916d359c5db67235e3e commit 70797ceb8f36c45593688652df98f19a5c1dd9f4 Author: Richard Fuchs 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 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 Date: Wed Jul 13 11:02:04 2016 -0400 implement free list Change-Id: I3e7dc2325c937923d19ce6000f2cf1c011e51037 commit bd75a1cf254c484878d07ecceca7220cdbf1ddb3 Author: Richard Fuchs 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 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 Date: Mon Jul 11 10:45:10 2016 -0400 restructure calls and streams to global arrays Change-Id: Icfca615b21f2e51b1abda4422c4eeb8f4ac70a9b commit 6cf9980f3e61f1f1d44b5ce883298ca2726af1b2 Author: Richard Fuchs Date: Fri Jul 8 11:53:32 2016 -0400 implement poll/select mechanism Change-Id: Ic10c017250f0991f691a887b078e80f694bba853 commit d95829e07b2bf4d3352806c70b3393ae3a8609fb Author: Richard Fuchs Date: Fri Jul 8 10:44:38 2016 -0400 discard new/old packets as required Change-Id: If73ce77dcbc24addb6ce0931b90de0f5efae9f51 commit be1d769e4454f1d98f0b75cc8301c128a40e980c Author: Richard Fuchs Date: Fri Jul 8 10:25:38 2016 -0400 implement EOF for stream readers Change-Id: I858dc1fdd7df3b65283e1d96457d87e7452840f5 commit ed2d98d55a87ce428cdc66d9336035f3dc0cb5ae Author: Richard Fuchs Date: Thu Jul 7 15:02:58 2016 -0400 blocking and nonblocking reads - incomplete Change-Id: I7cbfb09507ad8726773d6a28ddb98d5981decd04 commit 246709c7f4741e669c4a78b48c382497bf57c6be Author: Richard Fuchs Date: Thu Jul 7 13:55:41 2016 -0400 rudimentary packet reading/dequeuing Change-Id: I1a924e5cb2ef4e4f16aeff1f1dd90d0746f91da5 commit 427749394995d2e6fb8ad7ddc23c876b1c8e11f1 Author: Richard Fuchs Date: Thu Jul 7 12:40:31 2016 -0400 use kzalloc where appropriate Change-Id: Icd6a109a69ab4f6dc9f7d35fd9e8fc9127f8e7e0 commit 5b07819217dfb47201a373e941aab185789484a9 Author: Richard Fuchs Date: Thu Jul 7 09:07:54 2016 -0400 more module referencing Change-Id: I2e34fe74f2edef9170a4558f6a24394240966d79 commit 1d8268f636c26ec21c3cfa2308bad91262e0768a Author: Richard Fuchs Date: Wed Jul 6 14:56:42 2016 -0400 store delivered packets in queue Change-Id: Id349b75e06f9dd77c884196b7726027ac5cab7ae commit 23c6a53f94c46ead7093b5402486799fc631e432 Author: Richard Fuchs Date: Wed Jul 6 14:21:27 2016 -0400 implement call for packet delivery Change-Id: Ibeb815bf2fedfdd644d324c65b58a24871d47d4a commit c8fd855f325128b78f4e276bfb26b479ef189b11 Author: Richard Fuchs Date: Wed Jul 6 10:28:18 2016 -0400 automatic cleanup of objects upon free/delete Change-Id: If244905e2d074f491229316f3305c9b0b1451792 commit 282ef603a83637c9a8e33ed31d09268fa45a6301 Author: Richard Fuchs Date: Wed Jul 6 09:47:29 2016 -0400 unify read/write functions Change-Id: I78b0dd05cd730e16655034994c74cbe23be23fce commit e74c62cc6f0dce85fea6534d8d72e2f51e0947d6 Author: Richard Fuchs 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 Date: Tue Jul 5 15:33:07 2016 -0400 support creation and deletion of streams Change-Id: I7df05d232b5971c54ca50adce8144b5f1646fba0 commit 8be4e2c7c49dd7b893335f14ca98776a1f3fb12f Author: Richard Fuchs Date: Tue Jul 5 11:54:39 2016 -0400 create functions for redundant code Change-Id: Id3772f12294ee9891d22d833274e5935814cae0b commit 47ce4ca8f5d694e50c98f73147ed2eb81f14ea8a Author: Richard Fuchs Date: Thu Jun 30 12:12:19 2016 -0400 support creation and deletion of calls objects Change-Id: Ie5f9aa978bac21fc30909f14d6a438494848dfd5 commit 8dab54209d3e52d41f78d8f54956a4ed3dc15da3 Author: Richard Fuchs Date: Wed Jun 29 14:20:23 2016 -0400 create /proc/.../calls directory Change-Id: I682a4bf23edbb72772d64963e3ba2cab2a521ff4 commit 1401ae8db5f9e2a973c670f3bf72e9019c451276 Author: Richard Fuchs Date: Wed Jun 29 14:02:11 2016 -0400 rename kernel message enum Change-Id: I45d7aeae43df1fe6ecd6b6965dbd6ba7e7b715d8 commit ce44ff0dbe67a6687d5fb1bea9d31e21c9464907 Author: Richard Fuchs Date: Wed Jun 29 14:00:32 2016 -0400 convert kernel message data to union Change-Id: I7cfd9fe81623efae0a828ba457aa0a4b1380ff03 Change-Id: I4bb0b7f6d29c1c7a2144e4735ed59f3dc0e37094 --- README.md | 75 +- daemon/Makefile | 1 - daemon/call.c | 20 +- daemon/call.h | 13 +- daemon/call_interfaces.c | 67 +- daemon/call_interfaces.h | 4 + daemon/cli.c | 7 +- daemon/control_ng.c | 11 +- daemon/control_ng.h | 1 + daemon/kernel.c | 120 +- daemon/kernel.h | 29 +- daemon/log.c | 38 +- daemon/log.h | 4 +- daemon/main.c | 26 +- daemon/media_socket.c | 50 +- daemon/recording.c | 789 ++++++++---- daemon/recording.h | 121 +- daemon/redis.c | 30 + daemon/socket.c | 58 + daemon/socket.h | 8 + daemon/str.c | 40 +- daemon/str.h | 2 - debian/ngcp-rtpengine-daemon.default | 1 + debian/ngcp-rtpengine-daemon.init | 11 +- kernel-module/xt_RTPENGINE.c | 1647 ++++++++++++++++++++++---- kernel-module/xt_RTPENGINE.h | 55 +- tests/kernel-module-test.pl | 454 ++++++- tests/simulator-ng.pl | 7 +- utils/kernel-intercept-dump.pl | 260 ++++ 29 files changed, 3206 insertions(+), 743 deletions(-) create mode 100755 utils/kernel-intercept-dump.pl diff --git a/README.md b/README.md index bc6ff087b..81a13a7c4 100644 --- a/README.md +++ b/README.md @@ -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. @@ -503,12 +508,36 @@ The options are described in more detail below. generic metadata - There are two empty lines between each logic block of metadata. - We write out all answer SDP, each separated from one another by one empty - line. The generic metadata at the end can be any length with any number of - lines. Metadata files will appear in the subdirectory when the call - completes. PCAP files will be written to the subdirectory as the call is - being recorded. + There are two empty lines between each logic block of metadata. + We write out all answer SDP, each separated from one another by one empty + line. The generic metadata at the end can be any length with any number of + lines. Metadata files will appear in the subdirectory when the call + 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: @@ -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. diff --git a/daemon/Makefile b/daemon/Makefile index 1ad4df37f..06d7c41fd 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -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) diff --git a/daemon/call.c b/daemon/call.c index 1ef33a2d3..cee70bdf6 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -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); } diff --git a/daemon/call.h b/daemon/call.h index 5879f6eab..d8f19c7aa 100644 --- a/daemon/call.h +++ b/daemon/call.h @@ -14,10 +14,10 @@ #include #include #include -#include #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 diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c index cd5ea5e05..bde5b2752 100644 --- a/daemon/call_interfaces.c +++ b/daemon/call_interfaces.c @@ -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; +} diff --git a/daemon/call_interfaces.h b/daemon/call_interfaces.h index 214861ec0..6c3852ce0 100644 --- a/daemon/call_interfaces.h +++ b/daemon/call_interfaces.h @@ -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); diff --git a/daemon/cli.c b/daemon/cli.c index d3b4bceb3..b95fbb2de 100644 --- a/daemon/cli.c +++ b/daemon/cli.c @@ -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); } diff --git a/daemon/control_ng.c b/daemon/control_ng.c index ca77e8182..661b06efc 100644 --- a/daemon/control_ng.c +++ b/daemon/control_ng.c @@ -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); diff --git a/daemon/control_ng.h b/daemon/control_ng.h index 919cd872a..ac3cf418a 100644 --- a/daemon/control_ng.h +++ b/daemon/control_ng.h @@ -18,6 +18,7 @@ struct control_ng_stats { int delete; int query; int list; + int start_recording; int errors; }; diff --git a/daemon/kernel.c b/daemon/kernel.c index 407ea88ba..479386b2c 100644 --- a/daemon/kernel.c +++ b/daemon/kernel.c @@ -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; +} diff --git a/daemon/kernel.h b/daemon/kernel.h index bc93cac58..bddb5ec07 100644 --- a/daemon/kernel.h +++ b/daemon/kernel.h @@ -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); diff --git a/daemon/log.c b/daemon/log.c index 1928d21f5..c364b376b 100644 --- a/daemon/log.c +++ b/daemon/log.c @@ -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; diff --git a/daemon/log.h b/daemon/log.h index 9a7be2787..a3ffdef61 100644 --- a/daemon/log.h +++ b/daemon/log.h @@ -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))); diff --git a/daemon/main.c b/daemon/main.c index e136aaed2..a4b95c599 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -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) - 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) + if (kernel_setup_table(table)) { + if (no_fallback) { + ilog(LOG_CRIT, "Userspace fallback disallowed - exiting"); 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; } diff --git a/daemon/media_socket.c b/daemon/media_socket.c index d7e972103..8caadabc6 100644 --- a/daemon/media_socket.c +++ b/daemon/media_socket.c @@ -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) { diff --git a/daemon/recording.c b/daemon/recording.c index 8ff05c4e1..9d0526536 100644 --- a/daemon/recording.c +++ b/daemon/recording.c @@ -7,47 +7,142 @@ #include #include #include -#include #include -#include "call.h" +#include +#include +#include +#include + +#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 (!_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); } - 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); - exit(-1); +} + +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; } - } else { - spooldir = strdup(spoolpath); + 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); + 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]; + 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; - } else if (!S_ISDIR(info.st_mode)) { - fprintf(stderr, "Spool file exists, but \"%s\" is not a directory.\n", spoolpath); + if (!check_create_dir(rec_path, "pcaps", 1)) + spool_good = FALSE; + if (!check_create_dir(tmp_path, "tmp", 1)) spool_good = FALSE; - } else { - // 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); - spool_good = FALSE; - } - 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; - } + 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."); - return spool_good; + 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; + + 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; -/** - * 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; - // 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); - } - return call->record_call; + 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)); } -/** - * 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) { +static void pcap_init(struct call *call) { + struct recording *recording = call->recording; + + // Wireshark starts at packet index 1, so we start there, too + 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(call->recording->meta_fp, "%s\n\n", pcap_path->s); + fprintf(recording->pcap.meta_fp, "%s\n\n", pcap_path); } - 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); - 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); - if (mfp == NULL) { - ilog(LOG_ERROR, "Could not open metadata file: %s", meta_filepath->s); - free(recording->meta_filepath->s); - free(recording->meta_filepath); - recording->meta_filepath = NULL; - } - recording->meta_fp = mfp; - ilog(LOG_DEBUG, "Wrote metadata file to temporary path: %s", meta_filepath->s); - return meta_filepath; + + char *meta_filepath = file_path_str(recording->meta_prefix, "/tmp/rtpengine-meta-", ".tmp"); + recording->meta_filepath = meta_filepath; + 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); + free(meta_filepath); + recording->meta_filepath = NULL; + return NULL; } + 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); - } 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; +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); } 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); } diff --git a/daemon/recording.h b/daemon/recording.h index 7a3cfea94..ea343b956 100644 --- a/daemon/recording.h +++ b/daemon/recording.h @@ -11,27 +11,95 @@ #include #include #include -#include "call.h" +#include +#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 diff --git a/daemon/redis.c b/daemon/redis.c index a56a6972a..b79be0ede 100644 --- a/daemon/redis.c +++ b/daemon/redis.c @@ -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) { diff --git a/daemon/socket.c b/daemon/socket.c index 1660afef9..e88c08ef5 100644 --- a/daemon/socket.c +++ b/daemon/socket.c @@ -2,6 +2,9 @@ #include #include #include +#include +#include +#include #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; +} diff --git a/daemon/socket.h b/daemon/socket.h index dc7458b27..cd8206020 100644 --- a/daemon/socket.h +++ b/daemon/socket.h @@ -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); diff --git a/daemon/str.c b/daemon/str.c index 5872117fd..8a5d94f1a 100644 --- a/daemon/str.c +++ b/daemon/str.c @@ -1,4 +1,5 @@ #include "str.h" +#include "aux.h" #include #include @@ -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 /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 OPTIONS="$OPTIONS --foreground" diff --git a/kernel-module/xt_RTPENGINE.c b/kernel-module/xt_RTPENGINE.c index bdfda4fa4..567b2ac88 100644 --- a/kernel-module/xt_RTPENGINE.c +++ b/kernel-module/xt_RTPENGINE.c @@ -25,6 +25,7 @@ #include #include #include +#include #ifndef __RE_EXTERNAL #include #else @@ -62,11 +63,76 @@ MODULE_LICENSE("GPL"); (x).port #if 0 -#define DBG(x...) printk(KERN_DEBUG x) +#define DBG(fmt, ...) printk(KERN_DEBUG "[PID %i line %i] " fmt, current ? current->pid : -1, \ + __LINE__, ##__VA_ARGS__) #else #define DBG(x...) ((void)0) #endif +#if 0 +#define _s_lock(l, f) do { \ + printk(KERN_DEBUG "[PID %i %s:%i] acquiring lock %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + spin_lock_irqsave(l, f); \ + printk(KERN_DEBUG "[PID %i %s:%i] has acquired lock %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + } while (0) +#define _s_unlock(l, f) do { \ + printk(KERN_DEBUG "[PID %i %s:%i] is unlocking %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + spin_unlock_irqrestore(l, f); \ + printk(KERN_DEBUG "[PID %i %s:%i] has released lock %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + } while (0) +#define _r_lock(l, f) do { \ + printk(KERN_DEBUG "[PID %i %s:%i] acquiring read lock %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + read_lock_irqsave(l, f); \ + printk(KERN_DEBUG "[PID %i %s:%i] has acquired read lock %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + } while (0) +#define _r_unlock(l, f) do { \ + printk(KERN_DEBUG "[PID %i %s:%i] is read unlocking %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + read_unlock_irqrestore(l, f); \ + printk(KERN_DEBUG "[PID %i %s:%i] has released read lock %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + } while (0) +#define _w_lock(l, f) do { \ + printk(KERN_DEBUG "[PID %i %s:%i] acquiring write lock %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + write_lock_irqsave(l, f); \ + printk(KERN_DEBUG "[PID %i %s:%i] has acquired write lock %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + } while (0) +#define _w_unlock(l, f) do { \ + printk(KERN_DEBUG "[PID %i %s:%i] is write unlocking %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + write_unlock_irqrestore(l, f); \ + printk(KERN_DEBUG "[PID %i %s:%i] has released write lock %s\n", \ + current ? current->pid : -1, \ + __FUNCTION__, __LINE__, #l); \ + } while (0) +#else +#define _s_lock(l, f) spin_lock_irqsave(l, f) +#define _s_unlock(l, f) spin_unlock_irqrestore(l, f) +#define _r_lock(l, f) read_lock_irqsave(l, f) +#define _r_unlock(l, f) read_unlock_irqrestore(l, f) +#define _w_lock(l, f) write_lock_irqsave(l, f) +#define _w_unlock(l, f) write_unlock_irqrestore(l, f) +#endif + #if LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0) @@ -80,29 +146,34 @@ struct re_hmac; struct re_cipher; struct rtp_parsed; struct re_crypto_context; +struct re_auto_array; +struct re_call; +struct re_stream; +struct rtpengine_table; + + #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) -kuid_t proc_kuid; -uint proc_uid = 0; +static kuid_t proc_kuid; +static uint proc_uid = 0; module_param(proc_uid, uint, 0); MODULE_PARM_DESC(proc_uid, "rtpengine procfs tree user id"); -kgid_t proc_kgid; -uint proc_gid = 0; +static kgid_t proc_kgid; +static uint proc_gid = 0; module_param(proc_gid, uint, 0); MODULE_PARM_DESC(proc_gid, "rtpengine procfs tree group id"); #endif -static struct proc_dir_entry *my_proc_root; -static struct proc_dir_entry *proc_list; -static struct proc_dir_entry *proc_control; -static struct rtpengine_table *table[MAX_ID]; -static rwlock_t table_lock; +static uint stream_packets_list_limit = 10; +module_param(stream_packets_list_limit, uint, 0); +MODULE_PARM_DESC(stream_packets_list_limit, "maximum number of packets to retain for intercept streams"); +static ssize_t proc_control_read(struct file *, char __user *, size_t, loff_t *); static ssize_t proc_control_write(struct file *, const char __user *, size_t, loff_t *); static int proc_control_open(struct inode *, struct file *); static int proc_control_close(struct inode *, struct file *); @@ -110,8 +181,10 @@ static int proc_control_close(struct inode *, struct file *); static ssize_t proc_status(struct file *, char __user *, size_t, loff_t *); static ssize_t proc_main_control_write(struct file *, const char __user *, size_t, loff_t *); -static int proc_main_control_open(struct inode *, struct file *); -static int proc_main_control_close(struct inode *, struct file *); + +static int proc_generic_open_modref(struct inode *, struct file *); +static int proc_generic_close_modref(struct inode *, struct file *); +static int proc_generic_seqrelease_modref(struct inode *inode, struct file *file); static int proc_list_open(struct inode *, struct file *); @@ -131,7 +204,10 @@ static void proc_main_list_stop(struct seq_file *, void *); static void *proc_main_list_next(struct seq_file *, void *, loff_t *); static int proc_main_list_show(struct seq_file *, void *); -static void table_push(struct rtpengine_table *); +static ssize_t proc_stream_read(struct file *f, char __user *b, size_t l, loff_t *o); +static unsigned int proc_stream_poll(struct file *f, struct poll_table_struct *p); + +static void table_put(struct rtpengine_table *); static struct rtpengine_target *get_target(struct rtpengine_table *, const struct re_address *); static int is_valid_address(const struct re_address *rea); @@ -141,6 +217,13 @@ static int srtp_encrypt_aes_cm(struct re_crypto_context *, struct rtpengine_srtp static int srtp_encrypt_aes_f8(struct re_crypto_context *, struct rtpengine_srtp *, struct rtp_parsed *, u_int64_t); +static void call_put(struct re_call *call); +static void del_stream(struct re_stream *stream, struct rtpengine_table *); +static void del_call(struct re_call *call, struct rtpengine_table *); + +static inline int bitfield_set(unsigned long *bf, unsigned int i); +static inline int bitfield_clear(unsigned long *bf, unsigned int i); + @@ -204,21 +287,81 @@ struct re_dest_addr_hash { struct re_dest_addr *addrs[256]; }; +struct re_auto_array_free_list { + struct list_head list_entry; + unsigned int index; +}; +struct re_auto_array { + rwlock_t lock; + + void **array; + unsigned int array_len; + unsigned long *used_bitfield; + struct list_head free_list; +}; + +struct re_call { + atomic_t refcnt; + struct rtpengine_call_info info; + unsigned int table_id; + u32 hash_bucket; + + struct proc_dir_entry *root; + + struct list_head table_entry; /* protected by calls.lock */ + struct hlist_node calls_hash_entry; + + struct list_head streams; /* protected by streams.lock */ +}; + +struct re_stream_packet { + struct list_head list_entry; + unsigned int buflen; + struct sk_buff *skbuf; + unsigned char buf[]; +}; + +struct re_stream { + atomic_t refcnt; + struct rtpengine_stream_info info; + u32 hash_bucket; + + struct proc_dir_entry *file; + + struct list_head call_entry; /* protected by streams.lock */ + struct hlist_node streams_hash_entry; + + spinlock_t packet_list_lock; + struct list_head packet_list; + unsigned int list_count; + wait_queue_head_t wq; + int eof; +}; + +#define HASH_BITS 8 /* make configurable? */ struct rtpengine_table { atomic_t refcnt; rwlock_t target_lock; pid_t pid; - u_int32_t id; - struct proc_dir_entry *proc; - struct proc_dir_entry *status; - struct proc_dir_entry *control; - struct proc_dir_entry *list; - struct proc_dir_entry *blist; + unsigned int id; + struct proc_dir_entry *proc_root; + struct proc_dir_entry *proc_status; + struct proc_dir_entry *proc_control; + struct proc_dir_entry *proc_list; + struct proc_dir_entry *proc_blist; + struct proc_dir_entry *proc_calls; struct re_dest_addr_hash dest_addr_hash; unsigned int num_targets; + + struct list_head calls; /* protected by calls.lock */ + + spinlock_t calls_hash_lock[1 << HASH_BITS]; + struct hlist_head calls_hash[1 << HASH_BITS]; + spinlock_t streams_hash_lock[1 << HASH_BITS]; + struct hlist_head streams_hash[1 << HASH_BITS]; }; struct re_cipher { @@ -265,33 +408,55 @@ struct rtp_parsed { +static struct proc_dir_entry *my_proc_root; +static struct proc_dir_entry *proc_list; +static struct proc_dir_entry *proc_control; + +static struct rtpengine_table *table[MAX_ID]; +static rwlock_t table_lock; + +static struct re_auto_array calls; +static struct re_auto_array streams; + + + + + static const struct file_operations proc_control_ops = { + .owner = THIS_MODULE, + .read = proc_control_read, .write = proc_control_write, .open = proc_control_open, .release = proc_control_close, }; static const struct file_operations proc_main_control_ops = { + .owner = THIS_MODULE, .write = proc_main_control_write, - .open = proc_main_control_open, - .release = proc_main_control_close, + .open = proc_generic_open_modref, + .release = proc_generic_close_modref, }; static const struct file_operations proc_status_ops = { + .owner = THIS_MODULE, .read = proc_status, + .open = proc_generic_open_modref, + .release = proc_generic_close_modref, }; static const struct file_operations proc_list_ops = { + .owner = THIS_MODULE, .open = proc_list_open, .read = seq_read, .llseek = seq_lseek, - .release = seq_release, + .release = proc_generic_seqrelease_modref, }; static const struct file_operations proc_blist_ops = { + .owner = THIS_MODULE, .open = proc_blist_open, .read = proc_blist_read, .release = proc_blist_close, @@ -305,10 +470,11 @@ static const struct seq_operations proc_list_seq_ops = { }; static const struct file_operations proc_main_list_ops = { + .owner = THIS_MODULE, .open = proc_main_list_open, .read = seq_read, .llseek = seq_lseek, - .release = seq_release, + .release = proc_generic_seqrelease_modref, }; static const struct seq_operations proc_main_list_seq_ops = { @@ -318,6 +484,14 @@ static const struct seq_operations proc_main_list_seq_ops = { .show = proc_main_list_show, }; +static const struct file_operations proc_stream_ops = { + .owner = THIS_MODULE, + .read = proc_stream_read, + .poll = proc_stream_poll, + .open = proc_generic_open_modref, + .release = proc_generic_close_modref, +}; + static const struct re_cipher re_ciphers[] = { [REC_INVALID] = { .id = REC_INVALID, @@ -370,37 +544,129 @@ static const char *re_msm_strings[] = { +/* must already be initialized to zero */ +static void auto_array_init(struct re_auto_array *a) { + rwlock_init(&a->lock); + INIT_LIST_HEAD(&a->free_list); +} + +/* lock must be held */ +static void set_auto_array_index(struct re_auto_array *a, unsigned int idx, void *ptr) { + a->array[idx] = ptr; + bitfield_set(a->used_bitfield, idx); +} +/* lock must be held */ +static void auto_array_clear_index(struct re_auto_array *a, unsigned int idx) { + struct re_auto_array_free_list *fl; + + bitfield_clear(a->used_bitfield, idx); + a->array[idx] = NULL; + + fl = kmalloc(sizeof(*fl), GFP_ATOMIC); + if (!fl) + return; + + DBG("adding %u to free list\n", idx); + fl->index = idx; + list_add(&fl->list_entry, &a->free_list); +} +/* lock must be held */ +static unsigned int pop_free_list_entry(struct re_auto_array *a) { + unsigned int ret; + struct re_auto_array_free_list *fl; + + fl = list_first_entry(&a->free_list, struct re_auto_array_free_list, list_entry); + ret = fl->index; + list_del(&fl->list_entry); + kfree(fl); + + DBG("popped %u from free list\n", ret); + return ret; +} +static void auto_array_free(struct re_auto_array *a) { + + if (a->array) + kfree(a->array); + if (a->used_bitfield) + kfree(a->used_bitfield); + while (!list_empty(&a->free_list)) + pop_free_list_entry(a); +} + static struct rtpengine_table *new_table(void) { struct rtpengine_table *t; + unsigned int i; DBG("Creating new table\n"); if (!try_module_get(THIS_MODULE)) return NULL; - t = kmalloc(sizeof(*t), GFP_KERNEL); + t = kzalloc(sizeof(*t), GFP_KERNEL); if (!t) { module_put(THIS_MODULE); return NULL; } - memset(t, 0, sizeof(*t)); - atomic_set(&t->refcnt, 1); rwlock_init(&t->target_lock); + INIT_LIST_HEAD(&t->calls); t->id = -1; + for (i = 0; i < ARRAY_SIZE(t->calls_hash); i++) { + INIT_HLIST_HEAD(&t->calls_hash[i]); + spin_lock_init(&t->calls_hash_lock[i]); + } + for (i = 0; i < ARRAY_SIZE(t->streams_hash); i++) { + INIT_HLIST_HEAD(&t->streams_hash[i]); + spin_lock_init(&t->streams_hash_lock[i]); + } + return t; } -static void table_hold(struct rtpengine_table *t) { - atomic_inc(&t->refcnt); +#define ref_get(o) atomic_inc(&(o)->refcnt) + + + + + +static inline struct proc_dir_entry *proc_mkdir_user(const char *name, umode_t mode, + struct proc_dir_entry *parent) +{ + struct proc_dir_entry *ret; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0) + ret = create_proc_entry(name, S_IFDIR | mode, parent); +#else + ret = proc_mkdir_mode(name, mode, parent); +#endif + if (!ret) + return NULL; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) + proc_set_user(ret, proc_kuid, proc_kgid); +#endif + + return ret; } +static inline struct proc_dir_entry *proc_create_user(const char *name, umode_t mode, + struct proc_dir_entry *parent, const struct file_operations *ops, + void *ptr) +{ + struct proc_dir_entry *ret; + ret = proc_create_data(name, mode, parent, ops, ptr); + if (!ret) + return NULL; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) + proc_set_user(ret, proc_kuid, proc_kgid); +#endif + return ret; +} @@ -409,44 +675,35 @@ static int table_create_proc(struct rtpengine_table *t, u_int32_t id) { sprintf(num, "%u", id); -#if LINUX_VERSION_CODE < KERNEL_VERSION(3,0,0) - t->proc = create_proc_entry(num, S_IFDIR | S_IRUGO | S_IXUGO, my_proc_root); -#else - t->proc = proc_mkdir_mode(num, S_IRUGO | S_IXUGO, my_proc_root); -#endif - if (!t->proc) + t->proc_root = proc_mkdir_user(num, S_IRUGO | S_IXUGO, my_proc_root); + if (!t->proc_root) return -1; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) - proc_set_user(t->proc, proc_kuid, proc_kgid); -#endif - t->status = proc_create_data("status", S_IFREG | S_IRUGO, t->proc, &proc_status_ops, + + t->proc_status = proc_create_user("status", S_IFREG | S_IRUGO, t->proc_root, &proc_status_ops, (void *) (unsigned long) id); - if (!t->status) + if (!t->proc_status) return -1; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) - proc_set_user(t->status, proc_kuid, proc_kgid); -#endif - t->control = proc_create_data("control", S_IFREG | S_IWUSR | S_IWGRP, t->proc, + + t->proc_control = proc_create_user("control", S_IFREG | S_IWUSR | S_IWGRP | S_IRUSR | S_IRGRP, + t->proc_root, &proc_control_ops, (void *) (unsigned long) id); - if (!t->control) + if (!t->proc_control) return -1; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) - proc_set_user(t->control, proc_kuid, proc_kgid); -#endif - t->list = proc_create_data("list", S_IFREG | S_IRUGO, t->proc, + + t->proc_list = proc_create_user("list", S_IFREG | S_IRUGO, t->proc_root, &proc_list_ops, (void *) (unsigned long) id); - if (!t->list) + if (!t->proc_list) return -1; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) - proc_set_user(t->list, proc_kuid, proc_kgid); -#endif - t->blist = proc_create_data("blist", S_IFREG | S_IRUGO, t->proc, + + t->proc_blist = proc_create_user("blist", S_IFREG | S_IRUGO, t->proc_root, &proc_blist_ops, (void *) (unsigned long) id); - if (!t->blist) + if (!t->proc_blist) return -1; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) - proc_set_user(t->blist, proc_kuid, proc_kgid); -#endif + + t->proc_calls = proc_mkdir_user("calls", S_IRUGO | S_IXUGO, t->proc_root); + if (!t->proc_calls) + return -1; + return 0; } @@ -469,12 +726,12 @@ static struct rtpengine_table *new_table_link(u_int32_t id) { write_lock_irqsave(&table_lock, flags); if (table[id]) { write_unlock_irqrestore(&table_lock, flags); - table_push(t); + table_put(t); printk(KERN_WARNING "xt_RTPENGINE duplicate ID %u\n", id); return NULL; } - table_hold(t); + ref_get(t); table[id] = t; t->id = id; write_unlock_irqrestore(&table_lock, flags); @@ -501,7 +758,7 @@ static void free_crypto_context(struct re_crypto_context *c) { crypto_free_shash(c->shash); } -static void target_push(struct rtpengine_target *t) { +static void target_put(struct rtpengine_target *t) { if (!t) return; @@ -521,7 +778,7 @@ static void target_push(struct rtpengine_target *t) { -static void target_hold(struct rtpengine_target *t) { +static void target_get(struct rtpengine_target *t) { atomic_inc(&t->refcnt); } @@ -531,13 +788,15 @@ static void target_hold(struct rtpengine_target *t) { static void clear_proc(struct proc_dir_entry **e) { - if (!e || !*e) + struct proc_dir_entry *pde; + + if (!e || !(pde = *e)) return; #if LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0) - remove_proc_entry((*e)->name, (*e)->parent); + remove_proc_entry(pde->name, pde->parent); #else - proc_remove(*e); + proc_remove(pde); #endif *e = NULL; } @@ -546,7 +805,16 @@ static void clear_proc(struct proc_dir_entry **e) { -static void table_push(struct rtpengine_table *t) { +static void clear_table_proc_files(struct rtpengine_table *t) { + clear_proc(&t->proc_status); + clear_proc(&t->proc_control); + clear_proc(&t->proc_list); + clear_proc(&t->proc_blist); + clear_proc(&t->proc_calls); + clear_proc(&t->proc_root); +} + +static void table_put(struct rtpengine_table *t) { int i, j, k; struct re_dest_addr *rda; struct re_bucket *b; @@ -573,7 +841,7 @@ static void table_push(struct rtpengine_table *t) { if (!b->ports_lo[j]) continue; b->ports_lo[j]->table = -1; - target_push(b->ports_lo[j]); + target_put(b->ports_lo[j]); b->ports_lo[j] = NULL; } @@ -585,13 +853,7 @@ static void table_push(struct rtpengine_table *t) { t->dest_addr_hash.addrs[k] = NULL; } - - clear_proc(&t->status); - clear_proc(&t->control); - clear_proc(&t->list); - clear_proc(&t->blist); - clear_proc(&t->proc); - + clear_table_proc_files(t); kfree(t); module_put(THIS_MODULE); @@ -599,9 +861,62 @@ static void table_push(struct rtpengine_table *t) { +static inline void free_packet(struct re_stream_packet *packet) { + if (packet->skbuf) + kfree_skb(packet->skbuf); + kfree(packet); +} + +/* caller is responsible for locking */ +static void clear_stream_packets(struct re_stream *stream) { + struct re_stream_packet *packet; + + while (!list_empty(&stream->packet_list)) { + DBG("clearing packet from queue\n"); + packet = list_first_entry(&stream->packet_list, struct re_stream_packet, list_entry); + list_del(&packet->list_entry); + free_packet(packet); + } +} +static void stream_put(struct re_stream *stream) { + DBG("stream_put()\n"); + + if (!stream) + return; + + if (!atomic_dec_and_test(&stream->refcnt)) + return; + + DBG("Freeing stream object\n"); + + clear_stream_packets(stream); + clear_proc(&stream->file); + + kfree(stream); +} +static void call_put(struct re_call *call) { + DBG("call_put()\n"); + + if (!call) + return; + + if (!atomic_dec_and_test(&call->refcnt)) + return; + + DBG("Freeing call object\n"); + + if (!list_empty(&call->streams)) + panic("BUG! streams list not empty in call"); + + kfree(call); +} + + + static int unlink_table(struct rtpengine_table *t) { unsigned long flags; + struct re_call *call; if (t->id >= MAX_ID) return -EINVAL; @@ -621,13 +936,17 @@ static int unlink_table(struct rtpengine_table *t) { t->id = -1; write_unlock_irqrestore(&table_lock, flags); - clear_proc(&t->status); - clear_proc(&t->control); - clear_proc(&t->list); - clear_proc(&t->blist); - clear_proc(&t->proc); + _w_lock(&calls.lock, flags); + while (!list_empty(&t->calls)) { + call = list_first_entry(&t->calls, struct re_call, table_entry); + _w_unlock(&calls.lock, flags); + del_call(call, t); /* removes it from this list */ + _w_lock(&calls.lock, flags); + } + _w_unlock(&calls.lock, flags); - table_push(t); + clear_table_proc_files(t); + table_put(t); return 0; } @@ -635,7 +954,7 @@ static int unlink_table(struct rtpengine_table *t) { -static struct rtpengine_table *get_table(u_int32_t id) { +static struct rtpengine_table *get_table(unsigned int id) { struct rtpengine_table *t; unsigned long flags; @@ -645,7 +964,7 @@ static struct rtpengine_table *get_table(u_int32_t id) { read_lock_irqsave(&table_lock, flags); t = table[id]; if (t) - table_hold(t); + ref_get(t); read_unlock_irqrestore(&table_lock, flags); return t; @@ -681,7 +1000,7 @@ static ssize_t proc_status(struct file *f, char __user *b, size_t l, loff_t *o) len += sprintf(buf + len, "Targets: %u\n", t->num_targets); read_unlock_irqrestore(&t->target_lock, flags); - table_push(t); + table_put(t); if (copy_to_user(b, buf, len)) return -EFAULT; @@ -693,6 +1012,9 @@ static ssize_t proc_status(struct file *f, char __user *b, size_t l, loff_t *o) static int proc_main_list_open(struct inode *i, struct file *f) { + int err; + if ((err = proc_generic_open_modref(i, f))) + return err; return seq_open(f, &proc_main_list_seq_ops); } @@ -734,7 +1056,7 @@ static int proc_main_list_show(struct seq_file *f, void *v) { struct rtpengine_table *g = v; seq_printf(f, "%u\n", g->id); - table_push(g); + table_put(g); return 0; } @@ -749,36 +1071,47 @@ static inline unsigned char bitfield_next_slot(unsigned int slot) { c += sizeof(unsigned long) * 8; return c; } -static inline unsigned int bitfield_slot(unsigned char i) { +static inline unsigned int bitfield_slot(unsigned int i) { return i / (sizeof(unsigned long) * 8); } -static inline unsigned int bitfield_bit(unsigned char i) { +static inline unsigned int bitfield_bit(unsigned int i) { return i % (sizeof(unsigned long) * 8); } -static inline void bitfield_set(struct re_bitfield *bf, unsigned char i) { +static inline int bitfield_set(unsigned long *bf, unsigned int i) { unsigned int b, m; unsigned long k; b = bitfield_slot(i); m = bitfield_bit(i); k = 1UL << m; - if ((bf->b[b] & k)) - return; - bf->b[b] |= k; - bf->used++; + if ((bf[b] & k)) + return 0; + bf[b] |= k; + return 1; } -static inline void bitfield_clear(struct re_bitfield *bf, unsigned char i) { +static inline int bitfield_clear(unsigned long *bf, unsigned int i) { unsigned int b, m; unsigned long k; b = bitfield_slot(i); m = bitfield_bit(i); k = 1UL << m; - if (!(bf->b[b] & k)) - return; - bf->b[b] &= ~k; - bf->used--; + if (!(bf[b] & k)) + return 0; + bf[b] &= ~k; + return 1; } +static inline void re_bitfield_set(struct re_bitfield *bf, unsigned char i) { + if (bitfield_set(bf->b, i)) + bf->used++; +} +static inline void re_bitfield_clear(struct re_bitfield *bf, unsigned char i) { + if (bitfield_clear(bf->b, i)) + bf->used--; +} + + + static inline struct rtpengine_target *find_next_target(struct rtpengine_table *t, int *addr_bucket, int *port) { @@ -847,7 +1180,7 @@ static inline struct rtpengine_target *find_next_target(struct rtpengine_table * goto next_lo; } - target_hold(g); + target_get(g); break; next_lo: @@ -875,13 +1208,17 @@ next_rda: static int proc_blist_open(struct inode *i, struct file *f) { u_int32_t id; struct rtpengine_table *t; + int err; + + if ((err = proc_generic_open_modref(i, f))) + return err; id = (u_int32_t) (unsigned long) PDE_DATA(i); t = get_table(id); if (!t) return -ENOENT; - table_push(t); + table_put(t); return 0; } @@ -895,7 +1232,9 @@ static int proc_blist_close(struct inode *i, struct file *f) { if (!t) return 0; - table_push(t); + table_put(t); + + proc_generic_close_modref(i, f); return 0; } @@ -952,17 +1291,17 @@ static ssize_t proc_blist_read(struct file *f, char __user *b, size_t l, loff_t op.target.encrypt.last_index = g->target.encrypt.last_index; spin_unlock_irqrestore(&g->encrypt.lock, flags); - target_push(g); + target_put(g); err = -EFAULT; if (copy_to_user(b, &op, sizeof(op))) goto err; - table_push(t); + table_put(t); return l; err: - table_push(t); + table_put(t); return err; } @@ -972,11 +1311,14 @@ static int proc_list_open(struct inode *i, struct file *f) { u_int32_t id; struct rtpengine_table *t; + if ((err = proc_generic_open_modref(i, f))) + return err; + id = (u_int32_t) (unsigned long) PDE_DATA(i); t = get_table(id); if (!t) return -ENOENT; - table_push(t); + table_put(t); err = seq_open(f, &proc_list_seq_ops); if (err) @@ -1014,7 +1356,7 @@ static void *proc_list_next(struct seq_file *f, void *v, loff_t *o) { /* v is in g = find_next_target(t, &addr_bucket, &port); *o = (addr_bucket << 17) | port; - table_push(t); + table_put(t); return g; } @@ -1101,7 +1443,7 @@ static int proc_list_show(struct seq_file *f, void *v) { if (g->target.dtls) seq_printf(f, " option: dtls\n"); - target_push(g); + target_put(g); return 0; } @@ -1211,11 +1553,11 @@ static int table_del_target(struct rtpengine_table *t, const struct re_address * goto out; b->ports_lo[lo] = NULL; - bitfield_clear(&b->ports_lo_bf, lo); + re_bitfield_clear(&b->ports_lo_bf, lo); t->num_targets--; if (!b->ports_lo_bf.used) { rda->ports_hi[hi] = NULL; - bitfield_clear(&rda->ports_hi_bf, hi); + re_bitfield_clear(&rda->ports_hi_bf, hi); } else b = NULL; @@ -1230,7 +1572,7 @@ out: if (b) kfree(b); - target_push(g); + target_put(g); return 0; } @@ -1597,10 +1939,10 @@ static int table_new_target(struct rtpengine_table *t, struct rtpengine_target_i /* initializing */ err = -ENOMEM; - g = kmalloc(sizeof(*g), GFP_KERNEL); + g = kzalloc(sizeof(*g), GFP_KERNEL); if (!g) goto fail1; - memset(g, 0, sizeof(*g)); + g->table = t->id; atomic_set(&g->refcnt, 1); spin_lock_init(&g->decrypt.lock); @@ -1645,11 +1987,11 @@ retry: write_unlock_irqrestore(&t->target_lock, flags); - rda = kmalloc(sizeof(*rda), GFP_KERNEL); + rda = kzalloc(sizeof(*rda), GFP_KERNEL); err = -ENOMEM; if (!rda) goto fail2; - memset(rda, 0, sizeof(*rda)); + memcpy(&rda->destination, &i->local, sizeof(rda->destination)); write_lock_irqsave(&t->target_lock, flags); @@ -1661,7 +2003,7 @@ retry: } t->dest_addr_hash.addrs[rh_it] = rda; - bitfield_set(&t->dest_addr_hash.addrs_bf, rh_it); + re_bitfield_set(&t->dest_addr_hash.addrs_bf, rh_it); got_rda: /* find or allocate re_bucket */ @@ -1675,17 +2017,16 @@ got_rda: write_unlock_irqrestore(&t->target_lock, flags); - b = kmalloc(sizeof(*b), GFP_KERNEL); + b = kzalloc(sizeof(*b), GFP_KERNEL); err = -ENOMEM; if (!b) goto fail2; - memset(b, 0, sizeof(*b)); write_lock_irqsave(&t->target_lock, flags); if (!rda->ports_hi[hi]) { rda->ports_hi[hi] = b; - bitfield_set(&rda->ports_hi_bf, hi); + re_bitfield_set(&rda->ports_hi_bf, hi); } else { ba = b; @@ -1716,7 +2057,7 @@ got_bucket: err = -EEXIST; if (b->ports_lo[lo]) goto fail4; - bitfield_set(&b->ports_lo_bf, lo); + re_bitfield_set(&b->ports_lo_bf, lo); t->num_targets++; } @@ -1727,7 +2068,7 @@ got_bucket: if (ba) kfree(ba); if (og) - target_push(og); + target_put(og); return 0; @@ -1764,7 +2105,7 @@ static struct rtpengine_target *get_target(struct rtpengine_table *t, const stru rda = find_dest_addr(&t->dest_addr_hash, local); r = rda ? (rda->ports_hi[hi] ? rda->ports_hi[hi]->ports_lo[lo] : NULL) : NULL; if (r) - target_hold(r); + target_get(r); read_unlock_irqrestore(&t->target_lock, flags); return r; @@ -1774,16 +2115,19 @@ static struct rtpengine_target *get_target(struct rtpengine_table *t, const stru -static int proc_main_control_open(struct inode *inode, struct file *file) { +static int proc_generic_open_modref(struct inode *inode, struct file *file) { if (!try_module_get(THIS_MODULE)) return -ENXIO; return 0; } - -static int proc_main_control_close(struct inode *inode, struct file *file) { +static int proc_generic_close_modref(struct inode *inode, struct file *file) { module_put(THIS_MODULE); return 0; } +static int proc_generic_seqrelease_modref(struct inode *inode, struct file *file) { + proc_generic_close_modref(inode, file); + return seq_release(inode, file); +} static ssize_t proc_main_control_write(struct file *file, const char __user *buf, size_t buflen, loff_t *off) { char b[30]; @@ -1807,7 +2151,7 @@ static ssize_t proc_main_control_write(struct file *file, const char __user *buf t = new_table_link((u_int32_t) id); if (!t) return -EEXIST; - table_push(t); + table_put(t); t = NULL; } else if (!strncmp(b, "del ", 4)) { @@ -1820,7 +2164,7 @@ static ssize_t proc_main_control_write(struct file *file, const char __user *buf if (!t) return -ENOENT; err = unlink_table(t); - table_push(t); + table_put(t); t = NULL; if (err) return err; @@ -1839,6 +2183,10 @@ static int proc_control_open(struct inode *inode, struct file *file) { u_int32_t id; struct rtpengine_table *t; unsigned long flags; + int err; + + if ((err = proc_generic_open_modref(inode, file))) + return err; id = (u_int32_t) (unsigned long) PDE_DATA(inode); t = get_table(id); @@ -1848,13 +2196,13 @@ static int proc_control_open(struct inode *inode, struct file *file) { write_lock_irqsave(&table_lock, flags); if (t->pid) { write_unlock_irqrestore(&table_lock, flags); - table_push(t); + table_put(t); return -EBUSY; } t->pid = current->tgid; write_unlock_irqrestore(&table_lock, flags); - table_push(t); + table_put(t); return 0; } @@ -1872,123 +2220,878 @@ static int proc_control_close(struct inode *inode, struct file *file) { t->pid = 0; write_unlock_irqrestore(&table_lock, flags); - table_push(t); + table_put(t); + + proc_generic_close_modref(inode, file); return 0; } -static ssize_t proc_control_write(struct file *file, const char __user *buf, size_t buflen, loff_t *off) { - struct inode *inode; - u_int32_t id; - struct rtpengine_table *t; - struct rtpengine_message msg; - int err; +/* array must be locked */ +static int auto_array_find_free_index(struct re_auto_array *a) { + void *ptr; + unsigned int u, idx; - if (buflen != sizeof(msg)) - return -EIO; + DBG("auto_array_find_free_index()\n"); - inode = file->f_path.dentry->d_inode; - id = (u_int32_t) (unsigned long) PDE_DATA(inode); - t = get_table(id); - if (!t) - return -ENOENT; + if (!list_empty(&a->free_list)) { + DBG("returning from free list\n"); + return pop_free_list_entry(a); + } - err = -EFAULT; - if (copy_from_user(&msg, buf, sizeof(msg))) - goto err; + for (idx = 0; idx < a->array_len / (sizeof(unsigned long) * 8); idx++) { + if (~a->used_bitfield[idx]) + goto found; + } - switch (msg.cmd) { - case MMG_NOOP: - DBG("noop.\n"); - break; + /* nothing free found - extend array */ + DBG("no free slot found, extending array\n"); + + u = a->array_len * 2; + if (unlikely(!u)) + u = 256; /* XXX make configurable? */ + + DBG("extending array from %u to %u\n", a->array_len, u); + + ptr = krealloc(a->array, sizeof(*a->array) * u, GFP_ATOMIC); + if (!ptr) + return -ENOMEM; + a->array = ptr; + DBG("zeroing main array starting at idx %u for %lu bytes\n", + a->array_len, (u - a->array_len) * sizeof(*a->array)); + memset(&a->array[a->array_len], 0, + (u - a->array_len) * sizeof(*a->array)); + + ptr = krealloc(a->used_bitfield, u / 8, GFP_ATOMIC); + if (!ptr) + return -ENOMEM; + a->used_bitfield = ptr; + DBG("zeroing bitfield array starting at idx %lu for %u bytes\n", + a->array_len / (sizeof(unsigned long) * 8), + (u - a->array_len) / 8); + memset(&a->used_bitfield[a->array_len / (sizeof(unsigned long) * 8)], 0, + (u - a->array_len) / 8); + + idx = a->array_len / (sizeof(unsigned long) * 8); + a->array_len = u; + +found: + /* got our bitfield index, now look for the slot */ + + DBG("found unused slot at index %u\n", idx); + + idx = idx * sizeof(unsigned long) * 8; + for (u = 0; u < sizeof(unsigned long) * 8; u++) { + if (!a->array[idx + u]) + goto found2; + } + panic("BUG while looking for unused index"); - case MMG_ADD: - err = table_new_target(t, &msg.target, 0); - if (err) - goto err; - break; +found2: + idx += u; + DBG("unused idx is %u\n", idx); - case MMG_DEL: - err = table_del_target(t, &msg.target.local); - if (err) - goto err; - break; + return idx; +} - case MMG_UPDATE: - err = table_new_target(t, &msg.target, 1); - if (err) - goto err; - break; - default: - printk(KERN_WARNING "xt_RTPENGINE unimplemented op %u\n", msg.cmd); - err = -EINVAL; - goto err; - } - table_push(t); - return buflen; -err: - table_push(t); - return err; -} +/* lock must be held */ +static struct re_call *get_call(struct rtpengine_table *table, unsigned int idx) { + struct re_call *ret; + if (idx >= calls.array_len) + return NULL; + ret = calls.array[idx]; + if (!ret) + return NULL; + if (table && ret->table_id != table->id) + return NULL; + return ret; +} +/* handles the locking (read) and reffing */ +static struct re_call *get_call_lock(struct rtpengine_table *table, unsigned int idx) { + struct re_call *ret; + unsigned long flags; + DBG("entering get_call_lock()\n"); + _r_lock(&calls.lock, flags); -static int send_proxy_packet4(struct sk_buff *skb, struct re_address *src, struct re_address *dst, - unsigned char tos, const struct xt_action_param *par) -{ - struct iphdr *ih; - struct udphdr *uh; - unsigned int datalen; + DBG("calls.lock acquired\n"); - datalen = skb->len; + ret = get_call(table, idx); + if (ret) + ref_get(ret); + else + DBG("call not found\n"); - uh = (void *) skb_push(skb, sizeof(*uh)); - skb_reset_transport_header(skb); - ih = (void *) skb_push(skb, sizeof(*ih)); - skb_reset_network_header(skb); + _r_unlock(&calls.lock, flags); + DBG("calls.lock unlocked\n"); + return ret; +} +/* lock must be held */ +static struct re_stream *get_stream(struct re_call *call, unsigned int idx) { + struct re_stream *ret; - DBG("datalen=%u network_header=%p transport_header=%p\n", datalen, skb_network_header(skb), skb_transport_header(skb)); + if (idx >= streams.array_len) + return NULL; - datalen += sizeof(*uh); - *uh = (struct udphdr) { - .source = htons(src->port), - .dest = htons(dst->port), - .len = htons(datalen), - }; - *ih = (struct iphdr) { - .version = 4, - .ihl = 5, - .tos = tos, - .tot_len = htons(sizeof(*ih) + datalen), - .ttl = 64, - .protocol = IPPROTO_UDP, - .saddr = src->u.ipv4, - .daddr = dst->u.ipv4, - }; + ret = streams.array[idx]; + if (!ret) + return NULL; + if (call && ret->info.call_idx != call->info.call_idx) + return NULL; + return ret; +} +/* handles the locking (read) and reffing */ +static struct re_stream *get_stream_lock(struct re_call *call, unsigned int idx) { + struct re_stream *ret; + unsigned long flags; - skb->csum_start = skb_transport_header(skb) - skb->head; - skb->csum_offset = offsetof(struct udphdr, check); - uh->check = csum_tcpudp_magic(src->u.ipv4, dst->u.ipv4, datalen, IPPROTO_UDP, csum_partial(uh, datalen, 0)); - if (uh->check == 0) - uh->check = CSUM_MANGLED_0; + DBG("entering get_stream_lock()\n"); - skb->protocol = htons(ETH_P_IP); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0) - if (ip_route_me_harder(par->net, skb, RTN_UNSPEC)) -#else - if (ip_route_me_harder(skb, RTN_UNSPEC)) -#endif - goto drop; + _r_lock(&streams.lock, flags); - skb->ip_summed = CHECKSUM_NONE; + DBG("streams.lock acquired\n"); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0) + ret = get_stream(call, idx); + if (ret) + ref_get(ret); + else + DBG("stream not found\n"); + + _r_unlock(&streams.lock, flags); + DBG("streams.lock unlocked\n"); + return ret; +} + + + + + +static int table_new_call(struct rtpengine_table *table, struct rtpengine_call_info *info) { + int err; + struct re_call *call, *hash_entry; + unsigned int idx; + unsigned long flags; + + /* validation */ + + if (info->call_id[0] == '\0') + return -EINVAL; + if (!memchr(info->call_id, '\0', sizeof(info->call_id))) + return -EINVAL; + + DBG("Creating new call object\n"); + + /* allocate and initialize */ + + call = kzalloc(sizeof(*call), GFP_KERNEL); + if (!call) + return -ENOMEM; + + atomic_set(&call->refcnt, 1); + call->table_id = table->id; + INIT_LIST_HEAD(&call->streams); + + /* check for name collisions */ + + call->hash_bucket = crc32_le(0x52342, info->call_id, strlen(info->call_id)); + call->hash_bucket = call->hash_bucket & ((1 << HASH_BITS) - 1); + + spin_lock_irqsave(&table->calls_hash_lock[call->hash_bucket], flags); + + hlist_for_each_entry(hash_entry, &table->calls_hash[call->hash_bucket], calls_hash_entry) { + if (!strcmp(hash_entry->info.call_id, info->call_id)) + goto found; + } + goto not_found; +found: + spin_unlock_irqrestore(&table->calls_hash_lock[call->hash_bucket], flags); + printk(KERN_ERR "Call name collision: %s\n", info->call_id); + err = -EEXIST; + goto fail2; + +not_found: + hlist_add_head(&call->calls_hash_entry, &table->calls_hash[call->hash_bucket]); + ref_get(call); + spin_unlock_irqrestore(&table->calls_hash_lock[call->hash_bucket], flags); + + /* create proc */ + + call->root = proc_mkdir_user(info->call_id, S_IRUGO | S_IXUGO, table->proc_calls); + err = -ENOMEM; + if (!call->root) + goto fail4; + + _w_lock(&calls.lock, flags); + + idx = err = auto_array_find_free_index(&calls); + if (err < 0) + goto fail3; + set_auto_array_index(&calls, idx, call); /* handing over ref */ + + info->call_idx = idx; + memcpy(&call->info, info, sizeof(call->info)); + + list_add(&call->table_entry, &table->calls); /* new ref here */ + ref_get(call); + + _w_unlock(&calls.lock, flags); + + return 0; + +fail3: + _w_unlock(&calls.lock, flags); +fail4: + spin_lock_irqsave(&table->calls_hash_lock[call->hash_bucket], flags); + hlist_del(&call->calls_hash_entry); + spin_unlock_irqrestore(&table->calls_hash_lock[call->hash_bucket], flags); + call_put(call); +fail2: + call_put(call); + return err; +} + +static int table_del_call(struct rtpengine_table *table, unsigned int idx) { + int err; + struct re_call *call = NULL; + + call = get_call_lock(table, idx); + err = -ENOENT; + if (!call) + goto out; + + del_call(call, table); + + err = 0; + +out: + if (call) + call_put(call); + + return err; +} +/* must be called lock-free */ +static void del_call(struct re_call *call, struct rtpengine_table *table) { + struct re_stream *stream; + unsigned long flags; + + DBG("del_call()\n"); + + /* the only references left might be the ones in the lists, so get one until we're done */ + ref_get(call); + + _w_lock(&calls.lock, flags); + + if (!list_empty(&call->table_entry)) { + list_del_init(&call->table_entry); + call_put(call); + } + + if (calls.array[call->info.call_idx] == call) { + auto_array_clear_index(&calls, call->info.call_idx); + call_put(call); + } + + _w_unlock(&calls.lock, flags); + + DBG("locking streams.lock\n"); + _w_lock(&streams.lock, flags); + while (!list_empty(&call->streams)) { + stream = list_first_entry(&call->streams, struct re_stream, call_entry); + ref_get(stream); + _w_unlock(&streams.lock, flags); + del_stream(stream, table); /* removes it from this list */ + DBG("re-locking streams.lock\n"); + _w_lock(&streams.lock, flags); + } + _w_unlock(&streams.lock, flags); + + DBG("clearing call proc files\n"); + clear_proc(&call->root); + + DBG("locking table's call hash\n"); + spin_lock_irqsave(&table->calls_hash_lock[call->hash_bucket], flags); + if (!hlist_unhashed(&call->calls_hash_entry)) { + hlist_del_init(&call->calls_hash_entry); + call_put(call); + } + spin_unlock_irqrestore(&table->calls_hash_lock[call->hash_bucket], flags); + + DBG("del_call() done, releasing ref\n"); + call_put(call); /* might be the last ref */ +} + + + + + +static int table_new_stream(struct rtpengine_table *table, struct rtpengine_stream_info *info) { + int err; + struct re_call *call; + struct re_stream *stream, *hash_entry; + unsigned long flags; + unsigned int idx; + struct proc_dir_entry *pde; + + /* validation */ + + if (info->stream_name[0] == '\0') + return -EINVAL; + if (!memchr(info->stream_name, '\0', sizeof(info->stream_name))) + return -EINVAL; + + /* get call object */ + + call = get_call_lock(table, info->call_idx); + if (!call) + return -ENOENT; + + DBG("Creating new stream object\n"); + + /* allocate and initialize */ + + err = -ENOMEM; + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + goto fail2; + + atomic_set(&stream->refcnt, 1); + INIT_LIST_HEAD(&stream->packet_list); + spin_lock_init(&stream->packet_list_lock); + init_waitqueue_head(&stream->wq); + + /* check for name collisions */ + + stream->hash_bucket = crc32_le(0x52342 ^ info->call_idx, info->stream_name, strlen(info->stream_name)); + stream->hash_bucket = stream->hash_bucket & ((1 << HASH_BITS) - 1); + + spin_lock_irqsave(&table->streams_hash_lock[stream->hash_bucket], flags); + + hlist_for_each_entry(hash_entry, &table->streams_hash[stream->hash_bucket], streams_hash_entry) { + if (hash_entry->info.call_idx == info->call_idx + && !strcmp(hash_entry->info.stream_name, info->stream_name)) + goto found; + } + goto not_found; +found: + spin_unlock_irqrestore(&table->streams_hash_lock[stream->hash_bucket], flags); + printk(KERN_ERR "Stream name collision: %s\n", info->stream_name); + err = -EEXIST; + goto fail3; + +not_found: + hlist_add_head(&stream->streams_hash_entry, &table->streams_hash[stream->hash_bucket]); + ref_get(stream); + spin_unlock_irqrestore(&table->streams_hash_lock[stream->hash_bucket], flags); + + /* add into array */ + + _w_lock(&streams.lock, flags); + + idx = err = auto_array_find_free_index(&streams); + if (err < 0) + goto fail4; + set_auto_array_index(&streams, idx, stream); /* handing over ref */ + + /* copy info */ + + info->stream_idx = idx; + memcpy(&stream->info, info, sizeof(call->info)); + if (!stream->info.max_packets) + stream->info.max_packets = stream_packets_list_limit; + + list_add(&stream->call_entry, &call->streams); /* new ref here */ + ref_get(stream); + + _w_unlock(&streams.lock, flags); + + /* proc_ functions may sleep, so this must be done outside of the lock */ + pde = stream->file = proc_create_user(info->stream_name, S_IFREG | S_IRUSR | S_IRGRP, call->root, + &proc_stream_ops, (void *) (unsigned long) info->stream_idx); + err = -ENOMEM; + if (!pde) + goto fail5; + + call_put(call); + + return 0; + +fail5: + _w_lock(&streams.lock, flags); + auto_array_clear_index(&streams, idx); +fail4: + _w_unlock(&streams.lock, flags); + + spin_lock_irqsave(&table->streams_hash_lock[stream->hash_bucket], flags); + hlist_del(&stream->streams_hash_entry); + spin_unlock_irqrestore(&table->streams_hash_lock[stream->hash_bucket], flags); + stream_put(stream); +fail3: + stream_put(stream); +fail2: + call_put(call); + return err; +} + +/* must be called lock-free and with one reference held, which will be released */ +static void del_stream(struct re_stream *stream, struct rtpengine_table *table) { + unsigned long flags; + + DBG("del_stream()\n"); + + DBG("locking stream's packet list lock\n"); + spin_lock_irqsave(&stream->packet_list_lock, flags); + + if (stream->eof) { + /* already done this */ + spin_unlock_irqrestore(&stream->packet_list_lock, flags); + DBG("stream is EOF\n"); + stream_put(stream); + return; + } + + stream->eof = 1; + clear_stream_packets(stream); + + spin_unlock_irqrestore(&stream->packet_list_lock, flags); + + DBG("stream is finished (EOF), waking up threads\n"); + wake_up_interruptible(&stream->wq); + /* sleeping readers will now close files */ + + DBG("clearing stream proc file\n"); + /* this blocks until the files have been closed */ + clear_proc(&stream->file); + + DBG("clearing stream from streams_hash\n"); + spin_lock_irqsave(&table->streams_hash_lock[stream->hash_bucket], flags); + if (!hlist_unhashed(&stream->streams_hash_entry)) { + hlist_del_init(&stream->streams_hash_entry); + stream_put(stream); + } + spin_unlock_irqrestore(&table->streams_hash_lock[stream->hash_bucket], flags); + + _w_lock(&streams.lock, flags); + if (!list_empty(&stream->call_entry)) { + DBG("clearing stream's call_entry\n"); + list_del_init(&stream->call_entry); + stream_put(stream); + } + if (streams.array[stream->info.stream_idx] == stream) { + DBG("clearing stream's stream_idx entry\n"); + auto_array_clear_index(&streams, stream->info.stream_idx); + stream_put(stream); + } + _w_unlock(&streams.lock, flags); + + DBG("del_stream() done, releasing ref\n"); + stream_put(stream); /* might be the last ref */ +} + +static int table_del_stream(struct rtpengine_table *table, const struct rtpengine_stream_info *info) { + int err; + struct re_call *call; + struct re_stream *stream; + + DBG("table_del_stream()\n"); + + call = get_call_lock(table, info->call_idx); + err = -ENOENT; + if (!call) + return -ENOENT; + + stream = get_stream_lock(call, info->stream_idx); + err = -ENOENT; + if (!stream) + goto out; + + del_stream(stream, table); + + err = 0; + +out: + call_put(call); + return err; +} + + + + +static ssize_t proc_stream_read(struct file *f, char __user *b, size_t l, loff_t *o) { + unsigned int stream_idx = (unsigned int) (unsigned long) PDE_DATA(f->f_path.dentry->d_inode); + struct re_stream *stream; + unsigned long flags; + struct re_stream_packet *packet; + ssize_t ret; + const char *to_copy; + + DBG("entering proc_stream_read()\n"); + + stream = get_stream_lock(NULL, stream_idx); + if (!stream) + return -EINVAL; + + DBG("locking stream's packet list lock\n"); + spin_lock_irqsave(&stream->packet_list_lock, flags); + + while (list_empty(&stream->packet_list) && !stream->eof) { + spin_unlock_irqrestore(&stream->packet_list_lock, flags); + DBG("list is empty\n"); + ret = -EAGAIN; + if ((f->f_flags & O_NONBLOCK)) + goto out; + DBG("going to sleep\n"); + ret = -ERESTARTSYS; + if (wait_event_interruptible(stream->wq, !list_empty(&stream->packet_list) || stream->eof)) + goto out; + DBG("awakened\n"); + spin_lock_irqsave(&stream->packet_list_lock, flags); + } + + ret = 0; + if (stream->eof) { + DBG("eof\n"); + spin_unlock_irqrestore(&stream->packet_list_lock, flags); + goto out; + } + + DBG("removing packet from queue, reading %i bytes\n", (int) l); + packet = list_first_entry(&stream->packet_list, struct re_stream_packet, list_entry); + list_del(&packet->list_entry); + stream->list_count--; + + spin_unlock_irqrestore(&stream->packet_list_lock, flags); + + if (packet->buflen) { + ret = packet->buflen; + to_copy = packet->buf; + DBG("packet is from userspace, %i bytes\n", (int) ret); + } + else if (packet->skbuf) { + ret = packet->skbuf->len; + to_copy = packet->skbuf->data; + DBG("packet is from kernel, %i bytes\n", (int) ret); + } + else { + printk(KERN_WARNING "BUG in packet stream list buffer\n"); + ret = -ENXIO; + goto err; + } + + if (ret > l) + ret = l; + if (copy_to_user(b, to_copy, ret)) + ret = -EFAULT; + +err: + free_packet(packet); + +out: + stream_put(stream); + return ret; +} +static unsigned int proc_stream_poll(struct file *f, struct poll_table_struct *p) { + unsigned int stream_idx = (unsigned int) (unsigned long) PDE_DATA(f->f_path.dentry->d_inode); + struct re_stream *stream; + unsigned long flags; + unsigned int ret = 0; + + DBG("entering proc_stream_poll()\n"); + + stream = get_stream_lock(NULL, stream_idx); + if (!stream) + return POLLERR; + + DBG("locking stream's packet list lock\n"); + spin_lock_irqsave(&stream->packet_list_lock, flags); + + if (!list_empty(&stream->packet_list) || stream->eof) + ret |= POLLIN | POLLRDNORM; + + DBG("returning from proc_stream_poll()\n"); + + spin_unlock_irqrestore(&stream->packet_list_lock, flags); + + poll_wait(f, &stream->wq, p); + + stream_put(stream); + + return ret; +} + + + + +static void add_stream_packet(struct re_stream *stream, struct re_stream_packet *packet) { + int err; + unsigned long flags; + LIST_HEAD(delete_list); + + /* append */ + + DBG("entering add_stream_packet()\n"); + DBG("locking stream's packet list lock\n"); + spin_lock_irqsave(&stream->packet_list_lock, flags); + + err = 0; + if (stream->eof) + goto err; /* we accept, but ignore/discard */ + + DBG("adding packet to queue\n"); + list_add_tail(&packet->list_entry, &stream->packet_list); + stream->list_count++; + + DBG("%u packets now in queue\n", stream->list_count); + + /* discard older packets */ + while (stream->list_count > stream->info.max_packets) { + DBG("discarding old packet from queue\n"); + packet = list_first_entry(&stream->packet_list, struct re_stream_packet, list_entry); + list_del(&packet->list_entry); + list_add(&packet->list_entry, &delete_list); + stream->list_count--; + } + + spin_unlock_irqrestore(&stream->packet_list_lock, flags); + + DBG("stream's packet list lock is unlocked, now awakening processes\n"); + + wake_up_interruptible(&stream->wq); + + while (!list_empty(&delete_list)) { + packet = list_first_entry(&delete_list, struct re_stream_packet, list_entry); + list_del(&packet->list_entry); + free_packet(packet); + } + + return; + +err: + DBG("error adding packet to stream\n"); + spin_unlock_irqrestore(&stream->packet_list_lock, flags); + free_packet(packet); + return; +} + +static int stream_packet(struct rtpengine_table *t, const struct rtpengine_packet_info *info, + const unsigned char *data, unsigned int len) +{ + struct re_stream *stream; + int err; + struct re_stream_packet *packet; + + if (!len) /* can't have empty packets */ + return -EINVAL; + + DBG("received %u bytes of data from userspace\n", len); + + err = -ENOENT; + stream = get_stream_lock(NULL, info->stream_idx); + if (!stream) + goto out; + + DBG("data for stream %s\n", stream->info.stream_name); + + /* alloc and copy */ + + err = -ENOMEM; + packet = kmalloc(sizeof(*packet) + len, GFP_KERNEL); + if (!packet) + goto out2; + memset(packet, 0, sizeof(*packet)); + + memcpy(packet->buf, data, len); + packet->buflen = len; + + /* append */ + add_stream_packet(stream, packet); + + err = 0; + goto out2; + +out2: + stream_put(stream); +out: + return err; +} + + + + + +static inline ssize_t proc_control_read_write(struct file *file, char __user *ubuf, size_t buflen, loff_t *off, + int writeable) +{ + struct inode *inode; + u_int32_t id; + struct rtpengine_table *t; + struct rtpengine_message msgbuf; + struct rtpengine_message *msg; + int err; + + if (buflen < sizeof(*msg)) + return -EIO; + if (buflen == sizeof(*msg)) + msg = &msgbuf; + else { /* > */ + msg = kmalloc(buflen, GFP_KERNEL); + if (!msg) + return -ENOMEM; + } + + inode = file->f_path.dentry->d_inode; + id = (u_int32_t) (unsigned long) PDE_DATA(inode); + t = get_table(id); + err = -ENOENT; + if (!t) + goto out; + + err = -EFAULT; + if (copy_from_user(msg, ubuf, buflen)) + goto err; + + err = 0; + + switch (msg->cmd) { + case REMG_NOOP: + DBG("noop.\n"); + break; + + case REMG_ADD: + err = table_new_target(t, &msg->u.target, 0); + break; + + case REMG_DEL: + err = table_del_target(t, &msg->u.target.local); + break; + + case REMG_UPDATE: + err = table_new_target(t, &msg->u.target, 1); + break; + + case REMG_ADD_CALL: + err = -EINVAL; + if (!writeable) + goto err; + err = table_new_call(t, &msg->u.call); + break; + + case REMG_DEL_CALL: + err = table_del_call(t, msg->u.call.call_idx); + break; + + case REMG_ADD_STREAM: + err = -EINVAL; + if (!writeable) + goto err; + err = table_new_stream(t, &msg->u.stream); + break; + + case REMG_DEL_STREAM: + err = table_del_stream(t, &msg->u.stream); + break; + + case REMG_PACKET: + err = stream_packet(t, &msg->u.packet, msg->data, buflen - sizeof(*msg)); + break; + + default: + printk(KERN_WARNING "xt_RTPENGINE unimplemented op %u\n", msg->cmd); + err = -EINVAL; + break; + } + + table_put(t); + + if (err) + goto out; + + if (writeable) { + err = -EFAULT; + if (copy_to_user(ubuf, msg, sizeof(*msg))) + goto out; + } + + if (msg != &msgbuf) + kfree(msg); + + return buflen; + +err: + table_put(t); +out: + if (msg != &msgbuf) + kfree(msg); + return err; +} +static ssize_t proc_control_write(struct file *file, const char __user *ubuf, size_t buflen, loff_t *off) { + return proc_control_read_write(file, (char __user *) ubuf, buflen, off, 0); +} +static ssize_t proc_control_read(struct file *file, char __user *ubuf, size_t buflen, loff_t *off) { + return proc_control_read_write(file, ubuf, buflen, off, 1); +} + + + + + + +static int send_proxy_packet4(struct sk_buff *skb, struct re_address *src, struct re_address *dst, + unsigned char tos, const struct xt_action_param *par) +{ + struct iphdr *ih; + struct udphdr *uh; + unsigned int datalen; + + datalen = skb->len; + + uh = (void *) skb_push(skb, sizeof(*uh)); + skb_reset_transport_header(skb); + ih = (void *) skb_push(skb, sizeof(*ih)); + skb_reset_network_header(skb); + + DBG("datalen=%u network_header=%p transport_header=%p\n", datalen, skb_network_header(skb), skb_transport_header(skb)); + + datalen += sizeof(*uh); + *uh = (struct udphdr) { + .source = htons(src->port), + .dest = htons(dst->port), + .len = htons(datalen), + }; + *ih = (struct iphdr) { + .version = 4, + .ihl = 5, + .tos = tos, + .tot_len = htons(sizeof(*ih) + datalen), + .ttl = 64, + .protocol = IPPROTO_UDP, + .saddr = src->u.ipv4, + .daddr = dst->u.ipv4, + }; + + skb->csum_start = skb_transport_header(skb) - skb->head; + skb->csum_offset = offsetof(struct udphdr, check); + uh->check = csum_tcpudp_magic(src->u.ipv4, dst->u.ipv4, datalen, IPPROTO_UDP, csum_partial(uh, datalen, 0)); + if (uh->check == 0) + uh->check = CSUM_MANGLED_0; + + skb->protocol = htons(ETH_P_IP); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0) + if (ip_route_me_harder(par->net, skb, RTN_UNSPEC)) +#else + if (ip_route_me_harder(skb, RTN_UNSPEC)) +#endif + goto drop; + + skb->ip_summed = CHECKSUM_NONE; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0) ip_local_out(par->net, skb->sk, skb); #else ip_local_out(skb); @@ -2071,8 +3174,8 @@ drop: static int send_proxy_packet(struct sk_buff *skb, struct re_address *src, struct re_address *dst, - unsigned char tos, const struct xt_action_param *par) { - + unsigned char tos, const struct xt_action_param *par) +{ if (src->family != dst->family) goto drop; @@ -2450,6 +3553,50 @@ static inline int rtp_payload_type(const struct rtp_header *hdr, const struct rt } #endif +static struct sk_buff *intercept_skb_copy(struct sk_buff *oskb, const struct re_address *src) { + struct sk_buff *ret; + struct udphdr *uh; + struct iphdr *ih; + struct ipv6hdr *ih6; + + ret = skb_copy_expand(oskb, MAX_HEADER, MAX_SKB_TAIL_ROOM, GFP_ATOMIC); + if (!ret) + return NULL; + + // restore original header. it's still present in the copied skb, so we just need + // to push back our head room. the payload lengths might be wrong and must be fixed. + // checksums might also be wrong, but can be ignored. + + // restore transport header + skb_push(ret, ret->data - skb_transport_header(ret)); + uh = (void *) skb_transport_header(ret); + uh->len = htons(ret->len); + + // restore network header + skb_push(ret, ret->data - skb_network_header(ret)); + + // restore network length field + switch (src->family) { + case AF_INET: + ih = (void *) skb_network_header(ret); + ih->tot_len = htons(ret->len); + break; + case AF_INET6: + ih6 = (void *) skb_network_header(ret); + ih6->payload_len = htons(ret->len - sizeof(*ih6)); + break; + default: + kfree_skb(ret); + return NULL; + } + + return ret; +} + + + + + static unsigned int rtpengine46(struct sk_buff *skb, struct rtpengine_table *t, struct re_address *src, struct re_address *dst, u_int8_t in_tos, const struct xt_action_param *par) { @@ -2464,6 +3611,8 @@ static unsigned int rtpengine46(struct sk_buff *skb, struct rtpengine_table *t, u_int32_t *u32; struct rtp_parsed rtp; u_int64_t pkt_idx; + struct re_stream *stream; + struct re_stream_packet *packet; #if (RE_HAS_MEASUREDELAY) u_int64_t starttime, endtime, delay; @@ -2564,13 +3713,34 @@ src_check_ok: not_rtp: if (g->target.mirror_addr.family) { DBG("sending mirror packet to dst "MIPF"\n", MIPP(g->target.mirror_addr)); - skb2 = skb_copy(skb, GFP_ATOMIC); + skb2 = skb_copy_expand(skb, MAX_HEADER, MAX_SKB_TAIL_ROOM, GFP_ATOMIC); err = send_proxy_packet(skb2, &g->target.src_addr, &g->target.mirror_addr, g->target.tos, par); if (err) atomic64_inc(&g->stats.errors); } + if (g->target.do_intercept) { + DBG("do_intercept is set\n"); + stream = get_stream_lock(NULL, g->target.intercept_stream_idx); + if (!stream) + goto no_intercept; + packet = kzalloc(sizeof(*packet), GFP_ATOMIC); + if (!packet) + goto intercept_done; + packet->skbuf = intercept_skb_copy(skb, src); + if (!packet->skbuf) + goto no_intercept_free; + add_stream_packet(stream, packet); + goto intercept_done; + +no_intercept_free: + free_packet(packet); +intercept_done: + stream_put(stream); + } + +no_intercept: if (rtp.ok) { pkt_idx = packet_index(&g->encrypt, &g->target.encrypt, rtp.header); srtp_encrypt(&g->encrypt, &g->target.encrypt, &rtp, pkt_idx); @@ -2628,18 +3798,18 @@ out: atomic64_inc(&g->stats.errors); #endif - target_push(g); - table_push(t); + target_put(g); + table_put(t); return NF_DROP; skip_error: atomic64_inc(&g->stats.errors); skip1: - target_push(g); + target_put(g); skip2: kfree_skb(skb); - table_push(t); + table_put(t); return XT_CONTINUE; } @@ -2674,6 +3844,7 @@ static unsigned int rtpengine4(struct sk_buff *oskb, const struct xt_action_para goto skip2; memset(&src, 0, sizeof(src)); + memset(&dst, 0, sizeof(dst)); src.family = AF_INET; src.u.ipv4 = ih->saddr; dst.family = AF_INET; @@ -2684,7 +3855,7 @@ static unsigned int rtpengine4(struct sk_buff *oskb, const struct xt_action_para skip2: kfree_skb(skb); skip3: - table_push(t); + table_put(t); skip: return XT_CONTINUE; } @@ -2719,6 +3890,7 @@ static unsigned int rtpengine6(struct sk_buff *oskb, const struct xt_action_para goto skip2; memset(&src, 0, sizeof(src)); + memset(&dst, 0, sizeof(dst)); src.family = AF_INET6; memcpy(&src.u.ipv6, &ih->saddr, sizeof(src.u.ipv6)); dst.family = AF_INET6; @@ -2729,7 +3901,7 @@ static unsigned int rtpengine6(struct sk_buff *oskb, const struct xt_action_para skip2: kfree_skb(skb); skip3: - table_push(t); + table_put(t); skip: return XT_CONTINUE; } @@ -2791,6 +3963,11 @@ static int __init init(void) { int ret; const char *err; + err = "stream_packets_list_limit parameter must be larger than 0"; + ret = -EINVAL; + if (stream_packets_list_limit <= 0) + goto fail; + printk(KERN_NOTICE "Registering xt_RTPENGINE module - version %s\n", RTPENGINE_VERSION); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) DBG("using uid %u, gid %d\n", proc_uid, proc_gid); @@ -2798,30 +3975,25 @@ static int __init init(void) { proc_kgid = KGIDT_INIT(proc_gid); #endif rwlock_init(&table_lock); + auto_array_init(&calls); + auto_array_init(&streams); ret = -ENOMEM; err = "could not register /proc/ entries"; - my_proc_root = proc_mkdir("rtpengine", NULL); + my_proc_root = proc_mkdir_user("rtpengine", S_IRUGO | S_IXUGO, NULL); if (!my_proc_root) goto fail; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) - proc_set_user(my_proc_root, proc_kuid, proc_kgid); -#endif /* my_proc_root->owner = THIS_MODULE; */ - proc_control = proc_create("control", S_IFREG | S_IWUSR | S_IWGRP, my_proc_root, - &proc_main_control_ops); + proc_control = proc_create_user("control", S_IFREG | S_IWUSR | S_IWGRP, my_proc_root, + &proc_main_control_ops, NULL); if (!proc_control) goto fail; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) - proc_set_user(proc_control, proc_kuid, proc_kgid); -#endif - proc_list = proc_create("list", S_IFREG | S_IRUGO, my_proc_root, &proc_main_list_ops); + + proc_list = proc_create_user("list", S_IFREG | S_IRUGO, my_proc_root, &proc_main_list_ops, NULL); if (!proc_list) goto fail; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) - proc_set_user(proc_list, proc_kuid, proc_kgid); -#endif + err = "could not register xtables target"; ret = xt_register_targets(xt_rtpengine_regs, ARRAY_SIZE(xt_rtpengine_regs)); if (ret) @@ -2846,6 +4018,9 @@ static void __exit fini(void) { clear_proc(&proc_control); clear_proc(&proc_list); clear_proc(&my_proc_root); + + auto_array_free(&streams); + auto_array_free(&calls); } module_init(init); diff --git a/kernel-module/xt_RTPENGINE.h b/kernel-module/xt_RTPENGINE.h index 70367bdd8..08d6964ac 100644 --- a/kernel-module/xt_RTPENGINE.h +++ b/kernel-module/xt_RTPENGINE.h @@ -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; - struct rtpengine_target_info target; + 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 { diff --git a/tests/kernel-module-test.pl b/tests/kernel-module-test.pl index b69c44609..d53cf8501 100755 --- a/tests/kernel-module-test.pl +++ b/tests/kernel-module-test.pl @@ -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', $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; +} + +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 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 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 = 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 = ('', '', ''); +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); diff --git a/tests/simulator-ng.pl b/tests/simulator-ng.pl index 4147a722b..4f71a7227 100755 --- a/tests/simulator-ng.pl +++ b/tests/simulator-ng.pl @@ -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}; diff --git a/utils/kernel-intercept-dump.pl b/utils/kernel-intercept-dump.pl new file mode 100755 index 000000000..5a951a93d --- /dev/null +++ b/utils/kernel-intercept-dump.pl @@ -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); +}