]> git.sesse.net Git - cubemap/commitdiff
Create $(libdir) on make install. master
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 2 Jan 2024 17:47:33 +0000 (18:47 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 2 Jan 2024 17:48:08 +0000 (18:48 +0100)
19 files changed:
Makefile.in
NEWS
README
acceptor.cpp
client.cpp
configure.ac
cubemap.config.sample
ffmpeg_metacube_hack.c [new file with mode: 0644]
httpinput.cpp
httpinput.h
input_stats.cpp
main.cpp
server.cpp
serverpool.cpp
stream.cpp
tlse/README
tlse/ktls.h
udpinput.cpp
version.h

index b4d84569948d3ae34e4215496cb923a9d904c2e2..c910c259810de76f604043ecad717dee92cf6709 100644 (file)
@@ -5,12 +5,18 @@ PROTOC=protoc
 CPPFLAGS=@CPPFLAGS@
 CPPFLAGS += -Itlse -DWITH_KTLS -DNO_TLS_LEGACY_SUPPORT -DNO_SSL_COMPATIBLE_INTERFACE -DLTM_DESC -DTLS_REEXPORTABLE -DNO_TLS_WITH_CHACHA20_POLY1305
 CXXFLAGS=-Wall @CXXFLAGS@ @protobuf_CFLAGS@ @libsystemd_CFLAGS@ @libtomcrypt_CFLAGS@ -pthread
+CFLAGS=-Wall @CFLAGS@
 LDFLAGS=@LDFLAGS@ -pthread
 LIBS=@LIBS@ @protobuf_LIBS@ @libsystemd_LIBS@ @libtomcrypt_LIBS@
+SYSTEMDSYSTEMUNITDIR=@SYSTEMDSYSTEMUNITDIR@
 
 OBJS=main.o client.o server.o stream.o udpstream.o serverpool.o input.o input_stats.o httpinput.o udpinput.o parse.o config.o acceptor.o stats.o accesslog.o thread.o util.o log.o metacube2.o sa_compare.o timespec.o state.pb.o tlse/tlse.o
 
+ifeq (@have_ffmpeg@,yes)
+all: cubemap ffmpeg_metacube_hack.so
+else
 all: cubemap
+endif
 
 %.pb.cc %.pb.h : %.proto
        $(PROTOC) --cpp_out=. $<
@@ -23,42 +29,49 @@ all: cubemap
        $(CXX) -MMD -MP $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $<
 cubemap: $(OBJS)
        $(CXX) -o cubemap $(OBJS) $(LIBS) $(LDFLAGS)
+ffmpeg_metacube_hack.so: ffmpeg_metacube_hack.c
+       $(CC) -shared $(CPPFLAGS) $(CFLAGS) -fPIC -o $@ $< -ldl
 
 DEPS=$(OBJS:.o=.d)
 -include $(DEPS)
 
 clean:
-       $(RM) cubemap $(OBJS) $(DEPS) state.pb.h state.pb.cc
+       $(RM) cubemap $(OBJS) $(DEPS) state.pb.h state.pb.cc ffmpeg_metacube_hack.so
 
-PREFIX=@prefix@
-SYSCONFDIR=@sysconfdir@
-LOCALSTATEDIR=@localstatedir@
+prefix=@prefix@
+sysconfdir=@sysconfdir@
+localstatedir=@localstatedir@
+libdir=@libdir@
 install:
        $(INSTALL) -m 755 -o root -g root -d \
-               $(DESTDIR)$(PREFIX)/bin \
-               $(DESTDIR)$(PREFIX)/share/man/man1 \
-               $(DESTDIR)$(SYSCONFDIR) \
-               $(DESTDIR)$(LOCALSTATEDIR)/lib/cubemap \
-               $(DESTDIR)$(LOCALSTATEDIR)/log/cubemap \
-               $(DESTDIR)$(PREFIX)/share/munin/plugins \
-               $(DESTDIR)/lib/systemd/system
-       $(INSTALL) -m 755 -o root -g root cubemap $(DESTDIR)$(PREFIX)/bin/cubemap
-       $(INSTALL) -m 755 -o root -g root munin/cubemap munin/cubemap_input $(DESTDIR)$(PREFIX)/share/munin/plugins/
-       gzip -c cubemap.1 > $(DESTDIR)$(PREFIX)/share/man/man1/cubemap.1.gz
+               $(DESTDIR)$(prefix)/bin \
+               $(DESTDIR)$(prefix)/share/man/man1 \
+               $(DESTDIR)$(sysconfdir) \
+               $(DESTDIR)$(localstatedir)/lib/cubemap \
+               $(DESTDIR)$(localstatedir)/log/cubemap \
+               $(DESTDIR)$(prefix)/share/munin/plugins \
+               $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)
+       $(INSTALL) -m 755 -o root -g root cubemap $(DESTDIR)$(prefix)/bin/cubemap
+       $(INSTALL) -m 755 -o root -g root munin/cubemap munin/cubemap_input $(DESTDIR)$(prefix)/share/munin/plugins/
+ifeq (@have_ffmpeg@,yes)
+       $(INSTALL) -m 755 -o root -g root -d $(DESTDIR)$(libdir)
+       $(INSTALL) -m 755 -o root -g root ffmpeg_metacube_hack.so $(DESTDIR)$(libdir)/
+endif
+       gzip -c cubemap.1 > $(DESTDIR)$(prefix)/share/man/man1/cubemap.1.gz
        sed \
-               -e "s,@prefix[@],$(PREFIX),g" \
-               -e "s,@sysconfdir[@],$(SYSCONFDIR),g" \
-               cubemap.service.in > $(DESTDIR)/lib/systemd/system/cubemap.service
+               -e "s,@prefix[@],$(prefix),g" \
+               -e "s,@sysconfdir[@],$(sysconfdir),g" \
+               cubemap.service.in > $(DESTDIR)$(SYSTEMDSYSTEMUNITDIR)/cubemap.service
        # Make sure we don't overwrite an existing configuration.
-       test ! -f $(DESTDIR)$(SYSCONFDIR)/cubemap.config
+       test ! -f $(DESTDIR)$(sysconfdir)/cubemap.config
        sed \
-               -e "s,cubemap\.stats,$(LOCALSTATEDIR)/lib/cubemap/\0,g" \
-               -e "s,cubemap-input\.stats,$(LOCALSTATEDIR)/lib/cubemap/\0,g" \
-               -e "s,access\.log,$(LOCALSTATEDIR)/log/cubemap/\0,g" \
-               -e "s,cubemap\.log,$(LOCALSTATEDIR)/log/cubemap/\0,g" \
+               -e "s,cubemap\.stats,$(localstatedir)/lib/cubemap/\0,g" \
+               -e "s,cubemap-input\.stats,$(localstatedir)/lib/cubemap/\0,g" \
+               -e "s,access\.log,$(localstatedir)/log/cubemap/\0,g" \
+               -e "s,cubemap\.log,$(localstatedir)/log/cubemap/\0,g" \
                -e 's,^stream,#\0,g' \
                -e 's,^udpstream,#\0,g' \
-               cubemap.config.sample > $(DESTDIR)$(SYSCONFDIR)/cubemap.config
+               cubemap.config.sample > $(DESTDIR)$(sysconfdir)/cubemap.config
 
 .PHONY: clean install
 .SUFFIXES:
diff --git a/NEWS b/NEWS
index a154c1e2f556607c5e5222e5a668735977233cae..10d5c0e6931c1e9b4e080df63d2bd9d19ec302a1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,30 @@
+Cubemap 1.5.1, 2023-12-17
+
+  * Use systemd.pc to place systemd system units.
+    Patch by Chris Hofstaedtler.
+
+
+Cubemap 1.5.0, 2023-12-09
+
+  * Support input from pipes (subprocesses).
+
+    With a program that can output Metacube on stdout, such as a suitably
+    patched FFmpeg binary, this gives Cubemap transcoding/remuxing capabilities.
+
+    Of course, one cannot upgrade such a binary by SIGHUP-ing it, but it will
+    survive a Cubemap reload/restart.
+
+  * Add a LD_PRELOAD-able library to force Metacube output from FFmpeg.
+
+    This hooks just the right amount of functions to add Metacube output
+    to arbitrary FFmpeg programs, but is obviously very brittle.
+    (Native FFmpeg support would be better, but a patch did not
+    go through when I tried a while back.) It is only lightly tested.
+    Documentation in the README and cubemap.config.sample.
+
+  * Various bugfixes.
+
+
 Cubemap 1.4.3, 2019-11-17
 
   * Keep the HLS backlog even if the stream header changes. Useful
@@ -6,7 +33,7 @@ Cubemap 1.4.3, 2019-11-17
     discontinuities).
 
   * Various bugfixes.
-    
+
 
 Cubemap 1.4.2, 2018-12-21
 
diff --git a/README b/README
index e9520d3aec889da9e53f9bad82f69352024080c3..46a943aba596035d641743b2bb504dad5b98ce7e 100644 (file)
--- a/README
+++ b/README
@@ -1,5 +1,4 @@
-Cubemap is a high-performance, high-availability video reflector,
-specifically made for use with VLC.
+Cubemap is a high-performance, high-availability video reflector.
 
 A short list of features:
 
@@ -11,10 +10,12 @@ A short list of features:
    and sending a SIGHUP; all clients will continue as if nothing had happened
    (unless you delete the stream they are watching, of course).
    Cubemap also survives the encoder dying and reconnecting.
- - Support for setting max pacing rate through the fq packet scheduler
-   (depends on Linux 3.13 or newer).
+ - Support for setting max pacing rate through the fq packet scheduler.
  - Reflects anything VLC can reflect over HTTP, even the muxes VLC
-   has problems reflecting itself (in particular, FLV).
+   has problems reflecting itself (in particular, FLV). Can also
+   call out to external programs to receive or fetch streams in arbitrary
+   ways (e.g. through SRT), although of course they do not necessarily
+   have the same intrinsic high-availability reconfiguration support.
  - Multicast support, both for sending and receiving (supports only protocols
    that can go over UDP, e.g. MPEG-TS). Supports both ASM and SSM.
  - TLS output support, through the TLSe library (requires libtomcrypt)
@@ -27,12 +28,23 @@ A short list of features:
 
 HOWTO:
 
-  sudo apt install libprotobuf-dev protobuf-compiler libsystemd-dev libtomcrypt-dev
+  sudo apt install libprotobuf-dev protobuf-compiler libsystemd-dev libtomcrypt-dev pkg-config autoconf
+  sudo apt install libavformat-dev libavutil-dev  # Optional
+  autoreconf -I .
   ./configure
   make -j4
 
-If you want to use HTTP input (you probably want to), you want VLC 2.2.0
-or newer. Then start the VLC encoder with the “metacube” flag to the http
+Cubemap does not in itself understand the format of video streams;
+it requires HTTP input that is already delineated into headers and blocks,
+so that it knows what parts of the stream to skip to clients that
+connect after the stream has been. (The exception is self-synchronizing
+streams with no headers, such as MPEG-TS. These can also be sent or
+received over UDP) The input format is specific to Cubemap and is called
+Metacube (technically, Metacube2, but Metacube1 is long gone and nothing
+uses it anymore). Currently, you have three options to create Metacube
+video streams: VLC, Nageru or FFmpeg with a hack.
+
+For VLC, start the VLC encoder with the “metacube” flag to the http
 access mux, like this:
 
   cvlc [...] --sout '#std{access=http{metacube,mime=video/x-flv},mux=flv,dst=:4013/test.flv}'
@@ -40,8 +52,18 @@ access mux, like this:
 Then look through cubemap.config.sample, copy it to cubemap.config,
 compile and start cubemap.
 
-Nageru, my free video mixer, can also produce Metacube streams natively.
-See the manual at https://nageru.sesse.net/doc/ for more information.
+Nageru, my free video mixer, can also produce Metacube streams natively,
+and so can its included transcoder/remuxer Kaeru. See the manual
+at https://nageru.sesse.net/doc/ for more information.
+
+If you feel very adventurous, you can use LD_PRELOAD to load
+ffmpeg_metacube_hack.so into an FFmpeg-using binary. (This is
+experimental; native Metacube support is vastly preferred.)
+For instance, here's one way you can use the ffmpeg(1) binary
+to serve your webcam to Cubemap:
+
+  LD_PRELOAD=ffmpeg_metacube_hack.so ffmpeg -i /dev/video0 -f mpegts -listen 1 'http://[::]:9095'
+
 
 To upgrade cubemap (after you've compiled a new binary), or to pick up new
 config:
index 31901dd8406c97c39d97dc9b3310dedb4f37a18e..75dd70b5540ff291b1cb2f7f54000a1340ae9ef4 100644 (file)
@@ -104,7 +104,7 @@ Acceptor::Acceptor(const AcceptorProto &serialized)
 {
        // Set back the close-on-exec flag for the socket.
        // (This can't leak into a child, since we haven't been started yet.)
-       fcntl(server_sock, F_SETFD, O_CLOEXEC);
+       fcntl(server_sock, F_SETFD, FD_CLOEXEC);
 }
 
 AcceptorProto Acceptor::serialize() const
index 5d5c179be0e6df019930fb2924cf22be2e7bcab1..fb278e2803d73e57f1a20a683c9dab663c7c9e88 100644 (file)
@@ -72,7 +72,7 @@ Client::Client(const ClientProto &serialized, const vector<shared_ptr<const stri
 {
        // Set back the close-on-exec flag for the socket.
        // (This can't leak into a child, since we haven't been started yet.)
-       fcntl(sock, F_SETFD, O_CLOEXEC);
+       fcntl(sock, F_SETFD, FD_CLOEXEC);
 
        if (stream != nullptr) {
                if (setsockopt(sock, SOL_SOCKET, SO_MAX_PACING_RATE, &stream->pacing_rate, sizeof(stream->pacing_rate)) == -1) {
index 0e51aedf0721ffb2c1a8d3585a348a111c9325db..1d9bc396fb9c63b80e22723c50482d5f478fcc3c 100644 (file)
@@ -1,5 +1,5 @@
 AC_CONFIG_MACRO_DIR([m4])
-AC_INIT(cubemap, 1.4.3)
+AC_INIT(cubemap, 1.5.2-pre)
 
 AC_CONFIG_SRCDIR(main.cpp)
 
@@ -9,6 +9,15 @@ PKG_PROG_PKG_CONFIG
 PKG_CHECK_MODULES([libsystemd], [libsystemd])
 PKG_CHECK_MODULES([protobuf], [protobuf])
 PKG_CHECK_MODULES([libtomcrypt], [libtomcrypt])
+PKG_CHECK_VAR(SYSTEMDSYSTEMUNITDIR, systemd, systemdsystemunitdir)
+
+have_ffmpeg=yes
+PKG_CHECK_MODULES([libavformat], [libavformat], [true], [have_ffmpeg=no])
+PKG_CHECK_MODULES([libavutil], [libavutil], [true], [have_ffmpeg=no])
+AC_SUBST([have_ffmpeg])
+
+AS_IF([test "x$have_ffmpeg" = "xno" ],
+  [AC_MSG_WARN([FFmpeg libraries not found. ffmpeg_metacube_hack.so will not be built.])])
 
 CXXFLAGS="$CXXFLAGS -std=gnu++11"
 
index 51c46d317a4a017bfcabb15ba2cc5eafeffb084e..967059c7a62b369d2db9232cdbf34ca53b336159 100644 (file)
@@ -81,6 +81,20 @@ stream /test.ts src=http://gruessi.zrh.sesse.net:4013/test.ts src_encoding=raw
 # to be some reasonable fraction of your fragment length.
 stream /stream.mp4 src=http://gruessi.zrh.sesse.net:9095/test.mp4.metacube hls_playlist=/stream.m3u8 hls_frag_duration=6 backlog_size=20971520 hls_backlog_margin=1048576 allow_origin=*
 
+# An example of using pipe: to read from a program that generates Metacube
+# on standard output (or you can use src_encoding=raw for MPEG_TS).
+# This shows how to use FFmpeg, combined with a LD_PRELOAD hack
+# (the .so file is built with Cubemap if you have the FFmpeg development
+# libraries instealled), to receive MPEG-TS over SRT and remux it into MP4.
+# Note that using ffmpeg_metacube_hack.so is experimental and not recommended
+# if you can avoid it.
+#
+# Only one instance of the command will be started at a time (it will drive a
+# stream just like an HTTP input will). Such subprocesses will survive
+# a Cubemap reload, but can of course themselves not be live-reloaded
+# like Cubemap can. Standard error will be passed through unchanged.
+stream /srt.mp4 src=pipe:"LD_PRELOAD=ffmpeg_metacube_hack.so ffmpeg -loglevel warning -i 'srt://[::]:9710?mode=listener' -c:v copy -c:a copy -f mp4 -movflags empty_moov+frag_keyframe+default_base_moof+skip_trailer -frag_duration 125000 -bsf:a aac_adtstoasc -"
+
 # UDP input. TS is the most common container to use over UDP (you cannot
 # take any arbitrary container and expect it to work).
 # backlog_size=<number of bytes> overrides the backlog, which is normally 10 MB.
diff --git a/ffmpeg_metacube_hack.c b/ffmpeg_metacube_hack.c
new file mode 100644 (file)
index 0000000..4398cd6
--- /dev/null
@@ -0,0 +1,356 @@
+// A shared library that you can LD_PRELOAD into an FFmpeg-using process
+// (most likely ffmpeg(1)) to make it output Metacube. This is obviously
+// pretty hacky, since it needs to override various FFmpeg functions,
+// so there are few guarantees here. It is written in C to avoid pulling
+// in the C++ runtime into FFmpeg's C-only world.
+//
+// You should not link to this library. It does not have ABI stability.
+// It is licensed the same as the rest of Cubemap.
+
+#define _GNU_SOURCE
+#include <assert.h>
+#include <dlfcn.h>
+#include <libavformat/avformat.h>
+#include <libavformat/avio.h>
+#include <libavutil/avassert.h>
+#include <libavutil/crc.h>
+#include <libavutil/error.h>
+#include <libavutil/intreadwrite.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/signal.h>
+#include "metacube2.h"
+
+static pthread_once_t metacube2_crc_once_control = PTHREAD_ONCE_INIT;
+static AVCRC metacube2_crc_table[257];
+
+// We need to store some extra information for each context,
+// so this is where we do it. The “opaque” field in the AVIOContext
+// points to this struct, but we can also look it up by the AVIOContext
+// pointer by scanning through the singly linked list starting with
+// first_extra_data.
+struct ContextExtraData {
+       struct ContextExtraData *next;  // NULL for last entry.
+       AVIOContext *ctx;       // The context we are associating data with.
+       bool seen_sync_point;
+       void *old_opaque;
+       int (*old_write_data_type)(void *opaque, uint8_t * buf,
+                                  int buf_size,
+                                  enum AVIODataMarkerType type,
+                                  int64_t time);
+
+       // Used during avformat_write_header(), to combine adjacent header blocks
+       // into one (in particular, the MP4 mux has an unneeded avio_flush()
+       // halfway throughout).
+       bool in_header;
+       int64_t header_first_time;
+       uint8_t *buffered_header;
+       size_t buffered_header_bytes;
+};
+static struct ContextExtraData *first_extra_data = NULL;
+
+// Look up ContextExtraData for the given context, creating a new one if needed.
+static struct ContextExtraData *get_extra_data(AVIOContext * ctx)
+{
+       for (struct ContextExtraData * ed = first_extra_data; ed != NULL;
+            ed = ed->next) {
+               if (ed->ctx == ctx) {
+                       return ed;
+               }
+       }
+       struct ContextExtraData *ed = (struct ContextExtraData *)
+           malloc(sizeof(struct ContextExtraData));
+       ed->ctx = ctx;
+       ed->seen_sync_point = false;
+       ed->old_write_data_type = NULL;
+       ed->in_header = false;
+       ed->buffered_header = NULL;
+       ed->buffered_header_bytes = 0;
+
+       ed->next = first_extra_data;
+       first_extra_data = ed;
+       return ed;
+}
+
+// Clear ContextExtraData for a given context (presumably before it's freed).
+static void free_extra_data(AVIOContext * ctx)
+{
+       if (first_extra_data == NULL) {
+               return;
+       }
+       if (first_extra_data->ctx == ctx) {
+               struct ContextExtraData *to_free = first_extra_data;
+               first_extra_data = to_free->next;
+               free(to_free);
+               return;
+       }
+       for (struct ContextExtraData * ed = first_extra_data; ed != NULL;
+            ed = ed->next) {
+               if (ed->next != NULL && ed->next->ctx == ctx) {
+                       struct ContextExtraData *to_free = ed->next;
+                       ed->next = to_free->next;
+                       free(to_free);
+                       return;
+               }
+       }
+}
+
+static void metacube2_crc_init_table_once(void)
+{
+       av_assert0(av_crc_init
+                  (metacube2_crc_table, 0, 16, 0x8fdb,
+                   sizeof(metacube2_crc_table)) >= 0);
+}
+
+static uint16_t metacube2_compute_crc_ff(const struct
+                                        metacube2_block_header *hdr)
+{
+       static const int data_len = sizeof(hdr->size) + sizeof(hdr->flags);
+       const uint8_t *data = (uint8_t *) & hdr->size;
+       uint16_t crc;
+
+       pthread_once(&metacube2_crc_once_control,
+                    metacube2_crc_init_table_once);
+
+       // Metacube2 specifies a CRC start of 0x1234, but its pycrc-derived CRC
+       // includes a finalization step that is done somewhat differently in av_crc().
+       // 0x1234 alone sent through that finalization becomes 0x394a, and then we
+       // need a byte-swap of the CRC value (both on input and output) to account for
+       // differing conventions.
+       crc = av_crc(metacube2_crc_table, 0x4a39, data, data_len);
+       return av_bswap16(crc);
+}
+
+static int write_packet(void *opaque, uint8_t * buf, int buf_size,
+                       enum AVIODataMarkerType type, int64_t time)
+{
+       if (buf_size < 0) {
+               return AVERROR(EINVAL);
+       }
+
+       struct ContextExtraData *ed = (struct ContextExtraData *) opaque;
+
+       if (ed->in_header) {
+               if (ed->buffered_header_bytes == 0) {
+                       ed->header_first_time = time;
+               }
+
+               size_t new_buffered_header_bytes =
+                   ed->buffered_header_bytes + buf_size;
+               if (new_buffered_header_bytes < ed->buffered_header_bytes) {
+                       return AVERROR(ENOMEM);
+               }
+               ed->buffered_header =
+                   (uint8_t *) realloc(ed->buffered_header,
+                                       new_buffered_header_bytes);
+               if (ed->buffered_header == NULL) {
+                       return AVERROR(ENOMEM);
+               }
+
+               memcpy(ed->buffered_header + ed->buffered_header_bytes,
+                      buf, buf_size);
+               ed->buffered_header_bytes = new_buffered_header_bytes;
+               return buf_size;
+       }
+       // Find block size if we add a Metacube2 header in front.
+       unsigned new_buf_size =
+           (unsigned) buf_size + sizeof(struct metacube2_block_header);
+       if (new_buf_size < (unsigned) buf_size
+           || new_buf_size > (unsigned) INT_MAX) {
+               // Overflow.
+               return -1;
+       }
+       // Fill in the header.
+       struct metacube2_block_header hdr;
+       int flags = 0;
+       if (type == AVIO_DATA_MARKER_SYNC_POINT)
+               ed->seen_sync_point = 1;
+       else if (type == AVIO_DATA_MARKER_HEADER)
+               // NOTE: If there are multiple blocks marked METACUBE_FLAGS_HEADER,
+               // only the last one will count. This may become a problem if the
+               // mux flushes halfway through the stream header; if so, we would
+               // need to keep track of and concatenate the different parts.
+               flags |= METACUBE_FLAGS_HEADER;
+       else if (ed->seen_sync_point)
+               flags |= METACUBE_FLAGS_NOT_SUITABLE_FOR_STREAM_START;
+
+       memcpy(hdr.sync, METACUBE2_SYNC, sizeof(hdr.sync));
+       AV_WB32(&hdr.size, buf_size);
+       AV_WB16(&hdr.flags, flags);
+       AV_WB16(&hdr.csum, metacube2_compute_crc_ff(&hdr));
+
+       int ret;
+       ed->ctx->opaque = ed->old_opaque;
+       if (new_buf_size < ed->ctx->max_packet_size) {
+               // Combine the two packets. (This is what we normally want.)
+               // So we allocate a new block, with a Metacube2 header in front.
+               uint8_t *buf_with_hdr = (uint8_t *) malloc(new_buf_size);
+               if (buf_with_hdr == NULL) {
+                       return AVERROR(ENOMEM);
+               }
+               memcpy(buf_with_hdr, &hdr, sizeof(hdr));
+               memcpy(buf_with_hdr + sizeof(hdr), buf, buf_size);
+               if (ed->old_write_data_type) {
+                       ret =
+                           ed->old_write_data_type(ed->old_opaque,
+                                                   buf_with_hdr,
+                                                   new_buf_size, type,
+                                                   time);
+               } else {
+                       ret =
+                           ed->ctx->write_packet(ed->old_opaque,
+                                                 buf_with_hdr,
+                                                 new_buf_size);
+               }
+               free(buf_with_hdr);
+
+               if (ret >= 0
+                   && ret >= sizeof(struct metacube2_block_header)) {
+                       ret -= sizeof(struct metacube2_block_header);
+               }
+       } else {
+               // Send separately. This will split a header block if it's really large,
+               // which we don't want, but that's how things are.
+               if (ed->old_write_data_type) {
+                       ret =
+                           ed->old_write_data_type(ed->old_opaque,
+                                                   (uint8_t *) & hdr,
+                                                   sizeof(hdr), type,
+                                                   time);
+               } else {
+                       ret =
+                           ed->ctx->write_packet(ed->old_opaque,
+                                                 (uint8_t *) & hdr,
+                                                 sizeof(hdr));
+               }
+               if (ret < 0) {
+                       return ret;
+               }
+               if (ret != sizeof(hdr)) {
+                       return AVERROR(EIO);
+               }
+
+               if (ed->old_write_data_type) {
+                       ret =
+                           ed->old_write_data_type(ed->old_opaque, buf,
+                                                   buf_size, type, time);
+               } else {
+                       ret =
+                           ed->ctx->write_packet(ed->old_opaque, buf,
+                                                 buf_size);
+               }
+       }
+
+       ed->ctx->opaque = ed;
+       return ret;
+}
+
+// Actual hooked functions below.
+
+int avformat_write_header(AVFormatContext * ctx, AVDictionary ** options)
+{
+       metacube2_crc_init_table_once();
+
+       struct ContextExtraData *ed = get_extra_data(ctx->pb);
+       ed->old_opaque = ctx->pb->opaque;
+       ed->old_write_data_type = ctx->pb->write_data_type;
+       ctx->pb->opaque = ed;
+       ctx->pb->write_data_type = write_packet;
+       ctx->pb->seek = NULL;
+       ctx->pb->seekable = 0;
+       if (ed->old_write_data_type == NULL) {
+               ctx->pb->ignore_boundary_point = 1;
+       }
+
+       int (*original_func)(AVFormatContext * ctx,
+                            AVDictionary ** options);
+       original_func = dlsym(RTLD_NEXT, "avformat_write_header");
+
+       ed->in_header = true;
+       int ret = (*original_func) (ctx, options);
+       ed->in_header = false;
+
+       if (ed->buffered_header_bytes > 0) {
+               int hdr_ret = write_packet(ed, ed->buffered_header,
+                                          ed->buffered_header_bytes,
+                                          AVIO_DATA_MARKER_HEADER,
+                                          ed->header_first_time);
+               free(ed->buffered_header);
+               ed->buffered_header = NULL;
+
+               if (hdr_ret >= 0 && hdr_ret < ed->buffered_header_bytes) {
+                       hdr_ret = AVERROR(EIO);
+               }
+               ed->buffered_header_bytes = 0;
+               if (hdr_ret < 0) {
+                       return hdr_ret;
+               }
+       }
+
+       return ret;
+}
+
+void avformat_free_context(AVFormatContext * ctx)
+{
+       if (ctx == NULL) {
+               return;
+       }
+       free_extra_data(ctx->pb);
+
+       void (*original_func)(AVFormatContext * ctx);
+       original_func = dlsym(RTLD_NEXT, "avformat_free_context");
+       return (*original_func) (ctx);
+}
+
+// Hook so that we can restore opaque instead of ours being freed by the caller.
+int avio_close(AVIOContext * ctx)
+{
+       if (ctx == NULL) {
+               return 0;
+       }
+       struct ContextExtraData ed = *get_extra_data(ctx);
+       free_extra_data(ctx);
+       ctx->opaque = ed.old_opaque;
+
+       int (*original_func)(AVIOContext * ctx);
+       original_func = dlsym(RTLD_NEXT, "avio_close");
+       return (*original_func) (ctx);
+}
+
+// Identical to FFmpeg's definition, but we cannot hook avio_close()
+// when called from FFmpeg's avio_closep(), so we need to hook this one
+// as well.
+int avio_closep(AVIOContext ** s)
+{
+       int ret = avio_close(*s);
+       *s = NULL;
+       return ret;
+}
+
+
+int avio_open2(AVIOContext ** s, const char *filename, int flags,
+              const AVIOInterruptCB * int_cb, AVDictionary ** options)
+{
+       // The options, if any, are destroyed on entry, so we can add new ones
+       // pretty freely.
+       if (options && *options) {
+               AVDictionaryEntry *listen =
+                   av_dict_get(*options, "listen", NULL,
+                               AV_DICT_MATCH_CASE);
+               if (listen != NULL && atoi(listen->value) != 0) {
+                       // If -listen is set, we'll want to add a header, too.
+                       av_dict_set(options, "headers",
+                                   "Content-encoding: metacube\r\n",
+                                   AV_DICT_APPEND);
+               }
+       }
+
+       int (*original_func)(AVIOContext ** s, const char *filename,
+                            int flags, const AVIOInterruptCB * int_cb,
+                            AVDictionary ** options);
+       original_func = dlsym(RTLD_NEXT, "avio_open2");
+       return (*original_func) (s, filename, flags, int_cb, options);
+}
index 62ed663611402cef5d4517cef00d8033300391bc..b2b620706ad368983e37aafd8ae1e1f101e784f6 100644 (file)
@@ -80,7 +80,9 @@ HTTPInput::HTTPInput(const InputProto &serialized)
 {
        // Set back the close-on-exec flag for the socket.
        // (This can't leak into a child, since we haven't been started yet.)
-       fcntl(sock, F_SETFD, O_CLOEXEC);
+       if (sock != -1) {
+               fcntl(sock, F_SETFD, FD_CLOEXEC);
+       }
 
        pending_data.resize(serialized.pending_data().size());
        memcpy(&pending_data[0], serialized.pending_data().data(), serialized.pending_data().size());
@@ -167,6 +169,7 @@ int HTTPInput::lookup_and_connect(const string &host, const string &port)
                        log(WARNING, "[%s] Lookup of '%s' failed (%s).",
                                url.c_str(), host.c_str(), gai_strerror(err));
                }
+               freeaddrinfo(ai);
                return -1;
        }
 
@@ -198,6 +201,7 @@ int HTTPInput::lookup_and_connect(const string &host, const string &port)
                        bool complete = wait_for_activity(sock, POLLIN | POLLOUT, nullptr);
                        if (should_stop()) {
                                safe_close(sock);
+                               freeaddrinfo(base_ai);
                                return -1;
                        }
                        if (complete) {
index 1ee1f8f5c4d134e3066ec65f6923b74effc2489a..0c3b5344a5ccecc29341d7c52098cc545722fa65 100644 (file)
@@ -100,7 +100,7 @@ private:
        // The socket we are downloading on (or -1).
        int sock = -1;
 
-       // pid of the cihld process (or -1).
+       // pid of the child process (or -1).
        pid_t child_pid = -1;
 
        // Mutex protecting <stats>.
index eee597b273d42cb4b83b2c530bb9913254fe788c..5b7dc59ae762696f2eca6e9ad12fc4e54fc6272b 100644 (file)
@@ -52,7 +52,14 @@ void InputStatsThread::do_work()
                now = time(nullptr);
                for (size_t i = 0; i < inputs.size(); ++i) {
                        InputStats stats = inputs[i]->get_stats();
-                       fprintf(fp, "%s %llu %llu", stats.url.c_str(),
+                       for (const char ch : stats.url) {
+                               if (isspace(ch) || !isprint(ch)) {
+                                       putc('_', fp);
+                               } else {
+                                       putc(ch, fp);
+                               }
+                       }
+                       fprintf(fp, " %llu %llu",
                                (long long unsigned)(stats.bytes_received),
                                (long long unsigned)(stats.data_bytes_received));
                        if (stats.connect_time == -1) {
index 18976fc833b6d49a981af6fc67e79fe1efa64469..50fd48e729f1df4c47b30755e017b9f2d2c1a0d8 100644 (file)
--- a/main.cpp
+++ b/main.cpp
@@ -623,6 +623,9 @@ start:
        }
        for (const auto &key_and_input_with_refcount : inputs) {
                key_and_input_with_refcount.second.input->stop();
+               if (stopped) {
+                       key_and_input_with_refcount.second.input->close_socket();
+               }
        }
        servers->stop();
 
index a6f45744e1576b03065c712767bf4cea5078eb45..8025fd1b4cb257ee71aa6a280a98c1913e007e73 100644 (file)
@@ -70,6 +70,11 @@ Server::Server()
 Server::~Server()
 {
        safe_close(epoll_fd);
+
+       // We're going to die soon anyway, but clean this up to keep leak checking happy.
+       for (const auto &acceptor_and_context : tls_server_contexts) {
+               tls_destroy_context(acceptor_and_context.second);
+       }
 }
 
 vector<ClientStats> Server::get_client_stats() const
index c7ff10cf2866057e3ddc7a996a6d999da967720e..7b790c29b4e87de793a58de34d9a077861da54cb 100644 (file)
@@ -181,10 +181,11 @@ void ServerPool::add_data(int stream_index, const char *data, size_t bytes, uint
 
 void ServerPool::set_unavailable(int stream_index)
 {
-       assert(stream_index >= 0 && stream_index < ssize_t(num_http_streams));
-
-       for (int i = 0; i < num_servers; ++i) {
-               servers[i].set_unavailable(stream_index);
+       assert(stream_index >= 0 && stream_index < ssize_t(num_http_streams + udp_streams.size()));
+       if (stream_index < ssize_t(num_http_streams)) {
+               for (int i = 0; i < num_servers; ++i) {
+                       servers[i].set_unavailable(stream_index);
+               }
        }
 }
 
index 4e5ca24cceab8870dfdfa00e66b161aecc61f729..c79391a81990b2f94ddef1b460beb08b62de0274 100644 (file)
@@ -69,7 +69,7 @@ Stream::Stream(const StreamProto &serialized, int data_fd)
        }
 
        // Set the close-on-exec parameter back on the backlog fd.
-       fcntl(data_fd, F_SETFD, O_CLOEXEC);
+       fcntl(data_fd, F_SETFD, FD_CLOEXEC);
 
        for (ssize_t point : serialized.suitable_starting_point()) {
                if (point == -1) {
index 3fa5ca5db148ef57bf6ca9fdbca856d559764229..8e91f403f4f9bf6bc8567cc45c6e3c7f19ef26fb 100644 (file)
@@ -2,7 +2,6 @@ This is a copy of the core files of TLSe, checked out from
 
   https://github.com/eduardsui/tlse
 
-It is patched to add and include ktls.h, since glibc does not include
-kTLS definitions yet, and then irrelevant files removed. It is embedded
-(as opposed to a regular link) since TLSe does not provide a static or
-shared library.
+It has had irrelevant files removed, and a dummy ktls.h is added to
+forward to glibc's implementation. It is embedded (as opposed to
+a regular link) since TLSe does not provide a static or shared library.
index 7ed59c49f09ef38751899e6fe7daddd8b3001861..4693d2c47cad3fd3ec80ddc5a61ba5be08752065 100644 (file)
@@ -1,80 +1 @@
-/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
-/*
- * Copyright (c) 2016-2017, Mellanox Technologies. All rights reserved.
- *
- * This software is available to you under a choice of one of two
- * licenses.  You may choose to be licensed under the terms of the GNU
- * General Public License (GPL) Version 2, available from the file
- * COPYING in the main directory of this source tree, or the
- * OpenIB.org BSD license below:
- *
- *     Redistribution and use in source and binary forms, with or
- *     without modification, are permitted provided that the following
- *     conditions are met:
- *
- *      - Redistributions of source code must retain the above
- *        copyright notice, this list of conditions and the following
- *        disclaimer.
- *
- *      - Redistributions in binary form must reproduce the above
- *        copyright notice, this list of conditions and the following
- *        disclaimer in the documentation and/or other materials
- *        provided with the distribution.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#ifndef _UAPI_LINUX_TLS_H
-#define _UAPI_LINUX_TLS_H
-
-#include <linux/types.h>
-
-/* TLS socket options */
-#define TLS_TX                 1       /* Set transmit parameters */
-#define TLS_RX                 2       /* Set receive parameters */
-
-/* Supported versions */
-#define TLS_VERSION_MINOR(ver) ((ver) & 0xFF)
-#define TLS_VERSION_MAJOR(ver) (((ver) >> 8) & 0xFF)
-
-#define TLS_VERSION_NUMBER(id) ((((id##_VERSION_MAJOR) & 0xFF) << 8) | \
-                                ((id##_VERSION_MINOR) & 0xFF))
-
-#define TLS_1_2_VERSION_MAJOR  0x3
-#define TLS_1_2_VERSION_MINOR  0x3
-#define TLS_1_2_VERSION                TLS_VERSION_NUMBER(TLS_1_2)
-
-/* Supported ciphers */
-#define TLS_CIPHER_AES_GCM_128                         51
-#define TLS_CIPHER_AES_GCM_128_IV_SIZE                 8
-#define TLS_CIPHER_AES_GCM_128_KEY_SIZE                16
-#define TLS_CIPHER_AES_GCM_128_SALT_SIZE               4
-#define TLS_CIPHER_AES_GCM_128_TAG_SIZE                16
-#define TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE            8
-
-#define TLS_SET_RECORD_TYPE    1
-
-struct tls_crypto_info {
-       __u16 version;
-       __u16 cipher_type;
-};
-
-struct tls12_crypto_info_aes_gcm_128 {
-       struct tls_crypto_info info;
-       unsigned char iv[TLS_CIPHER_AES_GCM_128_IV_SIZE];
-       unsigned char key[TLS_CIPHER_AES_GCM_128_KEY_SIZE];
-       unsigned char salt[TLS_CIPHER_AES_GCM_128_SALT_SIZE];
-       unsigned char rec_seq[TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE];
-};
-
-#define SOL_TLS                282
-#define TCP_ULP        31
-
-#endif /* _UAPI_LINUX_TLS_H */
+#include <linux/tls.h>
index 873b0b952911a226d8f08255f80e2d092e163801..824c3bcf856146d23adb4dbf80b13f89325bb2cb 100644 (file)
@@ -123,7 +123,7 @@ UDPInput::UDPInput(const InputProto &serialized)
 {
        // Set back the close-on-exec flag for the socket.
        // (This can't leak into a child, since we haven't been started yet.)
-       fcntl(sock, F_SETFD, O_CLOEXEC);
+       fcntl(sock, F_SETFD, FD_CLOEXEC);
 
        // Should be verified by the caller.
        string protocol;
index 4a2a26fb331264f32667e99293844142629a41cc..fe89b311e34d52c5bf12a387e19d291ddd535086 100644 (file)
--- a/version.h
+++ b/version.h
@@ -1,9 +1,7 @@
 #ifndef _VERSION_H
 #define _VERSION_H
 
-// Version number. Don't expect this to change all that often.
-
-#define SERVER_VERSION "1.4.4-pre"
+#define SERVER_VERSION "1.5.2-pre"
 #define SERVER_IDENTIFICATION "Cubemap/" SERVER_VERSION
 
 #endif  // !defined(_VERSION_H)