]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
ACL: Add connection limit type
authorJaroslav Kysela <perex@perex.cz>
Thu, 4 Jun 2015 20:08:19 +0000 (22:08 +0200)
committerJaroslav Kysela <perex@perex.cz>
Fri, 5 Jun 2015 07:45:14 +0000 (09:45 +0200)
docs/html/config_access.html
src/access.c
src/access.h
src/dvr/dvr_rec.c
src/tcp.c
src/webui/static/app/acleditor.js

index 2b163bae782fd6a04ec8e67501a40bb3d4dbb14d..dfde67b712ce390786f0b1c1cb43fd82942f9b8d 100644 (file)
@@ -78,6 +78,10 @@ The columns have the following functions:
   <dd>
   Specify a streaming profile to be used when this user logs in; use the (default) stream if not specified.
   
+  <dt><b>Connection Limit Type</b>
+  <dd>
+  Allow to restrict the limit for connections to streaming or DVR only.
+
   <dt><b>Limit Connections</b>
   <dd>
   If set, this will limit the number of concurrent streaming connections and
index 1e57ca2b089af8ea9a67757cb87357b4584db822..21c8ec74f58e3aa68d92ee4293306813c2e14c8e 100644 (file)
@@ -369,7 +369,7 @@ access_dump_a(access_t *a)
   int first;
 
   tvh_strlcatf(buf, sizeof(buf), l,
-    "%s:%s [%c%c%c%c%c%c%c%c%c%c], conn=%u%s",
+    "%s:%s [%c%c%c%c%c%c%c%c%c%c], conn=%u:s%u:r%u%s",
     a->aa_representative ?: "<no-id>",
     a->aa_username ?: "<no-user>",
     a->aa_rights & ACCESS_STREAMING          ? 'S' : ' ',
@@ -383,6 +383,8 @@ access_dump_a(access_t *a)
     a->aa_rights & ACCESS_FAILED_RECORDER    ? 'F' : ' ',
     a->aa_rights & ACCESS_ADMIN              ? '*' : ' ',
     a->aa_conn_limit,
+    a->aa_conn_limit_streaming,
+    a->aa_conn_limit_dvr,
     a->aa_match ? ", matched" : "");
 
   if (a->aa_profiles) {
@@ -443,12 +445,37 @@ access_dump_a(access_t *a)
   tvhtrace("access", "%s", buf);
 }
 
+/*
+ *
+ */
+static access_t *access_alloc(void)
+{
+  return calloc(1, sizeof(access_t));
+}
+
 /*
  *
  */
 static void
 access_update(access_t *a, access_entry_t *ae)
 {
+  switch (ae->ae_conn_limit_type) {
+  case ACCESS_CONN_LIMIT_TYPE_ALL:
+    if (a->aa_conn_limit < ae->ae_conn_limit)
+      a->aa_conn_limit = ae->ae_conn_limit;
+    break;
+  case ACCESS_CONN_LIMIT_TYPE_STREAMING:
+    if (ae->ae_conn_limit && a->aa_conn_limit_streaming < ae->ae_conn_limit) {
+      a->aa_conn_limit_streaming = ae->ae_conn_limit;
+      a->aa_conn_limit = 0;
+    }
+    break;
+  case ACCESS_CONN_LIMIT_TYPE_DVR:
+    if (a->aa_conn_limit_dvr < ae->ae_conn_limit)
+      a->aa_conn_limit_dvr = ae->ae_conn_limit;
+    break;
+  }
+
   if(a->aa_conn_limit < ae->ae_conn_limit)
     a->aa_conn_limit = ae->ae_conn_limit;
 
@@ -488,7 +515,7 @@ access_update(access_t *a, access_entry_t *ae)
 access_t *
 access_get(const char *username, const char *password, struct sockaddr *src)
 {
-  access_t *a = calloc(1, sizeof(*a));
+  access_t *a = access_alloc();
   access_entry_t *ae;
   int nouser = username == NULL || username[0] == '\0';
 
@@ -556,7 +583,7 @@ access_t *
 access_get_hashed(const char *username, const uint8_t digest[20],
                  const uint8_t *challenge, struct sockaddr *src)
 {
-  access_t *a = calloc(1, sizeof(*a));
+  access_t *a = access_alloc();
   access_entry_t *ae;
   int nouser = username == NULL || username[0] == '\0';
 
@@ -622,7 +649,7 @@ access_get_hashed(const char *username, const uint8_t digest[20],
 access_t *
 access_get_by_username(const char *username)
 {
-  access_t *a = calloc(1, sizeof(*a));
+  access_t *a = access_alloc();
   access_entry_t *ae;
 
   a->aa_username = strdup(username);
@@ -656,7 +683,7 @@ access_get_by_username(const char *username)
 access_t *
 access_get_by_addr(struct sockaddr *src)
 {
-  access_t *a = calloc(1, sizeof(*a));
+  access_t *a = access_alloc();
   access_entry_t *ae;
 
   a->aa_representative = malloc(50);
@@ -1201,6 +1228,18 @@ access_entry_profile_get(void *o)
   return &ret;
 }
 
+static htsmsg_t *
+access_entry_conn_limit_type_enum ( void *p )
+{
+  static struct strtab
+  conn_limit_type_tab[] = {
+    { "All (Streaming + DVR)",  ACCESS_CONN_LIMIT_TYPE_ALL },
+    { "Streaming",              ACCESS_CONN_LIMIT_TYPE_STREAMING   },
+    { "DVR",                    ACCESS_CONN_LIMIT_TYPE_DVR },
+  };
+  return strtab2htsmsg(conn_limit_type_tab);
+}
+
 const idclass_t access_entry_class = {
   .ic_class      = "access",
   .ic_caption    = "Access",
@@ -1315,6 +1354,13 @@ const idclass_t access_entry_class = {
       .name     = "Admin",
       .off      = offsetof(access_entry_t, ae_admin),
     },
+    {
+      .type     = PT_INT,
+      .id       = "conn_limit_type",
+      .name     = "Connection Limit Type",
+      .off      = offsetof(access_entry_t, ae_conn_limit_type),
+      .list     = access_entry_conn_limit_type_enum,
+    },
     {
       .type     = PT_U32,
       .id       = "conn_limit",
index 411febacdcf2278b3e7e43ecb82c64f9c32371e8..952428fbf223239b5dd61ac595e7c7b464c08d19 100644 (file)
@@ -63,6 +63,12 @@ TAILQ_HEAD(access_entry_queue, access_entry);
 
 extern struct access_entry_queue access_entries;
 
+enum {
+  ACCESS_CONN_LIMIT_TYPE_ALL = 0,
+  ACCESS_CONN_LIMIT_TYPE_STREAMING,
+  ACCESS_CONN_LIMIT_TYPE_DVR,
+};
+
 typedef struct access_entry {
   idnode_t ae_id;
 
@@ -80,6 +86,7 @@ typedef struct access_entry {
   struct profile *ae_profile;
   LIST_ENTRY(access_entry) ae_profile_link;
 
+  int ae_conn_limit_type;
   uint32_t ae_conn_limit;
 
   int ae_dvr;
@@ -117,6 +124,10 @@ typedef struct access {
   htsmsg_t *aa_chtags;
   int       aa_match;
   uint32_t  aa_conn_limit;
+  uint32_t  aa_conn_limit_streaming;
+  uint32_t  aa_conn_limit_dvr;
+  uint32_t  aa_conn_streaming;
+  uint32_t  aa_conn_dvr;
 } access_t;
 
 TAILQ_HEAD(access_ticket_queue, access_ticket);
index 48dfd8119c850433cf51aaf5b059738c7326e851..ee3d721d3e76dfb362372b951e304ceaba016e42 100644 (file)
@@ -70,6 +70,7 @@ dvr_rec_subscribe(dvr_entry_t *de)
   struct sockaddr sa;
   access_t *aa;
   uint32_t rec_count, net_count;
+  int c1, c2;
 
   assert(de->de_s == NULL);
   assert(de->de_chain == NULL);
@@ -92,12 +93,15 @@ dvr_rec_subscribe(dvr_entry_t *de)
     return -EPERM;
  }
 
-  if (aa->aa_conn_limit) {
+  if (aa->aa_conn_limit || aa->aa_conn_limit_dvr) {
     rec_count = dvr_usage_count(aa);
-    net_count = tcp_connection_count(aa);
-    if (rec_count + net_count >= aa->aa_conn_limit) {
+    net_count = aa->aa_conn_limit ? tcp_connection_count(aa) : 0;
+    /* the rule is: allow if one condition is OK */
+    c1 = aa->aa_conn_limit ? rec_count + net_count >= aa->aa_conn_limit : -1;
+    c2 = aa->aa_conn_limit_dvr ? rec_count >= aa->aa_conn_limit : -1;
+    if (c1 && c2) {
       tvherror("dvr", "multiple connections are not allowed for user '%s' from '%s' "
-                      "(limit %u, active streaming %u, active DVR %u)",
+                      "(limit %u, streaming %u, active DVR %u)",
                aa->aa_username ?: "", aa->aa_representative ?: "",
                aa->aa_conn_limit, rec_count, net_count);
       return -EOVERFLOW;
index f35de01eb7a48d045373ec49b191830e6207584f..7c5ce63119470a0a6cf1e3e5221f936ac8b7f9c7 100644 (file)
--- a/src/tcp.c
+++ b/src/tcp.c
@@ -461,6 +461,7 @@ tcp_connection_launch
   tcp_server_launch_t *tsl, *res;
   uint32_t used = 0, used2;
   time_t started = dispatch_clock;
+  int c1, c2;
 
   lock_assert(&global_lock);
 
@@ -474,7 +475,7 @@ try_again:
   LIST_FOREACH(tsl, &tcp_server_active, alink) {
     if (tsl->fd == fd) {
       res = tsl;
-      if (!aa->aa_conn_limit)
+      if (!aa->aa_conn_limit && !aa->aa_conn_limit_streaming)
         break;
       continue;
     }
@@ -484,20 +485,27 @@ try_again:
   if (res == NULL)
     return NULL;
 
-  if (aa->aa_conn_limit && used + (used2 = dvr_usage_count(aa)) >= aa->aa_conn_limit) {
-    if (started + 3 < dispatch_clock) {
-      tvherror("tcp", "multiple connections are not allowed for user '%s' from '%s' "
-                      "(limit %u, active streaming %u, active DVR %u)",
-               aa->aa_username ?: "", aa->aa_representative ?: "", aa->aa_conn_limit,
-               used, used2);
+  if (aa->aa_conn_limit || aa->aa_conn_limit_streaming) {
+    used2 = aa->aa_conn_limit ? dvr_usage_count(aa) : 0;
+    /* the rule is: allow if one condition is OK */
+    c1 = aa->aa_conn_limit ? used + used2 >= aa->aa_conn_limit : -1;
+    c2 = aa->aa_conn_limit_streaming ? used >= aa->aa_conn_limit_streaming : -1;
+
+    if (c1 && c2) {
+      if (started + 3 < dispatch_clock) {
+        tvherror("tcp", "multiple connections are not allowed for user '%s' from '%s' "
+                        "(limit %u, active streaming %u, DVR %u)",
+                 aa->aa_username ?: "", aa->aa_representative ?: "", aa->aa_conn_limit,
+                 used, used2);
+        return NULL;
+      }
+      pthread_mutex_unlock(&global_lock);
+      usleep(250000);
+      pthread_mutex_lock(&global_lock);
+      if (tvheadend_running)
+        goto try_again;
       return NULL;
     }
-    pthread_mutex_unlock(&global_lock);
-    usleep(250000);
-    pthread_mutex_lock(&global_lock);
-    if (tvheadend_running)
-      goto try_again;
-    return NULL;
   }
 
   res->representative = aa->aa_representative ? strdup(aa->aa_representative) : NULL;
index c8759461a520e74e0d23ddd7a6b0c2ea70584cac..c3951aa2a1f0da4d22eca78abc15da531a389144 100644 (file)
@@ -7,13 +7,15 @@ tvheadend.acleditor = function(panel, index)
     var list = 'enabled,username,password,prefix,' +
                'webui,admin,' +
                'streaming,adv_streaming,htsp_streaming,' +
-               'profile,conn_limit,dvr,htsp_dvr,all_dvr,all_rw_dvr,' +
+               'profile,conn_limit_type,conn_limit,' +
+               'dvr,htsp_dvr,all_dvr,all_rw_dvr,' +
               'dvr_config,channel_min,channel_max,channel_tag,comment';
 
     var list2 = 'enabled,username,password,prefix,' +
                 'webui,admin,' +
                 'streaming,adv_streaming,htsp_streaming,' +
-                'profile,conn_limit,dvr,htsp_dvr,all_dvr,all_rw_dvr,' +
+                'profile,conn_limit_type,conn_limit,' +
+                'dvr,htsp_dvr,all_dvr,all_rw_dvr,' +
                 'failed_dvr,dvr_config,channel_min,channel_max,channel_tag,' +
                 'comment';
 
@@ -36,6 +38,7 @@ tvheadend.acleditor = function(panel, index)
             all_rw_dvr:     { width: 150 },
             webui:          { width: 140 },
             admin:          { width: 100 },
+            conn_limit_type:{ width: 160 },
             conn_limit:     { width: 160 },
             channel_min:    { width: 160 },
             channel_max:    { width: 160 }