From 66f93b615870f91b5c2558763e3fce031e3c7f89 Mon Sep 17 00:00:00 2001 From: Lwithwma Brahma <lbrahma@gipl-sw091.gxgroup.local> Date: Mon, 23 Jan 2023 17:10:58 +0530 Subject: [PATCH] R#29252 IPTV Multicast transport stream analysis Adds iptables-mod-mpeg2ts package. Change-Id: I7256fa201534cfe5176cb8c6130895ac438236a2 --- net/xtables-addons/Makefile | 1 + .../patches/300-xt_mpeg2ts.patch | 1837 +++++++++++++++++ 2 files changed, 1838 insertions(+) create mode 100644 net/xtables-addons/patches/300-xt_mpeg2ts.patch diff --git a/net/xtables-addons/Makefile b/net/xtables-addons/Makefile index cb678ac9cd..570b6b8e44 100644 --- a/net/xtables-addons/Makefile +++ b/net/xtables-addons/Makefile @@ -195,6 +195,7 @@ $(eval $(call BuildTemplate,psd,psd,xt_psd,xt_psd,)) $(eval $(call BuildTemplate,quota2,quota2,xt_quota2,xt_quota2,)) $(eval $(call BuildTemplate,sysrq,SYSRQ,xt_SYSRQ,xt_SYSRQ,+kmod-ipt-compat-xtables +kmod-crypto-hash)) $(eval $(call BuildTemplate,tarpit,TARPIT,xt_TARPIT,xt_TARPIT,+kmod-ipt-compat-xtables)) +$(eval $(call BuildTemplate,mpeg2ts,mpeg2ts,xt_mpeg2ts,xt_mpeg2ts,+kmod-ipt-compat-xtables)) $(eval $(call BuildPackage,iptaccount)) $(eval $(call BuildPackage,iptgeoip)) diff --git a/net/xtables-addons/patches/300-xt_mpeg2ts.patch b/net/xtables-addons/patches/300-xt_mpeg2ts.patch new file mode 100644 index 0000000000..8513425be3 --- /dev/null +++ b/net/xtables-addons/patches/300-xt_mpeg2ts.patch @@ -0,0 +1,1837 @@ +diff -uNr xtables-addons-3.24.orig/extensions/Kbuild xtables-addons-3.24/extensions/Kbuild +--- xtables-addons-3.24.orig/extensions/Kbuild 2024-05-24 13:15:28.150705304 +0200 ++++ xtables-addons-3.24/extensions/Kbuild 2024-05-24 13:16:12.395671624 +0200 +@@ -25,6 +25,7 @@ + obj-${build_ipv4options} += xt_ipv4options.o + obj-${build_length2} += xt_length2.o + obj-${build_lscan} += xt_lscan.o ++obj-${build_mpeg2ts} += xt_mpeg2ts.o + obj-${build_pknock} += pknock/ + obj-${build_psd} += xt_psd.o + obj-${build_quota2} += xt_quota2.o +diff -uNr xtables-addons-3.24.orig/extensions/libxt_mpeg2ts.c xtables-addons-3.24/extensions/libxt_mpeg2ts.c +--- xtables-addons-3.24.orig/extensions/libxt_mpeg2ts.c 1970-01-01 01:00:00.000000000 +0100 ++++ xtables-addons-3.24/extensions/libxt_mpeg2ts.c 2024-05-24 13:16:12.395671624 +0200 +@@ -0,0 +1,226 @@ ++/* ++ * Userspace interface for MPEG2 TS match extension "mpeg2ts" for Xtables. ++ * ++ * Copyright (c) Jesper Dangaard Brouer <netoptimizer@brouer.com>, 2009-2013 ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License; either ++ * version 2 of the License, or any later version, as published by the ++ * Free Software Foundation. See <http://www.gnu.org/licenses/gpl-2.0.html>. ++ * ++ */ ++ ++#include <getopt.h> ++#include <netdb.h> ++#include <stdbool.h> ++#include <stdio.h> ++#include <stdlib.h> ++#include <string.h> ++#include <stddef.h> ++ ++#include <xtables.h> ++#include "xt_mpeg2ts.h" ++ ++/* ++ * Userspace iptables/xtables interface for mpeg2ts module. ++ */ ++ ++/* FIXME: don't think this compat check does not cover all versions */ ++#ifndef XTABLES_VERSION ++#define xtables_error exit_error ++#endif ++ ++/* ++ The default match criteria, since version 0.9.0, is to match on ++ correct MPEG2 TS packets. Previously (<= 0.8.0) a rule would only ++ match when a drop were detected. ++ ++ Its still possible to match on drop detection, via the parameter ++ "--match-drop" (which is default off). ++ ++ Stats on packet drops are available via the proc filesystem. These ++ drop detection stats can be disabled via inverting the parameter ++ --drop-detect, eg. "! --drop-detect". ++ ++*/ ++static const struct option mpeg2ts_mt_opts[] = { ++ {.name = "name", .has_arg = true, .val = 'n'}, ++ {.name = "drop-detect", .has_arg = false, .val = 'd'}, ++ {.name = "match-drop", .has_arg = false, .val = 'm'}, ++ {.name = "max-streams", .has_arg = true, .val = 'x'}, ++ {NULL}, ++}; ++ ++static void mpeg2ts_mt_help(void) ++{ ++ printf( ++"mpeg2ts (MPEG2 Transport Stream) match options:\n" ++"VERSION %s\n" ++" [--name <name>] Name for proc file /proc/net/xt_mpeg2ts/rule_NAME\n" ++" [--match-drop] Match on lost TS frames (default: off)\n" ++" [--drop-detect] Detect TS frame loss and store stats (default: ON)\n" ++" [--max-streams <num>] Track 'max' number of streams (per rule)\n", ++ version ++ ); ++} ++ ++static void mpeg2ts_mt_init(struct xt_entry_match *match) ++{ ++ struct xt_mpeg2ts_mtinfo *info = (void *)match->data; ++ /* Enable drop detection per default */ ++ info->flags = XT_MPEG2TS_DETECT_DROP; ++ ++ /* Match on drops is disabled per default */ ++ /* XT_MPEG2TS_MATCH_DROP */ ++} ++ ++static int mpeg2ts_mt_parse(int c, char **argv, int invert, unsigned int *flags, ++ const void *entry, struct xt_entry_match **match) ++{ ++ struct xt_mpeg2ts_mtinfo *info = (void *)(*match)->data; ++ uint32_t num; ++ ++ switch (c) { ++ case 'n': /* --name */ ++ xtables_param_act(XTF_ONLY_ONCE, "mpeg2ts", "--name", ++ *flags & XT_MPEG2TS_PARAM_NAME); ++ if (invert) ++ xtables_error(PARAMETER_PROBLEM, "Inverting name?"); ++ if (strlen(optarg) == 0) ++ xtables_error(PARAMETER_PROBLEM, "Zero-length name?"); ++ if (strchr(optarg, '"') != NULL) ++ xtables_error(PARAMETER_PROBLEM, ++ "Illegal character in name (\")!"); ++ strncpy(info->rule_name, optarg, sizeof(info->rule_name)); ++ info->flags |= XT_MPEG2TS_PARAM_NAME; ++ *flags |= XT_MPEG2TS_PARAM_NAME; ++ break; ++ ++ case 'd': /* --drop-detect */ ++ if (*flags & XT_MPEG2TS_DETECT_DROP) ++ xtables_error(PARAMETER_PROBLEM, ++ "Can't specify --drop-detect option twice"); ++ *flags |= XT_MPEG2TS_DETECT_DROP; ++ ++ if (invert) ++ info->flags &= ~XT_MPEG2TS_DETECT_DROP; ++ else ++ info->flags |= XT_MPEG2TS_DETECT_DROP; ++ ++ break; ++ ++ case 'm': /* --match-drop */ ++ if (*flags & XT_MPEG2TS_MATCH_DROP) ++ xtables_error(PARAMETER_PROBLEM, ++ "Can't specify --match-drop option twice"); ++ *flags |= XT_MPEG2TS_MATCH_DROP; ++ ++ if (invert) ++ info->flags &= ~XT_MPEG2TS_MATCH_DROP; ++ else ++ info->flags |= XT_MPEG2TS_MATCH_DROP; ++ ++ break; ++ ++ case 'x': /* --max-streams */ ++ if (*flags & XT_MPEG2TS_MAX_STREAMS) ++ xtables_error(PARAMETER_PROBLEM, ++ "Can't specify --max-streams option twice"); ++ *flags |= XT_MPEG2TS_MAX_STREAMS; ++ ++ if (invert) { ++ info->cfg.max = 0; ++ /* printf("inverted\n"); */ ++ break; ++ } ++ ++ /* OLD iptables style ++ if (string_to_number(optarg, 0, 0xffffffff, &num) == -1) ++ xtables_error(PARAMETER_PROBLEM, ++ "bad --max-stream: `%s'", optarg); ++ */ ++ ++ /* C-style ++ char *end; ++ num = strtoul(optarg, &end, 0); ++ */ ++ ++ /* New xtables style */ ++ if (!xtables_strtoui(optarg, NULL, &num, 0, UINT32_MAX)) ++ xtables_error(PARAMETER_PROBLEM, ++ "bad --max-streams: `%s'", optarg); ++ ++ /* DEBUG: printf("--max-stream=%lu\n", num); */ ++ info->flags |= XT_MPEG2TS_MAX_STREAMS; ++ info->cfg.max = num; ++ ++ break; ++ ++ default: ++ return false; ++ } ++ ++ return true; ++} ++ ++static void mpeg2ts_mt_print(const void *entry, ++ const struct xt_entry_match *match, int numeric) ++{ ++ const struct xt_mpeg2ts_mtinfo *info = (const void *)(match->data); ++ ++ /* Always indicate this is a mpeg2ts match rule */ ++ printf("mpeg2ts match"); ++ ++ if (info->flags & XT_MPEG2TS_PARAM_NAME) ++ printf(" name:\"%s\"", info->rule_name); ++ ++ if (!(info->flags & XT_MPEG2TS_DETECT_DROP)) ++ printf(" !drop-detect"); ++ ++ if ((info->flags & XT_MPEG2TS_MATCH_DROP)) ++ printf(" match-drop"); ++ ++ if (info->flags & XT_MPEG2TS_MAX_STREAMS) ++ printf(" max-streams:%u", info->cfg.max); ++} ++ ++static void mpeg2ts_mt_save(const void *entry, ++ const struct xt_entry_match *match) ++{ ++ const struct xt_mpeg2ts_mtinfo *info = (const void *)(match->data); ++ ++ /* We need to handle --name, --drop-detect, and --max-streams. */ ++ if (info->flags & XT_MPEG2TS_PARAM_NAME) ++ printf(" --name \"%s\"", info->rule_name); ++ ++ if (!(info->flags & XT_MPEG2TS_DETECT_DROP)) ++ printf(" ! --drop-detect"); ++ ++ if (info->flags & XT_MPEG2TS_MATCH_DROP) ++ printf(" --match-drop"); ++ ++ if (info->flags & XT_MPEG2TS_MAX_STREAMS) ++ printf(" --max-streams %u", info->cfg.max); ++ ++} ++ ++static struct xtables_match mpeg2ts_mt_reg = { ++ .version = XTABLES_VERSION, ++ .name = "mpeg2ts", ++ .revision = 0, ++ .family = PF_UNSPEC, ++ .size = XT_ALIGN(sizeof(struct xt_mpeg2ts_mtinfo)), ++ .userspacesize = offsetof(struct xt_mpeg2ts_mtinfo, hinfo), ++ .init = mpeg2ts_mt_init, ++ .help = mpeg2ts_mt_help, ++ .parse = mpeg2ts_mt_parse, ++/* .final_check = mpeg2ts_mt_check,*/ ++ .print = mpeg2ts_mt_print, ++ .save = mpeg2ts_mt_save, ++ .extra_opts = mpeg2ts_mt_opts, ++}; ++ ++static __attribute__((constructor)) void mpeg2ts_mt_ldr(void) ++{ ++ xtables_register_match(&mpeg2ts_mt_reg); ++} +diff -uNr xtables-addons-3.24.orig/extensions/Mbuild xtables-addons-3.24/extensions/Mbuild +--- xtables-addons-3.24.orig/extensions/Mbuild 2024-05-24 13:15:28.150705304 +0200 ++++ xtables-addons-3.24/extensions/Mbuild 2024-05-24 13:16:12.395671624 +0200 +@@ -20,6 +20,7 @@ + obj-${build_ipv4options} += libxt_ipv4options.so + obj-${build_length2} += libxt_length2.so + obj-${build_lscan} += libxt_lscan.so ++obj-${build_mpeg2ts} += libxt_mpeg2ts.so + obj-${build_pknock} += pknock/ + obj-${build_psd} += libxt_psd.so + obj-${build_quota2} += libxt_quota2.so +diff -uNr xtables-addons-3.24.orig/extensions/xt_mpeg2ts.c xtables-addons-3.24/extensions/xt_mpeg2ts.c +--- xtables-addons-3.24.orig/extensions/xt_mpeg2ts.c 1970-01-01 01:00:00.000000000 +0100 ++++ xtables-addons-3.24/extensions/xt_mpeg2ts.c 2024-05-24 13:17:47.013742067 +0200 +@@ -0,0 +1,1490 @@ ++/* ++ * MPEG2 TS match extension "mpeg2ts" for Xtables. ++ * ++ * This module analyses the contents of MPEG2 Transport Stream (TS) ++ * packets, and can detect TS/CC packet drops. ++ * ++ * Copyright (C) Jesper Dangaard Brouer <netoptimizer@brouer.com>, 2009-2014 ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License; either ++ * version 2 of the License, or any later version, as published by the ++ * Free Software Foundation. See <http://www.gnu.org/licenses/gpl-2.0.html>. ++ * ++ */ ++ ++#include <linux/ip.h> ++#include <linux/udp.h> ++#include <linux/module.h> ++#include <linux/skbuff.h> ++#include <linux/version.h> ++#include <linux/netfilter/x_tables.h> ++ ++#include <linux/rculist.h> ++ ++#include <linux/netdevice.h> /* msg levels */ ++ ++/* Proc file related */ ++#include <linux/proc_fs.h> ++#include <linux/seq_file.h> ++ ++/* Timestamp related */ ++#include <linux/time.h> ++ ++#include "xt_mpeg2ts.h" ++#include "compat_xtables.h" ++ ++MODULE_AUTHOR("Jesper Dangaard Brouer <netoptimizer@brouer.com>"); ++MODULE_DESCRIPTION("Detecting packet drops in MPEG2 Transport Streams (TS)"); ++MODULE_LICENSE("GPL"); ++MODULE_VERSION(XT_MODULE_VERSION); ++MODULE_ALIAS("ipt_mp2t"); ++MODULE_ALIAS("ipt_mpeg2ts"); ++ ++/* COMPAT trick: Kernel >= 3.8.0 ++ * commit b67bfe0d42: hlist: drop the node parameter from iterators ++ * workaround this with some defines ++ */ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0) ++# define HLIST_NODE_POS struct hlist_node *pos; ++#else ++# define HLIST_NODE_POS ++#endif ++ ++/* Compatibility with kernels before 3.10, specifically before ++ * commit 59d8053f1 (proc: Move non-public stuff from linux/proc_fs.h ++ * to fs/proc/internal.h) ++ */ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0) ++# define PDE_DATA(x) (PDE(x)->data) ++void proc_remove(struct proc_dir_entry *pde) ++{ ++ /* can't use remove_proc_subtree() here, it only exists since 3.9 ++ * and we only want to remove a single entry anyway ++ */ ++ if (pde) ++ remove_proc_entry(pde->name, pde->parent); ++} ++#endif ++ ++/* Proc related */ ++static struct proc_dir_entry *mpeg2ts_procdir; ++static const struct file_operations dl_file_ops; ++ ++/* Message level instrumentation based upon the device driver message ++ * levels see include/linux/netdevice.h. ++ * ++ * Note that "msg_level" is runtime adjustable via: ++ * /sys/module/xt_mpeg2ts/parameters/msg_level ++ * ++ */ ++#define NETIF_MSG_DEBUG 0x10000 ++ ++/* Performance tuning instrumentation that can be compiled out */ ++/* #define PERFTUNE 1 */ ++#define PERFTUNE 0 ++ ++#if 1 ++#define MPEG2TS_MSG_DEFAULT \ ++ (NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK | \ ++ NETIF_MSG_IFUP | NETIF_MSG_IFDOWN | \ ++ NETIF_MSG_DEBUG | NETIF_MSG_RX_ERR | NETIF_MSG_RX_STATUS \ ++ ) ++#else ++#define MPEG2TS_MSG_DEFAULT \ ++ (NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK | \ ++ NETIF_MSG_IFUP | NETIF_MSG_IFDOWN | \ ++ NETIF_MSG_RX_ERR | \ ++ ) ++#endif ++ ++static int debug = -1; ++static int msg_level; ++module_param(debug, int, 0); ++module_param(msg_level, int, 0664); ++MODULE_PARM_DESC(debug, "Set low N bits of message level"); ++MODULE_PARM_DESC(msg_level, "Message level bit mask"); ++ ++/* Possibility to compile out print statements, this was used when ++ * profiling the code. ++ */ ++/* #define NO_MSG_CODE 1 */ ++/* #undef DEBUG */ ++/* #define DEBUG 1 */ ++ ++#ifdef NO_MSG_CODE ++#undef DEBUG ++#endif ++ ++#ifdef DEBUG ++#define msg_dbg(TYPE, f, a...) \ ++ do { if (msg_level & NETIF_MSG_##TYPE) \ ++ if (net_ratelimit()) \ ++ printk(KERN_DEBUG PFX f "\n", ## a); \ ++ } while (0) ++#else ++#define msg_dbg(TYPE, f, a...) ++#endif ++ ++#ifdef NO_MSG_CODE ++#define msg_info(TYPE, f, a...) ++#else ++#define msg_info(TYPE, f, a...) \ ++ do { if (msg_level & NETIF_MSG_##TYPE) \ ++ if (net_ratelimit()) \ ++ printk(KERN_INFO PFX f "\n", ## a); \ ++ } while (0) ++#endif ++ ++#ifdef NO_MSG_CODE ++#define msg_notice(TYPE, f, a...) ++#else ++#define msg_notice(TYPE, f, a...) \ ++ do { if (msg_level & NETIF_MSG_##TYPE) \ ++ if (net_ratelimit()) \ ++ printk(KERN_NOTICE PFX f "\n", ## a); \ ++ } while (0) ++#endif ++ ++#ifdef NO_MSG_CODE ++#define msg_warn(TYPE, f, a...) ++#else ++#define msg_warn(TYPE, f, a...) \ ++ do { if (msg_level & NETIF_MSG_##TYPE) \ ++ if (net_ratelimit()) \ ++ printk(KERN_WARNING PFX f "\n", ## a); \ ++ } while (0) ++#endif ++ ++ ++#ifdef NO_MSG_CODE ++#define msg_err(TYPE, f, a...) ++#else ++#define msg_err(TYPE, f, a...) \ ++ do { if (msg_level & NETIF_MSG_##TYPE) \ ++ if (net_ratelimit()) \ ++ printk(KERN_ERR PFX f "\n", ## a); \ ++ } while (0) ++#endif ++ ++ ++/*** Defines from Wireshark packet-mp2t.c ***/ ++#define MP2T_PACKET_SIZE 188 ++#define MP2T_SYNC_BYTE 0x47 ++ ++#define MP2T_SYNC_BYTE_MASK 0xFF000000 ++#define MP2T_TEI_MASK 0x00800000 ++#define MP2T_PUSI_MASK 0x00400000 ++#define MP2T_TP_MASK 0x00200000 ++#define MP2T_PID_MASK 0x001FFF00 ++#define MP2T_TSC_MASK 0x000000C0 ++#define MP2T_AFC_MASK 0x00000030 ++#define MP2T_CC_MASK 0x0000000F ++ ++#define MP2T_SYNC_BYTE_SHIFT 24 ++#define MP2T_TEI_SHIFT 23 ++#define MP2T_PUSI_SHIFT 22 ++#define MP2T_TP_SHIFT 21 ++#define MP2T_PID_SHIFT 8 ++#define MP2T_TSC_SHIFT 6 ++#define MP2T_AFC_SHIFT 4 ++#define MP2T_CC_SHIFT 0 ++ ++/** WIRESHARK CODE COPY-PASTE ++ * ++ * Wireshark value_string structures ++ * typedef struct _value_string { ++ * u32 value; ++ * const char *strptr; ++ * } value_string; ++ * ++ * Adaption field values "doc" taken from Wireshark ++ * static const value_string mp2t_afc_vals[] = { ++ * { 0, "Reserved" }, ++ * { 1, "Payload only" }, ++ * { 2, "Adaptation Field only" }, ++ * { 3, "Adaptation Field and Payload" }, ++ * { 0, NULL } ++ * }; ++ * ++ * WIRESHARK Data structure used for detecting CC drops ++ * ++ * conversation ++ * | ++ * +-> mp2t_analysis_data ++ * | ++ * +-> pid_table (RB tree) ++ * | | ++ * | +-> pid_analysis_data (per pid) ++ * | +-> pid_analysis_data ++ * | +-> pid_analysis_data ++ * | ++ * +-> frame_table (RB tree) ++ * | ++ * +-> frame_analysis_data (only created if drop detected) ++ * | ++ * +-> ts_table (RB tree) ++ * | ++ * +-> pid_analysis_data (per TS subframe) ++ * +-> pid_analysis_data ++ ++ * Datastructures: ++ * --------------- ++ * ++ * xt_rule_mpeg2ts_conn_htable (per iptables rule) ++ * metadata ++ * locking: RCU ++ * hash[metadata.cfg.size] ++ * | ++ * +-> lists of type mpeg2ts_stream elements ++ * ++ * ++ * mpeg2ts_stream (per multicast/mpeg2-ts stream) ++ * stats (about skips and discontinuities) ++ * locking: Spinlock ++ * pid_cc_table (normal list) ++ * | ++ * +-> list of type pid_data_t ++ * One per PID representing the last TS frames CC value ++ * ++ * ++ **/ ++ ++/*** Global defines ***/ ++static DEFINE_SPINLOCK(mpeg2ts_lock); /* Protects conn_htables list */ ++static LIST_HEAD(conn_htables); /* List of xt_rule_mpeg2ts_conn_htable's */ ++static unsigned int GLOBAL_ID; /* Used for assigning rule_id's */ ++/* TODO/FIXME: xt_hashlimit has this extra mutex, do I need it? ++static DEFINE_MUTEX(mpeg2ts_mutex);*/ /* Additional checkentry protection */ ++ ++ ++/* This is sort of the last TS frames info per pid */ ++struct pid_data_t { ++ struct list_head list; ++ int16_t pid; ++ int16_t cc_prev; ++}; ++ ++#define MAX_PID 0x1FFF ++ ++/** Hash table stuff **/ ++ ++/* Data to match a stream / connection */ ++struct mpeg2ts_stream_match { /* Like xt_hashlimit: dsthash_dst */ ++ __be32 dst_addr; /* MC addr first */ ++ __be32 src_addr; ++ __be16 dst_port; ++ __be16 src_port; ++}; ++ ++/* Hash entry with info about the mpeg2ts stream / connection */ ++struct mpeg2ts_stream { /* Like xt_hashlimit: dsthash_ent */ ++ /* Place static / read-only parts in the beginning */ ++ struct hlist_node node; ++ struct mpeg2ts_stream_match match; ++ ++ /* PID list with last CC value (not updated too often) */ ++ int pid_list_len; ++ struct list_head pid_list; ++ ++ /* For RCU-protected deletion */ ++ struct rcu_head rcu_head; ++ ++ /* Place highly modified members after the next cache line */ ++ ++ /* Cache alignment, is assured via ++ ____cacheline_aligned trick, on "packets", this makes "packets" ++ start on the next cacheline boundary. ++ Eventhough is currently already correctly aligned on 64-bit. ++ */ ++ uint64_t packets ____cacheline_aligned; ++ uint64_t payload_bytes; ++ ++ /* Per stream total skips and discontinuity */ ++ /* TODO: Explain difference between skips and discontinuity */ ++ uint64_t skips; ++ uint64_t discontinuity; ++ ++ /* lock for writing/changing/updating */ ++ spinlock_t lock; ++ ++ /* Usage counter to protect against dealloc/kfree */ ++ atomic_t use; ++ ++ /* Cacheline notes (64-bit):*/ ++ /* size: 128, cachelines: 2 */ ++ /* sum members: 104, holes: 0, sum holes: 0 */ ++ /* padding: 24 */ ++}; ++ ++ ++/* This is basically our "stream" connection tracking. ++ * ++ * Keeping track of the MPEG2 streams per iptables rule. ++ * There is one hash-table per iptables rule. ++ * (Based on xt_hashlimit). ++ */ ++struct xt_rule_mpeg2ts_conn_htable { ++ ++ /* Global list containing these elements are needed: (1) to ++ * avoid realloc of our data structures when other rules gets ++ * inserted. (2) to provide stats via /proc/ as data must not ++ * be deallocated while a process reads data from /proc. ++ */ ++ struct list_head list; /* global list of all htables */ ++ atomic_t use; /* reference counting */ ++ unsigned int id; /* id corrosponding to rule_id */ ++ /* uint8_t family; */ /* needed for IPv6 support */ ++ ++ /* "cfg" is also defined here as the real hash array size might ++ * differ from the user defined size, and changing the ++ * userspace defined rule data is not allowed as userspace ++ * then cannot match the rule again for deletion */ ++ struct mpeg2ts_cfg cfg; /* config */ ++ ++ /* Used internally */ ++ spinlock_t lock; /* write lock for hlist_head */ ++ uint32_t rnd; /* random seed for hash */ ++ int rnd_initialized; ++ unsigned int count; /* number entries in table */ ++ uint16_t warn_condition; /* limiting warn printouts */ ++ ++ /* Rule creation time can be used by userspace to 1) determine ++ * the running periode and 2) to detect if the rule has been ++ * flushed between two reads. ++ */ ++ struct timespec64 time_created; ++ ++ /*TODO: Implement timer GC cleanup, to detect streams disappearing ++ struct timer_list timer;*/ /* timer for gc */ ++ ++ /* Instrumentation for perf tuning */ ++ int32_t max_list_search; /* Longest search in a hash list */ ++ atomic_t concurrency_cnt; /* Trying to detect concurrency */ ++ int32_t stream_not_found; /* Number of stream created */ ++ ++ /* Proc seq_file entry */ ++ struct proc_dir_entry *pde; ++ ++ struct hlist_head stream_hash[0];/* conn/stream hashtable ++ * struct mpeg2ts_stream elements */ ++}; ++ ++/* Inspired by xt_hashlimit.c : htable_create() */ ++static bool ++mpeg2ts_htable_create(struct xt_mpeg2ts_mtinfo *minfo) ++{ ++ struct xt_rule_mpeg2ts_conn_htable *hinfo; ++ unsigned int hash_buckets; ++ unsigned int hash_struct_sz; ++ char rule_name[IFNAMSIZ+5]; ++ unsigned int i; ++ unsigned int id; ++ size_t size; ++ ++ /* Q: is lock with mpeg2ts_lock necessary */ ++ spin_lock(&mpeg2ts_lock); ++ id = GLOBAL_ID++; ++ spin_unlock(&mpeg2ts_lock); ++ ++ if (minfo->cfg.size) ++ hash_buckets = minfo->cfg.size; ++ else ++ hash_buckets = 100; ++ ++ hash_struct_sz = sizeof(*minfo->hinfo); /* metadata struct size */ ++ size = hash_struct_sz + sizeof(struct list_head) * hash_buckets; ++ ++ msg_info(IFUP, "Alloc htable(%u) %zu bytes elems:%u metadata:%u bytes", ++ id, size, hash_buckets, hash_struct_sz); ++ ++ hinfo = kzalloc(size, GFP_ATOMIC); ++ if (hinfo == NULL) { ++ msg_err(DRV, "unable to create hashtable(%u), out of memory!", ++ id); ++ return false; ++ } ++ minfo->hinfo = hinfo; ++ ++ /* Copy match config into hashtable config */ ++ memcpy(&hinfo->cfg, &minfo->cfg, sizeof(hinfo->cfg)); ++ hinfo->cfg.size = hash_buckets; ++ ++ /* Max number of connection we want to track */ ++ /* TODO: REMOVE code ++ if (minfo->cfg.max == 0) ++ hinfo->cfg.max = 8 * hinfo->cfg.size; ++ else if (hinfo->cfg.max < hinfo->cfg.size) ++ hinfo->cfg.max = hinfo->cfg.size; ++ */ ++ ++ if (hinfo->cfg.max_list == 0) ++ hinfo->cfg.max_list = 20; ++ ++ /* Init the hash buckets */ ++ for (i = 0; i < hinfo->cfg.size; i++) ++ INIT_HLIST_HEAD(&hinfo->stream_hash[i]); ++ ++ /* Refcnt to allow alloc data to survive between rule updates*/ ++ atomic_set(&hinfo->use, 1); ++ hinfo->id = id; ++ ++ INIT_LIST_HEAD(&hinfo->list); ++ /* ++ spin_lock(&mpeg2ts_lock); ++ list_add_tail(&conn_htables, &hinfo->list); ++ spin_unlock(&mpeg2ts_lock); ++ */ ++ ++ hinfo->count = 0; ++ hinfo->rnd_initialized = 0; ++ hinfo->max_list_search = 0; ++ atomic_set(&hinfo->concurrency_cnt, 0); ++ hinfo->stream_not_found = 0; ++ ++ getnstimeofday64(&hinfo->time_created); ++ ++ /* Generate a rule_name for proc if none given */ ++ if (!minfo->rule_name || !strlen(minfo->rule_name)) ++ snprintf(rule_name, IFNAMSIZ+5, "rule_%u", hinfo->id); ++ else ++ /* FIXME: Check for duplicate names! */ ++ snprintf(rule_name, IFNAMSIZ+5, "rule_%s", minfo->rule_name); ++ ++ /* Create proc entry */ ++ hinfo->pde = proc_create_data(rule_name, 0, mpeg2ts_procdir, ++ &dl_file_ops, hinfo); ++ ++#ifdef CONFIG_PROC_FS ++ if (!hinfo->pde) { ++ msg_err(PROBE, "Cannot create proc file named: %s", ++ minfo->rule_name); ++ kfree(hinfo); ++ return false; ++ } ++#endif ++ ++ spin_lock_init(&hinfo->lock); ++ ++ return true; ++} ++ ++static uint32_t ++hash_match(const struct xt_rule_mpeg2ts_conn_htable *ht, ++ const struct mpeg2ts_stream_match *match) ++{ ++ uint32_t hash = jhash2((const uint32_t *)match, ++ sizeof(*match) / sizeof(uint32_t), ++ ht->rnd); ++ /* ++ * Instead of returning hash % ht->cfg.size (implying a divide) ++ * we return the high 32 bits of the (hash * ht->cfg.size) that will ++ * give results between [0 and cfg.size-1] and same hash distribution, ++ * but using a multiply, less expensive than a divide ++ */ ++ return ((uint64_t)hash * ht->cfg.size) >> 32; ++} ++ ++static inline ++bool match_cmp(const struct mpeg2ts_stream *ent, ++ const struct mpeg2ts_stream_match *b) ++{ ++ return !memcmp(&ent->match, b, sizeof(ent->match)); ++} ++ ++static struct mpeg2ts_stream * ++mpeg2ts_stream_find(struct xt_rule_mpeg2ts_conn_htable *ht, ++ const struct mpeg2ts_stream_match *match) ++{ ++ struct mpeg2ts_stream *entry; ++ uint32_t hash; ++ int cnt = 0; ++ HLIST_NODE_POS; ++ ++#if PERFTUNE ++ int parallel = 0; ++ static int limit; ++ ++ /* rcu_read_lock(); // Taken earlier */ ++ parallel = atomic_inc_return(&ht->concurrency_cnt); ++#endif ++ hash = hash_match(ht, match); ++ ++ if (!hlist_empty(&ht->stream_hash[hash])) { ++ /* The hlist_for_each_entry_rcu macro uses the ++ * appropiate rcu_dereference() to access the ++ * mpeg2ts_stream pointer */ ++ hlist_for_each_entry_rcu(entry, ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0) ++ pos, ++#endif ++ &ht->stream_hash[hash], node) { ++ cnt++; ++ if (match_cmp(entry, match)) ++ goto found; ++ } ++ } ++ ++ /* rcu_read_unlock(); // Released later */ ++#if PERFTUNE ++ atomic_dec(&ht->concurrency_cnt); ++#endif ++ ht->stream_not_found++; /* This is racy, but its only a debug var */ ++ return NULL; ++ ++found: ++ if (unlikely(cnt > ht->cfg.max_list) && ++ unlikely(cnt > ht->max_list_search)) { ++ ht->max_list_search = cnt; ++ msg_warn(PROBE, "Perf: Long list search %d in stream_hash[%u]", ++ cnt, hash); ++ } ++ ++#if PERFTUNE ++ atomic_dec(&ht->concurrency_cnt); ++ ++ if (parallel > 2 && (limit++ % 100 == 0)) ++ msg_info(PROBE, "Did it in parallel, concurrency count:%d", ++ parallel); ++#endif ++ ++ return entry; ++} ++ ++static struct pid_data_t * ++mpeg2ts_pid_find(struct mpeg2ts_stream *stream, const int16_t pid) ++{ ++ struct pid_data_t *entry; ++ ++ list_for_each_entry(entry, &stream->pid_list, list) { ++ if (entry->pid == pid) ++ return entry; ++ } ++ return NULL; ++} ++ ++static struct pid_data_t * ++mpeg2ts_pid_create(struct mpeg2ts_stream *stream, const int16_t pid) ++{ ++ struct pid_data_t *entry; ++ ++ entry = kmalloc(sizeof(*entry), GFP_ATOMIC); ++ if (!entry) { ++ msg_err(DRV, "can't allocate new pid list entry"); ++ return NULL; ++ } ++ entry->pid = pid; ++ entry->cc_prev = -1; ++ ++ stream->pid_list_len++; ++ ++ list_add_tail(&entry->list, &stream->pid_list); ++ ++ return entry; ++} ++ ++static int ++mpeg2ts_pid_destroy_list(struct mpeg2ts_stream *stream) ++{ ++ struct pid_data_t *entry, *n; ++ ++ msg_dbg(PROBE, "Cleanup up pid list with %d elements", ++ stream->pid_list_len); ++ ++ list_for_each_entry_safe(entry, n, &stream->pid_list, list) { ++ stream->pid_list_len--; ++ kfree(entry); ++ } ++ WARN_ON(stream->pid_list_len != 0); ++ return stream->pid_list_len; ++} ++ ++static struct mpeg2ts_stream * ++mpeg2ts_stream_alloc_init(struct xt_rule_mpeg2ts_conn_htable *ht, ++ const struct mpeg2ts_stream_match *match) ++{ ++ struct mpeg2ts_stream *entry; /* hashtable entry */ ++ unsigned int entry_sz; ++ size_t size; ++ uint32_t hash; ++ ++ /* initialize hash with random val at the time we allocate ++ * the first hashtable entry */ ++ if (unlikely(!ht->rnd_initialized)) { ++ spin_lock_bh(&ht->lock); ++ if (unlikely(!ht->rnd_initialized)) { ++ get_random_bytes(&ht->rnd, 4); ++ ht->rnd_initialized = 1; ++ } ++ spin_unlock_bh(&ht->lock); ++ } ++ ++ /* DoS protection / embedded feature, for protection the size ++ * of the hash table lists. Limit the number of streams the ++ * module are willing to track. This limit is configurable ++ * from userspace. Can also be useful on small CPU/memory ++ * systems. */ ++ if (ht->cfg.max && ht->count >= ht->cfg.max) { ++ if (unlikely(ht->warn_condition < 10)) { ++ ht->warn_condition++; ++ msg_warn(RX_ERR, ++ "Rule[%u]: " ++ "Stopped tracking streams, max %u exceeded (%u) " ++ "(Max can be adjusted via --max-streams param)", ++ ht->id, ht->cfg.max, ht->count); ++ } ++ return NULL; ++ } ++ ++ /* Calc the hash value */ ++ hash = hash_match(ht, match); ++ ++ /* Allocate new stream element */ ++ /* entry = kmem_cache_alloc(hashlimit_cachep, GFP_ATOMIC); */ ++ size = entry_sz = sizeof(*entry); ++ /* msg_info(IFUP, "Alloc new stream entry (%d bytes)", entry_sz); */ ++ ++ entry = kzalloc(entry_sz, GFP_ATOMIC); ++ if (!entry) { ++ msg_err(DRV, "can't allocate new stream elem"); ++ return NULL; ++ } ++ memcpy(&entry->match, match, sizeof(entry->match)); ++ ++ spin_lock_init(&entry->lock); ++ atomic_set(&entry->use, 1); ++ ++ /* Init the pid table list */ ++ INIT_LIST_HEAD(&entry->pid_list); ++ entry->pid_list_len = 0; ++ ++ /* init the RCU callback structure needed by call_rcu() */ ++ /* The INIT_RCU_HEAD macro/function got removed in 2.6.36, ++ * from what I can see in the git kernel changelogs it should ++ * be safe to drop that init call. ++ */ ++ /* INIT_RCU_HEAD(&entry->rcu_head); */ ++ ++ /* Q Locking: Adding and deleting elements from the ++ * stream_hash[] lists is protected by the spinlock ht->lock. ++ * Should we only use try lock and exit if we cannot get it??? ++ * I'm worried about what happens if we are waiting for the ++ * lock held by xt_mpeg2ts_mt_destroy() which will dealloc ht ++ */ ++ spin_lock_bh(&ht->lock); ++ hlist_add_head_rcu(&entry->node, &ht->stream_hash[hash]); ++ ht->count++; /* Convert to atomic? Its write protected by ht->lock */ ++ spin_unlock_bh(&ht->lock); ++ ++ return entry; ++} ++ ++/* ++ * The xt_mpeg2ts_mt_check() / checkentry, return type logic differs ++ * between kernel versions. Fortunately the compat_xtables ++ * module/system handles the different cases. ++ */ ++static int ++xt_mpeg2ts_mt_check(const struct xt_mtchk_param *par) ++{ ++ struct xt_mpeg2ts_mtinfo *info = par->matchinfo; ++ ++ /* ++ Add check to see if drop-detect is off and match-drop is on, ++ which would result in this rule can never match anything ++ */ ++ if (!(info->flags & XT_MPEG2TS_DETECT_DROP)) ++ if (info->flags & XT_MPEG2TS_MATCH_DROP) { ++ msg_err(DRV, "Rejected: Rule will never match"); ++ return -EINVAL; ++ } ++ ++ /* Debugging, this should not be possible */ ++ if (!info) { ++ msg_err(DRV, "ERROR info is NULL"); ++ return -EINVAL; ++ } ++ ++ /* Debugging, this should not be possible */ ++ if (IS_ERR_VALUE((unsigned long)(info->hinfo))) { ++ msg_err(DRV, "ERROR info->hinfo is an invalid pointer!!!"); ++ return -EFAULT; ++ } ++ ++ /* TODO/FIXME: Add a check to NOT allow proc files with same ++ * name in /proc/net/xt_mpeg2ts/rule_%s */ ++ ++ ++ /* TODO: Write about how, this preserves htable memory by ++ * reuse of hinfo pointer and incrementing 'use' refcounter ++ * assures that xt_mpeg2ts_mt_destroy() will not call ++ * conn_htable_destroy() thus not deallocating our memory */ ++ if (info->hinfo != NULL) { ++ atomic_inc(&info->hinfo->use); ++ msg_info(DEBUG, "ReUsing info->hinfo ptr:[%p] htable id:%u", ++ info->hinfo, info->hinfo->id); ++ return 0; /* success */ ++ } ++ ++ if (!mpeg2ts_htable_create(info)) { ++ msg_err(DRV, "Error creating hash table"); ++ return -ENOMEM; ++ } ++ ++ return 0; /* success */ ++} ++ ++static void ++mpeg2ts_stream_free(struct rcu_head *head) ++{ ++ struct mpeg2ts_stream *stream; ++ ++ stream = container_of(head, struct mpeg2ts_stream, rcu_head); ++ ++ /* Debugging check */ ++ if (unlikely(!stream)) ++ printk(KERN_CRIT PFX ++ "Free BUG: Stream ptr is NULL (tell:author)\n"); ++ ++ /* Deallocate the PID list */ ++ spin_lock_bh(&stream->lock); ++ mpeg2ts_pid_destroy_list(stream); ++ spin_unlock_bh(&stream->lock); ++ ++ /* Before free, check the 'use' reference counter */ ++ if (atomic_dec_and_test(&stream->use)) { ++ kfree(stream); ++ } else { ++ /* If this can occur, we should schedule something ++ * that can clean up */ ++ printk(KERN_CRIT PFX ++ "Free BUG: Stream still in use! (tell:author)\n"); ++ } ++} ++ ++static void ++conn_htable_destroy(struct xt_rule_mpeg2ts_conn_htable *ht) ++{ ++ unsigned int i; ++ ++ /* Remove proc entry */ ++ proc_remove(ht->pde); ++ ++ msg_info(IFDOWN, "Destroy stream elements (%u count) in htable(%u)", ++ ht->count, ht->id); ++ msg_dbg(IFDOWN, "Find stream, not found %d times", ++ ht->stream_not_found); ++ ++ /* lock hash table and iterate over it to release all elements */ ++ spin_lock(&ht->lock); ++ for (i = 0; i < ht->cfg.size; i++) { ++ struct mpeg2ts_stream *stream; ++ struct hlist_node *n; ++ HLIST_NODE_POS; ++ hlist_for_each_entry_safe(stream, ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0) ++ pos, ++#endif ++ n, &ht->stream_hash[i], node) { ++ ++ hlist_del_rcu(&stream->node); ++ ht->count--; ++ ++ /* Have to use call_rcu(), because we cannot ++ use synchronize_rcu() here, because we are ++ holding a spinlock, or else we will get a ++ "scheduling while atomic" bug. ++ */ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ++ call_rcu_bh(&stream->rcu_head, mpeg2ts_stream_free); ++#else ++ call_rcu(&stream->rcu_head, mpeg2ts_stream_free); ++#endif ++ } ++ } ++ spin_unlock(&ht->lock); ++ ++ msg_info(IFDOWN, ++ "Free htable(%u) (%u buckets) longest list search %d", ++ ht->id, ht->cfg.size, ht->max_list_search); ++ ++ if (ht->count != 0) ++ printk(KERN_CRIT PFX ++ "Free BUG: ht->count != 0 (tell:author)\n"); ++ ++ kfree(ht); ++} ++ ++ ++/* ++ * Keeping dynamic allocated memory when the rulesets are swapped. ++ * ++ * Iptables rule updates works by replacing the entire ruleset. Our ++ * dynamic allocated data (per rule) needs to survive this update, BUT ++ * only if our rule has not been removed. This is achieved by having ++ * a reference counter. The reason it works, is that during swapping ++ * of rulesets, the checkentry function (xt_mpeg2ts_mt_check) is called ++ * on the new ruleset _before_ calling the destroy function ++ * (xt_mpeg2ts_mt_destroy) on the old ruleset. During checkentry, we ++ * increment the reference counter on data if we can find the data ++ * associated with this rule. ++ * ++ * Functions used to achieve this is: ++ * conn_htable_get() - Find data and increment refcnt ++ * conn_htable_put() - Finished usind data, delete if last user ++ * conn_htable_add() - Add data to the global searchable list ++ */ ++ ++static struct xt_rule_mpeg2ts_conn_htable* ++conn_htable_get(uint32_t rule_id) ++{ ++ struct xt_rule_mpeg2ts_conn_htable *hinfo; ++ ++ spin_lock_bh(&mpeg2ts_lock); ++ list_for_each_entry(hinfo, &conn_htables, list) { ++ if (hinfo->id == rule_id) { ++ atomic_inc(&hinfo->use); ++ spin_unlock_bh(&mpeg2ts_lock); ++ return hinfo; ++ } ++ } ++ spin_unlock_bh(&mpeg2ts_lock); ++ return NULL; ++} ++ ++static void ++conn_htable_put(struct xt_rule_mpeg2ts_conn_htable *hinfo) ++{ ++ /* Finished using element, delete if last user */ ++ if (atomic_dec_and_test(&hinfo->use)) { ++ spin_lock_bh(&mpeg2ts_lock); ++ list_del(&hinfo->list); ++ spin_unlock_bh(&mpeg2ts_lock); ++ conn_htable_destroy(hinfo); ++ } ++} ++ ++static void ++conn_htable_add(struct xt_rule_mpeg2ts_conn_htable *hinfo) ++{ ++ spin_lock_bh(&mpeg2ts_lock); ++ list_add_tail(&conn_htables, &hinfo->list); ++ spin_unlock_bh(&mpeg2ts_lock); ++} ++ ++static void ++xt_mpeg2ts_mt_destroy(const struct xt_mtdtor_param *par) ++{ ++ const struct xt_mpeg2ts_mtinfo *info = par->matchinfo; ++ struct xt_rule_mpeg2ts_conn_htable *hinfo; ++ hinfo = info->hinfo; ++ ++ /* Calls only destroy if refcnt is zero */ ++ if (atomic_dec_and_test(&hinfo->use)) ++ conn_htable_destroy(hinfo); ++} ++ ++ ++/* Calc the number of skipped CC numbers. Note that this can easy ++ * overflow, and a value above 7 indicate several network packets ++ * could be lost. ++ */ ++static inline unsigned int ++calc_skips(unsigned int curr, unsigned int prev) ++{ ++ int res = 0; ++ ++ /* Only count the missing TS frames in between prev and curr. ++ * The "prev" frame CC number seen is confirmed received, its ++ * the next frames CC counter which is the first known missing ++ * TS frame ++ */ ++ prev += 1; ++ ++ /* Calc missing TS frame 'skips' */ ++ res = curr - prev; ++ ++ /* Handle wrap around */ ++ if (res < 0) ++ res += 16; ++ ++ return res; ++} ++ ++/* Return the number of skipped CC numbers */ ++static int ++detect_cc_drops(struct pid_data_t *pid_data, int8_t cc_curr, ++ const struct sk_buff *skb) ++{ ++ int8_t cc_prev; ++ int skips = 0; ++ ++ cc_prev = pid_data->cc_prev; ++ pid_data->cc_prev = cc_curr; ++ ++ /* Null packet always have a CC value equal 0 */ ++ if (pid_data->pid == 0x1fff) ++ return 0; ++ ++ /* FIXME: Handle adaptation fields and Remove this code */ ++ /* Its allowed that (cc_prev == cc_curr) if its an adaptation ++ * field. ++ */ ++ if (cc_prev == cc_curr) ++ return 0; ++ ++ /* Have not seen this pid before */ ++ if (cc_prev == -1) ++ return 0; ++ ++ /* Detect if CC is not increasing by one all the time */ ++ if (cc_curr != ((cc_prev+1) & MP2T_CC_MASK)) { ++ skips = calc_skips(cc_curr, cc_prev); ++ ++ msg_info(RX_STATUS, ++ "Detected drop pid:%d CC curr:%d prev:%d skips:%d", ++ pid_data->pid, cc_curr, cc_prev, skips); ++ ++ /* TODO: Do accounting per PID ? ++ pid_data->cc_skips += skips; ++ pid_data->cc_err++; ++ */ ++ } ++ ++ return skips; ++} ++ ++ ++static int ++dissect_tsp(const unsigned char *payload_ptr, uint16_t payload_len, ++ const struct sk_buff *skb, struct mpeg2ts_stream *stream) ++{ ++ __be32 header; ++ uint16_t pid; ++ uint8_t afc; ++ int8_t cc_curr; ++ int skips = 0; ++ struct pid_data_t *pid_data; ++ ++ /* ++ * Process header. TSP headers come every MP2T_PACKET_SIZE bytes, ++ * which is a multiple of 32 bits, so not using get_unaligned ++ * is ok here. ++ */ ++ header = ntohl(*(uint32_t *)payload_ptr); ++ pid = (header & MP2T_PID_MASK) >> MP2T_PID_SHIFT; ++ afc = (header & MP2T_AFC_MASK) >> MP2T_AFC_SHIFT; ++ cc_curr = (header & MP2T_CC_MASK) >> MP2T_CC_SHIFT; ++ ++ msg_dbg(PKTDATA, "TS header:0x%X pid:%d cc:%d afc:%u", ++ header, pid, cc_curr, afc); ++ ++ /* Adaption Field Control header */ ++ if (unlikely(afc == 2)) { ++ /* An 'adaptation field only' packet will have the ++ * same CC value as the previous payload packet. */ ++ return 0; ++ /* TODO: Add parsing of Adaption headers. The PCR ++ * counter is hidden here...*/ ++ } ++ ++ pid_data = mpeg2ts_pid_find(stream, pid); ++ if (!pid_data) { ++ pid_data = mpeg2ts_pid_create(stream, pid); ++ if (!pid_data) ++ return 0; ++ } ++ ++ ++ skips = detect_cc_drops(pid_data, cc_curr, skb); ++ ++ return skips; ++} ++ ++ ++static int ++dissect_mpeg2ts(const unsigned char *payload_ptr, uint16_t payload_len, ++ const struct sk_buff *skb, const struct udphdr *uh, ++ const struct xt_mpeg2ts_mtinfo *info) ++{ ++ uint16_t offset = 0; ++ int skips = 0; ++ int skips_total = 0; ++ int discontinuity = 0; ++ const struct iphdr *iph = ip_hdr(skb); ++ ++ struct mpeg2ts_stream *stream; /* "Connection" */ ++ struct mpeg2ts_stream_match match; ++ ++ struct xt_rule_mpeg2ts_conn_htable *hinfo; ++ hinfo = info->hinfo; ++ ++ /** Lookup stream data structures **/ ++ ++ /* Fill in the match struct */ ++ memset(&match, 0, sizeof(match)); /* Worried about struct padding */ ++ match.src_addr = iph->saddr; ++ match.dst_addr = iph->daddr; ++ match.src_port = uh->source; ++ match.dst_port = uh->dest; ++ ++ /* spin_lock_bh(&hinfo->lock); // Replaced by RCU */ ++ rcu_read_lock_bh(); ++ ++ stream = mpeg2ts_stream_find(hinfo, &match); ++ if (!stream) { ++ stream = mpeg2ts_stream_alloc_init(hinfo, &match); ++ if (!stream) { ++ /* spin_unlock_bh(&hinfo->lock); // Replaced by RCU */ ++ rcu_read_unlock_bh(); ++ return 0; ++ } ++ /* msg_info(RX_STATUS, */ ++ printk(KERN_INFO ++ "Rule:%u New stream (%pI4 -> %pI4)\n", ++ hinfo->id, &iph->saddr, &iph->daddr); ++ } ++ ++ /** Process payload **/ ++ ++ spin_lock_bh(&stream->lock); /* Update lock for the stream */ ++ ++ /* Protect against dealloc (via atomic counter stream->use) */ ++ if (!atomic_inc_not_zero(&stream->use)) { ++ /* If "use" is zero, then we about to be free'd */ ++ spin_unlock_bh(&stream->lock); /* Update lock for the stream */ ++ rcu_read_unlock_bh(); ++ printk(KERN_CRIT PFX "Error atomic stream->use is zero\n"); ++ return 0; ++ } ++ ++ while ((payload_len - offset) >= MP2T_PACKET_SIZE) { ++ ++ skips = dissect_tsp(payload_ptr, payload_len, skb, stream); ++ ++ if (skips > 0) ++ discontinuity++; ++ /* TODO: if (skips > 7) signal_loss++; */ ++ skips_total += skips; ++ ++ offset += MP2T_PACKET_SIZE; ++ payload_ptr += MP2T_PACKET_SIZE; ++ } ++ ++ // TODO: Add flag to avoid/disable this update, for perf testing ++ stream->payload_bytes += payload_len; ++ stream->packets++; ++ ++ if (discontinuity > 0) { ++ stream->skips += skips_total; ++ stream->discontinuity += discontinuity; ++ } ++ ++ atomic_dec(&stream->use); /* Protect agains dealloc */ ++ spin_unlock_bh(&stream->lock); /* Update lock for the stream */ ++ rcu_read_unlock_bh(); ++ /* spin_unlock_bh(&hinfo->lock); // Replaced by RCU */ ++ ++ /* Place print statement after the unlock section */ ++ if (discontinuity > 0) { ++ msg_notice(RX_STATUS, ++ "Detected discontinuity " ++ "%pI4 -> %pI4 (CCerr:%d skips:%d)", ++ &ip_hdr(skb)->saddr, &ip_hdr(skb)->daddr, ++ discontinuity, skips_total); ++ } ++ ++ return skips_total; ++} ++ ++static bool ++is_mpeg2ts_packet(const unsigned char *payload_ptr, uint16_t payload_len) ++{ ++ uint16_t offset = 0; ++ ++ /* IDEA/TODO: Detect wrong/changing TS mappings */ ++ ++ /* Basic payload Transport Stream check */ ++ if (payload_len % MP2T_PACKET_SIZE > 0) { ++ msg_dbg(PKTDATA, "Not a MPEG2 TS packet, wrong size"); ++ return false; ++ } ++ ++ /* Check for a sync byte in all TS frames */ ++ while ((payload_len - offset) >= MP2T_PACKET_SIZE) { ++ ++ if (payload_ptr[0] != MP2T_SYNC_BYTE) { ++ msg_dbg(PKTDATA, "Invalid MPEG2TS packet skip!"); ++ return false; ++ } ++ offset += MP2T_PACKET_SIZE; ++ payload_ptr += MP2T_PACKET_SIZE; ++ } ++ /* msg_dbg(PKTDATA, "True MPEG2TS packet"); */ ++ ++ return true; ++} ++ ++ ++static bool ++xt_mpeg2ts_match(const struct sk_buff *skb, struct xt_action_param *par) ++{ ++ const struct xt_mpeg2ts_mtinfo *info = par->matchinfo; ++ const struct iphdr *iph = ip_hdr(skb); ++ const struct udphdr *uh; ++ struct udphdr _udph; ++ __be32 saddr, daddr; ++ uint16_t ulen; ++ uint16_t hdr_size; ++ uint16_t payload_len; ++ const unsigned char *payload_ptr; ++ ++ bool res = true; ++ int skips = 0; ++ ++ /* ++ if (!(info->flags & XT_MPEG2TS_DETECT_DROP)) { ++ msg_err(RX_ERR, "You told me to do nothing...?!"); ++ return false; ++ } ++ */ ++ ++ /* ++ if (!pskb_may_pull((struct sk_buff *)skb, sizeof(struct udphdr))) ++ return false; ++ */ ++ ++ saddr = iph->saddr; ++ daddr = iph->daddr; ++ ++ /* Must not be a fragment. */ ++ if (par->fragoff != 0) { ++ msg_warn(RX_ERR, "Skip cannot handle fragments " ++ "(pkt from:%pI4 to:%pI4) len:%u datalen:%u" ++ , &saddr, &daddr, skb->len, skb->data_len); ++ return false; ++ } ++ ++ /* We need to walk through the payload data, and I don't want ++ * to handle fragmented SKBs, the SKB has to be linearized */ ++ if (skb_is_nonlinear(skb)) { ++ if (skb_linearize((struct sk_buff *)skb) != 0) { ++ msg_err(RX_ERR, "SKB linearization failed" ++ "(pkt from:%pI4 to:%pI4) len:%u datalen:%u", ++ &saddr, &daddr, skb->len, skb->data_len); ++ /* TODO: Should we just hotdrop it? ++ *par->hotdrop = true; ++ */ ++ return false; ++ } ++ } ++ ++ uh = skb_header_pointer(skb, par->thoff, sizeof(_udph), &_udph); ++ if (unlikely(uh == NULL)) { ++ /* Something is wrong, cannot even access the UDP ++ * header, no choice but to drop. */ ++ msg_err(RX_ERR, "Dropping evil UDP tinygram " ++ "(pkt from:%pI4 to:%pI4)", &saddr, &daddr); ++ par->hotdrop = true; ++ return false; ++ } ++ ulen = ntohs(uh->len); ++ ++ /* How much do we need to skip to access payload data */ ++ hdr_size = par->thoff + sizeof(struct udphdr); ++ payload_ptr = skb_network_header(skb) + hdr_size; ++ /* payload_ptr = skb->data + hdr_size; */ ++ BUG_ON(payload_ptr != (skb->data + hdr_size)); ++ ++ /* Different ways to determine the payload_len. Think the ++ * safest is to use the skb->len, as we really cannot trust ++ * the contents of the packet. ++ payload_len = ntohs(iph->tot_len)- hdr_size; ++ payload_len = ulen - sizeof(struct udphdr); ++ */ ++ payload_len = skb->len - hdr_size; ++ ++/* Not sure if we need to clone packets ++ if (skb_shared(skb)) ++ msg_dbg(RX_STATUS, "skb(0x%p) shared", skb); ++ ++ if (!skb_cloned(skb)) ++ msg_dbg(RX_STATUS, "skb(0x%p) NOT cloned", skb); ++*/ ++ ++ if (is_mpeg2ts_packet(payload_ptr, payload_len)) { ++ msg_dbg(PKTDATA, "Jubii - its a MPEG2TS packet"); ++ ++ if (!(info->flags & XT_MPEG2TS_DETECT_DROP)) { ++ /* ! --drop-detect */ ++ /* Don't perform drop detection, just match mpeg2ts */ ++ res = true; ++ } else { ++ skips = dissect_mpeg2ts(payload_ptr, payload_len, ++ skb, uh, info); ++ } ++ } else { ++ msg_dbg(PKTDATA, "Not a MPEG2 TS packet " ++ "(pkt from:%pI4 to:%pI4)", &saddr, &daddr); ++ return false; ++ } ++ ++ if (info->flags & XT_MPEG2TS_MATCH_DROP) ++ res = !!(skips); /* Convert to a bool */ ++ ++ return res; ++} ++ ++static struct xt_match mpeg2ts_mt_reg __read_mostly = { ++ .name = "mpeg2ts", ++ .revision = 0, ++ .family = NFPROTO_IPV4, ++ .match = xt_mpeg2ts_match, ++ .checkentry = xt_mpeg2ts_mt_check, ++ .destroy = xt_mpeg2ts_mt_destroy, ++ .proto = IPPROTO_UDP, ++ .matchsize = sizeof(struct xt_mpeg2ts_mtinfo), ++ .me = THIS_MODULE, ++}; ++ ++ ++/*** Proc seq_file functionality ***/ ++ ++static void *mpeg2ts_seq_start(struct seq_file *s, loff_t *pos) ++{ ++ struct inode *inode = s->private; ++ struct xt_rule_mpeg2ts_conn_htable *htable = PDE_DATA(inode); ++ unsigned int *bucket; ++ ++ if (*pos >= htable->cfg.size) ++ return NULL; ++ ++ if (!*pos) ++ return SEQ_START_TOKEN; ++ ++ bucket = kmalloc(sizeof(unsigned int), GFP_ATOMIC); ++ if (!bucket) ++ return ERR_PTR(-ENOMEM); ++ ++ *bucket = *pos; ++ return bucket; ++} ++ ++static void *mpeg2ts_seq_next(struct seq_file *s, void *v, loff_t *pos) ++{ ++ struct inode *inode = s->private; ++ struct xt_rule_mpeg2ts_conn_htable *htable = PDE_DATA(inode); ++ unsigned int *bucket = v; ++ ++ if (v == SEQ_START_TOKEN) { ++ bucket = kmalloc(sizeof(unsigned int), GFP_ATOMIC); ++ if (!bucket) ++ return ERR_PTR(-ENOMEM); ++ *bucket = 0; ++ *pos = 0; ++ v = bucket; ++ return bucket; ++ } ++ ++ *pos = ++(*bucket); ++ if (*pos >= htable->cfg.size) { ++ kfree(v); ++ return NULL; ++ } ++ return bucket; ++} ++ ++static void mpeg2ts_seq_stop(struct seq_file *s, void *v) ++{ ++ unsigned int *bucket = v; ++ kfree(bucket); ++} ++ ++static int mpeg2ts_seq_show_real(struct mpeg2ts_stream *stream, ++ struct seq_file *s, unsigned int bucket) ++{ ++ int res; ++ ++ if (!atomic_inc_not_zero(&stream->use)) { ++ /* If "use" is zero, then we about to be free'd */ ++ return 0; ++ } ++ ++ seq_printf(s, "bucket:%d dst:%pI4 src:%pI4 dport:%u sport:%u " ++ "pids:%d skips:%llu discontinuity:%llu " ++ "payload_bytes:%llu packets:%llu\n", ++ bucket, ++ &stream->match.dst_addr, ++ &stream->match.src_addr, ++ ntohs(stream->match.dst_port), ++ ntohs(stream->match.src_port), ++ stream->pid_list_len, ++ stream->skips, ++ stream->discontinuity, ++ stream->payload_bytes, ++ stream->packets ++ ); ++ ++ atomic_dec(&stream->use); ++ ++ return res; ++} ++ ++static int mpeg2ts_seq_show(struct seq_file *s, void *v) ++{ ++ struct inode *inode = s->private; ++ struct xt_rule_mpeg2ts_conn_htable *htable = PDE_DATA(inode); ++ unsigned int *bucket = v; ++ struct mpeg2ts_stream *stream; ++ struct timespec64 delta; ++ struct timespec64 now; ++ HLIST_NODE_POS; ++ ++ /* ++ The syntax for the proc output is "key:value" constructs, ++ seperated by a space. This is done to ease machine/script ++ parsing and still keeping it human readable. ++ */ ++ ++ if (v == SEQ_START_TOKEN) { ++ getnstimeofday64(&now); ++ delta = timespec64_sub(now, htable->time_created); ++ ++ /* version info */ ++ seq_printf(s, "# info:version module:%s version:%s\n", ++ XT_MODULE_NAME, XT_MODULE_VERSION); ++ ++ /* time info */ ++ seq_printf(s, "# info:time created:%ld.%09lu" ++ " now:%ld.%09lu delta:%ld.%09lu\n", ++ (long)htable->time_created.tv_sec, ++ htable->time_created.tv_nsec, ++ (long)now.tv_sec, now.tv_nsec, ++ (long)delta.tv_sec, delta.tv_nsec); ++ ++ /* dynamic info */ ++ seq_puts(s, "# info:dynamic"); ++ seq_printf(s, " rule_id:%u", htable->id); ++ seq_printf(s, " streams:%d", htable->count); ++ seq_printf(s, " streams_check:%d", htable->stream_not_found); ++ seq_printf(s, " max_list_search:%d", htable->max_list_search); ++ seq_printf(s, " rnd:%u", htable->rnd); ++ seq_puts(s, "\n"); ++ ++ /* config info */ ++ seq_puts(s, "# info:config"); ++ seq_printf(s, " htable_size:%u", htable->cfg.size); ++ seq_printf(s, " max-streams:%u", htable->cfg.max); ++ seq_printf(s, " list_search_warn_level:%d", ++ htable->cfg.max_list); ++ seq_puts(s, "\n"); ++ ++ } else { ++ rcu_read_lock(); ++ if (!hlist_empty(&htable->stream_hash[*bucket])) { ++ hlist_for_each_entry_rcu(stream, ++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,8,0) ++ pos, ++#endif ++ &htable->stream_hash[*bucket], ++ node) { ++ if (mpeg2ts_seq_show_real(stream, s, *bucket)) { ++ rcu_read_unlock(); ++ return -1; ++ } ++ } ++ } ++ rcu_read_unlock(); ++ } ++ return 0; ++} ++ ++static const struct seq_operations dl_seq_ops = { ++ .start = mpeg2ts_seq_start, ++ .next = mpeg2ts_seq_next, ++ .stop = mpeg2ts_seq_stop, ++ .show = mpeg2ts_seq_show ++}; ++ ++static int mpeg2ts_proc_open(struct inode *inode, struct file *file) ++{ ++ int ret = seq_open(file, &dl_seq_ops); ++ ++ if (!ret) { ++ struct seq_file *sf = file->private_data; ++ sf->private = inode; ++ } ++ return ret; ++} ++ ++static const struct file_operations dl_file_ops = { ++ .owner = THIS_MODULE, ++ .open = mpeg2ts_proc_open, ++ .read = seq_read, ++ .llseek = seq_lseek, ++ .release = seq_release ++}; ++ ++/*** Module init & exit ***/ ++ ++static int __init mpeg2ts_mt_init(void) ++{ ++ int err; ++ GLOBAL_ID = 1; /* Module counter for rule_id assignments */ ++ ++ /* The list conn_htables contain references to dynamic ++ * allocated memory (via xt_rule_mpeg2ts_conn_htable ptr) that ++ * needes to survive between rule updates. ++ */ ++ INIT_LIST_HEAD(&conn_htables); ++ ++ msg_level = netif_msg_init(debug, MPEG2TS_MSG_DEFAULT); ++ msg_info(DRV, "Loading: %s", version); ++ msg_dbg(DRV, "Message level (msg_level): 0x%X", msg_level); ++ ++ /* Register the mpeg2ts matches */ ++ err = xt_register_match(&mpeg2ts_mt_reg); ++ if (err) { ++ msg_err(DRV, "unable to register matches"); ++ return err; ++ } ++ ++#ifdef CONFIG_PROC_FS ++ /* Create proc directory shared by all rules */ ++ mpeg2ts_procdir = proc_mkdir(XT_MODULE_NAME, init_net.proc_net); ++ if (!mpeg2ts_procdir) { ++ msg_err(DRV, "unable to create proc dir entry"); ++ /* In case of error unregister the mpeg2ts match */ ++ xt_unregister_match(&mpeg2ts_mt_reg); ++ err = -ENOMEM; ++ } ++#endif ++ ++ return err; ++} ++ ++static void __exit mpeg2ts_mt_exit(void) ++{ ++ msg_info(DRV, "Unloading: %s", version); ++ ++ remove_proc_entry(XT_MODULE_NAME, init_net.proc_net); ++ ++ xt_unregister_match(&mpeg2ts_mt_reg); ++ ++ /* Its important to wait for all call_rcu_bh() callbacks to ++ * finish before this module is deallocated as the code ++ * mpeg2ts_stream_free() is used by these callbacks. ++ * ++ * Notice doing a synchronize_rcu() is NOT enough. Need to ++ * invoke rcu_barrier_bh() to enforce wait for completion of ++ * call_rcu_bh() callbacks on all CPUs. ++ */ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) ++ rcu_barrier_bh(); ++#else ++ rcu_barrier(); ++#endif ++} ++ ++module_init(mpeg2ts_mt_init); ++module_exit(mpeg2ts_mt_exit); +diff -uNr xtables-addons-3.24.orig/extensions/xt_mpeg2ts.h xtables-addons-3.24/extensions/xt_mpeg2ts.h +--- xtables-addons-3.24.orig/extensions/xt_mpeg2ts.h 1970-01-01 01:00:00.000000000 +0100 ++++ xtables-addons-3.24/extensions/xt_mpeg2ts.h 2024-05-24 13:16:12.395671624 +0200 +@@ -0,0 +1,76 @@ ++/* ++ * Header file for MPEG2 TS match extension "mpeg2ts" for Xtables. ++ * ++ * Copyright (c) Jesper Dangaard Brouer <netoptimizer@brouer.com>, 2009-2013 ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License; either ++ * version 2 of the License, or any later version, as published by the ++ * Free Software Foundation. ++ * ++ */ ++#ifndef _LINUX_NETFILTER_XT_MPEG2TS_MATCH_H ++#define _LINUX_NETFILTER_XT_MPEG2TS_MATCH_H 1 ++ ++/* Get version number PACKAGE_VERSION from configure system */ ++#include "../config.h" ++ ++/* XT_MODULE_NAME could be replaced by KBUILD_MODNAME, if this version ++ info were only used the kernel module, but we also use it in ++ userspace. ++*/ ++#define XT_MODULE_NAME "xt_mpeg2ts" ++#define XT_MODULE_VERSION PACKAGE_VERSION ++#define PFX XT_MODULE_NAME ": " ++ ++static char version[] = ++ XT_MODULE_NAME ".c:v" XT_MODULE_VERSION \ ++ " (part of " PACKAGE_NAME ")"; ++ ++enum { ++ XT_MPEG2TS_DETECT_DROP = 1 << 0, ++ XT_MPEG2TS_MAX_STREAMS = 1 << 1, ++ XT_MPEG2TS_PARAM_NAME = 1 << 2, ++ XT_MPEG2TS_MATCH_DROP = 1 << 3, ++ /*Future:*/ ++ XT_MPEG2TS_RECORD_COUNTERS = 1 << 4, ++}; ++ ++/* Details of this hash structure is hidden in kernel space xt_mpeg2ts.c */ ++struct xt_rule_mpeg2ts_conn_htable; ++ ++struct mpeg2ts_cfg { ++ ++ /* Hash table setup */ ++ __u32 size; /* how many hash buckets */ ++ __u32 max; /* max number of entries */ ++ __u32 max_list; /* warn if list searches exceed this number */ ++}; ++ ++ ++struct xt_mpeg2ts_mtinfo { ++ __u16 flags; ++ ++ /* FIXME: ++ ++ I need to fix the problem, where I have to reallocated data ++ each time a single rule change occur. ++ ++ The idea with rule_name and rule_id is that the name is ++ optional, simply to provide a name in /proc/, the rule_id ++ is the real lookup-key in the internal kernel list of the ++ rules associated dynamic-allocated-data. ++ ++ */ ++ char rule_name[IFNAMSIZ]; ++ ++ struct mpeg2ts_cfg cfg; ++ ++ /** Below used internally by the kernel **/ ++ __u32 rule_id; ++ ++ /* Hash table pointer */ ++ struct xt_rule_mpeg2ts_conn_htable *hinfo __attribute__((aligned(8))); ++}; ++ ++#endif /* _LINUX_NETFILTER_XT_MPEG2TS_MATCH_H */ +diff -uNr xtables-addons-3.24.orig/mconfig xtables-addons-3.24/mconfig +--- xtables-addons-3.24.orig/mconfig 2024-05-24 13:15:28.150705304 +0200 ++++ xtables-addons-3.24/mconfig 2024-05-24 13:16:12.395671624 +0200 +@@ -21,6 +21,7 @@ + build_ipv4options=m + build_length2=m + build_lscan=m ++build_mpeg2ts=m + build_pknock=m + build_psd=m + build_quota2=m -- GitLab