From 742b50d21b8dc7669b5ec9556f556288c0705530 Mon Sep 17 00:00:00 2001 From: Nigel Kukard <nkukard@lbsd.net> Date: Sat, 14 Sep 2013 17:51:51 +0000 Subject: [PATCH] Refactored code, added support for overrides --- opentrafficshaper/plugins/configmanager.pm | 729 +++++++++++++-------- 1 file changed, 467 insertions(+), 262 deletions(-) diff --git a/opentrafficshaper/plugins/configmanager.pm b/opentrafficshaper/plugins/configmanager.pm index eb92e76..5c5804d 100644 --- a/opentrafficshaper/plugins/configmanager.pm +++ b/opentrafficshaper/plugins/configmanager.pm @@ -43,12 +43,17 @@ our (@ISA,@EXPORT,@EXPORT_OK); setLimitAttribute getLimitAttribute + getOverride + getOverrides + getShaperState setShaperState getTrafficClasses - getPriorityName + getTrafficClassName + + isTrafficClassValid ); use constant { @@ -63,42 +68,61 @@ use constant { }; # Mandatory config attributes -sub CONFIG_ATTRIBUTES { +sub LIMIT_REQUIRED_ATTRIBUTES { qw( Username IP GroupID ClassID - TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst + TrafficLimitTx TrafficLimitRx Expires Status Source ) } -# Changeset attributes - things that can be changed on the fly -sub CHANGESET_ATTRIBUTES { +# Limit Changeset attributes - things that can be changed on the fly in the shaper +sub LIMIT_CHANGESET_ATTRIBUTES { qw( GroupID ClassID TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst - Expires ) } # Persistent attributes supported -sub PERSISTENT_ATTRIBUTES { +sub LIMIT_PERSISTENT_ATTRIBUTES { qw( Username IP GroupID ClassID - TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst + TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst TrafficPriority + FriendlyName Notes Expires Created Source ) } -# Override attributes supported -sub OVERRIDE_ATTRIBUTES { +# Mandatory override attribute, one is required +sub OVERRIDE_REQUIRED_ATTRIBUTES { qw( + Username IP + GroupID + ) +} +# Override changeset attributes +sub OVERRIDE_CHANGESET_ATTRIBUTES { + qw( + ClassID + TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst + ) +} +# Override attributes supported for persistent storage +sub OVERRIDE_PERSISTENT_ATTRIBUTES { + qw( + Key Username IP GroupID ClassID TrafficLimitTx TrafficLimitRx TrafficLimitTxBurst TrafficLimitRxBurst + FriendlyName Notes + Expires Created + Source + LastUpdate ) } @@ -140,11 +164,76 @@ my $config = { # Pending changes my $changeQueue = { }; -# Main variables handling our limits +# +# LIMITS +# +# Supoprted user attributes: +# * Username +# - Users username +# * IP +# - Users IP +# * GroupID +# - Group ID +# * ClassID +# - Class ID +# * TrafficLimitTx +# - Traffic limit in kbps +# * TrafficLimitRx +# - Traffic limit in kbps +# * TrafficLimitTxBurst +# - Traffic bursting limit in kbps +# * TrafficLimitRxBurst +# - Traffic bursting limit in kbps +# * Expires +# - Unix timestamp when this entry expires, 0 if never +# * FriendlyName +# - Used for display purposes instead of username if specified +# * Notes +# - Notes on this limit +# * Status +# - new +# - offline +# - online +# - unknown +# * Source +# - This is the source of the limit, typically plugin.ModuleName my $limits = { }; my $limitIPMap = { }; my $limitIDMap = { }; my $limitIDCounter = 1; + +# +# OVERRIDES +# +# Selection criteria: +# * Username +# - Users username +# * IP +# - Users IP +# * GroupID +# - Group ID +# +# Overrides: +# * ClassID +# - Class ID +# * TrafficLimitTx +# - Traffic limit in kbps +# * TrafficLimitRx +# - Traffic limit in kbps +# * TrafficLimitTxBurst +# - Traffic bursting limit in kbps +# * TrafficLimitRxBurst +# - Traffic bursting limit in kbps +# +# Parameters: +# * FriendlyName +# - Used for display purposes +# * Expires +# - Unix timestamp when this entry expires, 0 if never +# * Notes +# - Notes on this limit +# * Source +# - This is the source of the limit, typically plugin.ModuleName my $overrides = { }; @@ -246,7 +335,9 @@ sub plugin_init _stop => \&session_stop, tick => \&session_tick, - process_change => \&process_change, + + process_limit_change => \&process_limit_change, + process_override_change => \&process_override_change, handle_SIGHUP => \&handle_SIGHUP, } @@ -273,7 +364,7 @@ sub session_start # Load config if (-f $config->{'statefile'}) { - _load_statefile(); + _load_statefile($kernel); } else { $logger->log(LOG_WARN,"[CONFIGMANAGER] Statefile '$config->{'statefile'}' cannot be opened: $!"); } @@ -314,216 +405,30 @@ sub session_stop # Time ticker for processing changes -sub session_tick { +sub session_tick +{ my $kernel = $_[KERNEL]; - - # Now - my $now = time(); - - - # - # LOOP WITH CHANGES - # - - foreach my $lid (keys %{$changeQueue}) { - # Changes for limit - # Minimum required info is: - # - Username - # - IP - # - Status - # - LastUpdate - my $climit = $changeQueue->{$lid}; - - # - # LIMIT IN LIST - # - if (defined(my $glimit = $limits->{$lid})) { - - # This is a new limit notification - if ($climit->{'Status'} eq "new") { - $logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit already live but new state provided?"); - - # Get the changes we made and push them to the shaper - if (my $changes = _getChangeset($glimit,$climit)) { - # Post to shaper - $kernel->post("shaper" => "change" => $lid => $changes); - } - - # Remove from change queue - delete($changeQueue->{$lid}); - - # Online or "ping" status notification - } elsif ($climit->{'Status'} eq "online") { - $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit still online"); - - # Get the changes we made and push them to the shaper - if (my $changes = _getChangeset($glimit,$climit)) { - # Post to shaper - $kernel->post("shaper" => "change" => $lid => $changes); - } - - # Remove from change queue - delete($changeQueue->{$lid}); - - # Offline notification, this we going to treat specially - } elsif ($climit->{'Status'} eq "offline") { - - # We first check if this update was received some time ago, and if it exceeds our expire time - # We don't want to immediately remove a limit, only for him to come back on a few seconds later, the cost in exec()'s - # would be pretty high - if ($now - $climit->{'LastUpdate'} > TIMEOUT_EXPIRE_OFFLINE) { - - # Remove entry if no longer live - if ($glimit->{'_shaper.state'} == SHAPER_NOTLIVE) { - $logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid] offline and removed from shaper"); - - # Remove from system - delete($limits->{$lid}); - # Remove from change queue - delete($changeQueue->{$lid}); - # Set this UID as no longer using this IP - # NK: If we try remove it before the limit is actually removed we could get a reconnection causing this value - # to be totally gone, which means we not tracking this limit using this IP anymore, not easily solved!! - delete($limitIPMap->{$glimit->{'IP'}}->{$lid}); - # Check if we can delete the IP too - if (keys %{$limitIPMap->{$glimit->{'IP'}}} == 0) { - delete($limitIPMap->{$glimit->{'IP'}}); - } - - # Next record, we don't want to do any updates below - next; - - # Push to shaper - } elsif ($glimit->{'_shaper.state'} == SHAPER_LIVE) { - $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid] offline, queue remove from shaper"); - - # Post removal to shaper - $kernel->post("shaper" => "remove" => $lid); - - } else { - $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit in list, but offline now and". - " expired, still live, waiting for shaper"); - } - } - } - - # Update the limit data - $glimit->{'Status'} = $climit->{'Status'}; - $glimit->{'LastUpdate'} = $climit->{'LastUpdate'}; - $glimit->{'Expires'} = $climit->{'Expires'}; - - # - # LIMIT NOT IN LIST - # - } else { - # We take new and online notifications the same way here if the limit is not in our global limit list already - if (($climit->{'Status'} eq "new" || $climit->{'Status'} eq "online")) { - $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Processing new limit '$climit->{'Username'}' [$lid]"); - - # We first going to look for IP conflicts... - my @ipLimits = keys %{$limitIPMap->{$climit->{'IP'}}}; - if ( - # If there is already an entry and its not us ... - ( @ipLimits == 1 && !defined($limitIPMap->{$climit->{'IP'}}->{$lid}) ) - # Or if there is more than 1 entry... - || @ipLimits > 1 - ) { - # We not going to post this to the shaper, but we are going to override the status - $climit->{'Status'} = 'conflict'; - $climit->{'_shaper.state'} = SHAPER_NOTLIVE; - # Give a bit of info - my @conflictUsernames; - foreach my $lid (@ipLimits) { - push(@conflictUsernames,$limits->{$lid}->{'Username'}); - } - # Output log line - $logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit '".$climit->{'Username'}."' IP '$climit->{'IP'}' conflicts with users '". - join(',',@conflictUsernames)."'."); - - # We cannot trust shaping when there is more than 1 limit on the IP, so we going to remove all limits with this - # IP from the shaper below... - foreach my $lid2 (@ipLimits) { - # Check if the limit has been setup already (all but the limit we busy with, as its setup below) - if (defined($limitIPMap->{$climit->{'IP'}}->{$lid2})) { - my $glimit2 = $limits->{$lid2}; - - # If the limit is active or pending on the shaper, remove it - if ($glimit2->{'_shaper.state'} == SHAPER_LIVE || $glimit2->{'_shaper.state'} == SHAPER_PENDING) { - $logger->log(LOG_WARN,"[CONFIGMANAGER] Removing conflicted limit '".$glimit2->{'Username'}."' [$lid2] from shaper'"); - # Post removal to shaper - $kernel->post("shaper" => "remove" => $lid2); - # Update that we're offline directly to global limit table - $glimit2->{'Status'} = 'conflict'; - } - } - } - - # All looks good, no conflicts, we're set to add this limit! - } else { - # Post to the limit to the shaper - $climit->{'_shaper.state'} = SHAPER_PENDING; - $kernel->post("shaper" => "add" => $lid); - - } - - # Set this UID as using this IP - $limitIPMap->{$climit->{'IP'}}->{$lid} = 1; - - # This is now live - $limits->{$lid} = $climit; - - # Limit is not in our list and this is an unknown state we're trasitioning to - } else { - $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Ignoring limit '$climit->{'Username'}' [$lid] state '$climit->{'Status'}', not in our". - " global list"); - } - - # Remove from change queue - delete($changeQueue->{$lid}); - } - - } - - - # - # CHECK OUT CONNECTED LIMITS - # - foreach my $lid (keys %{$limits}) { - # Global limit - my $glimit = $limits->{$lid}; - - -if (!defined($glimit->{'Expires'})) { - use Data::Dumper; warn "UNDEFINED: ".Dumper($glimit,$lid); -} - - # Check for expired limits - if ($glimit->{'Expires'} && $glimit->{'Expires'} < $now) { - $logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$glimit->{'Username'}' has expired, marking offline"); - # Looks like this limit has expired? - my $climit = { - 'Username' => $glimit->{'Username'}, - 'IP' => $glimit->{'IP'}, - 'Status' => 'offline', - 'LastUpdate' => $glimit->{'LastUpdate'}, - }; - # Add to change queue - $changeQueue->{$lid} = $climit; - } - } + _process_limit_change_queue($kernel); # Reset tick $kernel->delay(tick => TICK_PERIOD); -}; - +} -# Process shaper change -sub process_change +# Process limit change +sub process_limit_change { my ($kernel, $limit) = @_[KERNEL, ARG0]; - _process_change($limit); + _process_limit_change($limit); +} + +# Process override change +sub process_override_change +{ + my ($kernel, $override) = @_[KERNEL, ARG0]; + + _process_override_change($override); } @@ -590,6 +495,7 @@ sub getLimits return (keys %{$limits}); } + # Function to set a limit attribute sub setLimitAttribute { @@ -618,6 +524,26 @@ sub getLimitAttribute } +# Function to return a override +sub getOverride +{ + my $oid = shift; + + if (defined($overrides->{$oid})) { + my %override = %{$overrides->{$oid}}; + return \%override; + } + return; +} + + +# Function to return a list of override ID's +sub getOverrides +{ + return (keys %{$overrides}); +} + + # Function to set shaper state on a limit sub setShaperState { @@ -649,11 +575,22 @@ sub getTrafficClasses } -# Function to get priority name -sub getPriorityName +# Function to get class name +sub getTrafficClassName { - my $prio = shift; - return $config->{'classes'}->{$prio}; + my $class = shift; + return $config->{'classes'}->{$class}; +} + + +# Function to check if traffic class is valid +sub isTrafficClassValid +{ + my $class = shift; + if (defined($config->{'classes'}->{$class})) { + return $class; + } + return; } @@ -672,14 +609,14 @@ sub handle_SIGHUP # # Function to compute the changes between two users -sub _getChangeset +sub _getLimitChangeset { my ($orig,$new) = @_; my $res; # Loop through what can change - foreach my $item (CHANGESET_ATTRIBUTES) { + foreach my $item (LIMIT_CHANGESET_ATTRIBUTES) { # Check if its first set, if it is, check if its changed if (defined($new->{$item}) && $orig->{$item} ne $new->{$item}) { # If so record it & make the change @@ -692,53 +629,39 @@ sub _getChangeset # This is the real function -# Supoprted user attributes: -# -# Username -# - Users username -# IP -# - Users IP -# GroupID -# - Group ID -# ClassID -# - Class ID -# TrafficLimitTx -# - Traffic limit in kbps -# TrafficLimitRx -# - Traffic limit in kbps -# TrafficLimitTxBurst -# - Traffic bursting limit in kbps -# TrafficLimitRxBurst -# - Traffic bursting limit in kbps -# Expires -# - Unix timestamp when this entry expires, 0 if never -# Status -# - new -# - offline -# - online -# - unknown -# Source -# - This is the source of the limit, typically plugin.ModuleName -sub _process_change +sub _process_limit_change { my $limit = shift; + # Check if we have all the attributes we need + my $isInvalid; + foreach my $attr (LIMIT_REQUIRED_ATTRIBUTES) { + if (!defined($limit->{$attr})) { + $isInvalid = $attr; + last; + } + } + if ($isInvalid) { + $logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit change as not attributes is missing: '$isInvalid'"); + return; + } + # We start off blank so we only pull in whats supported my $limitChange; - if (!($limitChange->{'Username'} = $limit->{'Username'})) { + if (!defined($limitChange->{'Username'} = $limit->{'Username'})) { $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change as username is invalid."); return; } $limitChange->{'Username'} = $limit->{'Username'}; $limitChange->{'IP'} = $limit->{'IP'}; # Check group is OK - if (!($limitChange->{'GroupID'} = checkGroupID($limit->{'GroupID'}))) { + if (!defined($limitChange->{'GroupID'} = checkGroupID($limit->{'GroupID'}))) { $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change for '".$limit->{'Username'}."' as the GroupID is invalid."); return; } # Check class is OK - if (!($limitChange->{'ClassID'} = checkClassID($limit->{'ClassID'}))) { + if (!defined($limitChange->{'ClassID'} = checkClassID($limit->{'ClassID'}))) { $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Cannot process limit change for '".$limit->{'Username'}."' as the ClassID is invalid."); return; } @@ -765,6 +688,10 @@ sub _process_change # Set when this entry expires $limitChange->{'Expires'} = defined($limit->{'Expires'}) ? $limit->{'Expires'} : 0; + # Set friendly name and notes + $limitChange->{'FriendlyName'} = $limit->{'FriendlyName'}; + $limitChange->{'Notes'} = $limit->{'Notes'}; + # Set when this entry was created $limitChange->{'Created'} = defined($limit->{'Created'}) ? $limit->{'Created'} : time(); @@ -788,15 +715,82 @@ sub _process_change # Set the user ID before we post to the change queue $limitChange->{'ID'} = $lid; $limitChange->{'LastUpdate'} = time(); - # Push change to change queue $changeQueue->{$lid} = $limitChange; } +# This is the real process_override_change function +sub _process_override_change +{ + my $override = shift; + + + # Pull in mandatory items and check if the result is valid + my $overrideChange; + my $isValid = 0; + foreach my $item (OVERRIDE_REQUIRED_ATTRIBUTES) { + $overrideChange->{$item} = $override->{$item}; + $isValid++; + } + # Make sure we have at least 1 + if (!$isValid) { + $logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process override as there is no selection attribute"); + return; + } + + # Pull in attributes that can be changed + foreach my $item (OVERRIDE_CHANGESET_ATTRIBUTES) { + $overrideChange->{$item} = $override->{$item}; + } + + # Check group is OK + if (defined($overrideChange->{'GroupID'}) && !checkGroupID($overrideChange->{'GroupID'})) { + $logger->log(LOG_DEBUG,'[CONFIGMANAGER] Cannot process override for "User: %s, IP: %s, GroupID: %s" as the GroupID is invalid.', + prettyUndef($overrideChange->{'Username'}),prettyUndef($overrideChange->{'IP'}),prettyUndef($overrideChange->{'GroupID'}) + ); + return; + } + + # Check class is OK + if (defined($overrideChange->{'ClassID'}) && !checkClassID($overrideChange->{'ClassID'})) { + $logger->log(LOG_DEBUG,'[CONFIGMANAGER] Cannot process override for "User: %s, IP: %s, GroupID: %s" as the ClassID is invalid.', + prettyUndef($overrideChange->{'Username'}),prettyUndef($overrideChange->{'IP'}),prettyUndef($overrideChange->{'GroupID'}) + ); + return; + } + + # Set when this entry expires + $overrideChange->{'Expires'} = defined($override->{'Expires'}) ? $override->{'Expires'} : 0; + + # Set friendly name and notes + $overrideChange->{'FriendlyName'} = $override->{'FriendlyName'}; + $overrideChange->{'Notes'} = $override->{'Notes'}; + + # Set when this entry was created + $overrideChange->{'Created'} = defined($override->{'Created'}) ? $override->{'Created'} : time(); + + $overrideChange->{'LastUpdate'} = time(); + + # This is our key for this entry + my $oid = sprintf('%s%%%s%%%s', + defined($overrideChange->{'Username'}) ? $overrideChange->{'Username'} : "", + defined($overrideChange->{'IP'}) ? $overrideChange->{'IP'} : "", + defined($overrideChange->{'GroupID'}) ? $overrideChange->{'GroupID'} : "" + ); + # Set the user ID before we post to the change queue + $overrideChange->{'Key'} = $oid; + + $overrides->{$oid} = $overrideChange; +} + + # Load our statefile sub _load_statefile { + my $kernel = shift; + + # Check if the state file exists first of all if (! -e $config->{'statefile'}) { $logger->log(LOG_INFO,"[CONFIGMANAGER] Statefile '".$config->{'statefile'}."' doesn't exist"); @@ -822,19 +816,19 @@ sub _load_statefile # Our user override my $ouser; - foreach my $attr (OVERRIDE_ATTRIBUTES) { + foreach my $attr (OVERRIDE_PERSISTENT_ATTRIBUTES) { if (defined($override->{$attr})) { $ouser->{$attr} = $override->{$attr}; } } - # Check username & IP are defined - if (!defined($ouser->{'Username'})) { - $logger->log(LOG_WARN,"[CONFIGMANAGER] Failed to load user override with no username '$section'"); + # Check username, IP or gorup ID is defined + if (!defined($ouser->{'Key'})) { + $logger->log(LOG_WARN,"[CONFIGMANAGER] Failed to load override with no Key '$section'"); next; } - $overrides->{$ouser->{'Username'}} = $ouser; + $overrides->{$ouser->{'Key'}} = $ouser; } # Loop with persistent users @@ -843,7 +837,7 @@ sub _load_statefile # User to push through to process change my $cuser; - foreach my $attr (PERSISTENT_ATTRIBUTES) { + foreach my $attr (LIMIT_PERSISTENT_ATTRIBUTES) { if (defined($user->{$attr})) { $cuser->{$attr} = $user->{$attr}; } @@ -853,12 +847,12 @@ sub _load_statefile # Check username & IP are defined if (!defined($cuser->{'Username'}) || !defined($cuser->{'IP'})) { - $logger->log(LOG_WARN,"[CONFIGMANAGER] Failed to load persistent user with no username or no IP '$section'"); + $logger->log(LOG_WARN,"[CONFIGMANAGER] Failed to load persistent user with no username or IP '$section'"); next; } # Process this user - _process_change($cuser); + _process_limit_change($cuser); } } @@ -872,6 +866,12 @@ sub _write_statefile return; } + # Only write out if we actually have users, else we may of crashed? + if (keys %{$limits} < 1) { + $logger->log(LOG_WARN,"[CONFIGMANAGER] Not writing state file as there are no active users"); + return; + } + # Create new state file object my $state = new Config::IniFiles(); @@ -886,7 +886,7 @@ sub _write_statefile # Add a new section for this user $state->AddSection($section); # Items we want for persistent entries - foreach my $pItem (PERSISTENT_ATTRIBUTES) { + foreach my $pItem (LIMIT_PERSISTENT_ATTRIBUTES) { # Set items up if (defined(my $value = $limits->{$lid}->{$pItem})) { $state->newval($section,$pItem,$value); @@ -902,7 +902,7 @@ sub _write_statefile # Add a new section for this user $state->AddSection($section); # Items we want for override entries - foreach my $pItem (OVERRIDE_ATTRIBUTES) { + foreach my $pItem (OVERRIDE_PERSISTENT_ATTRIBUTES) { # Set items up if (defined(my $value = $overrides->{$username}->{$pItem})) { $state->newval($section,$pItem,$value); @@ -921,5 +921,210 @@ sub _write_statefile } +# Do the actual queue processing +sub _process_limit_change_queue +{ + my $kernel = shift; + + + # Now + my $now = time(); + + # + # LOOP WITH CHANGES + # + + foreach my $lid (keys %{$changeQueue}) { + # Changes for limit + # Minimum required info is: + # - Username + # - IP + # - Status + # - LastUpdate + my $climit = $changeQueue->{$lid}; + + # + # LIMIT IN LIST + # + if (defined(my $glimit = $limits->{$lid})) { + + # This is a new limit notification + if ($climit->{'Status'} eq "new") { + $logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit already live but new state provided?"); + + # Get the changes we made and push them to the shaper + if (my $changes = _getLimitChangeset($glimit,$climit)) { + # Post to shaper + $kernel->post("shaper" => "change" => $lid => $changes); + } + + # Remove from change queue + delete($changeQueue->{$lid}); + + # Online or "ping" status notification + } elsif ($climit->{'Status'} eq "online") { + $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit still online"); + + # Get the changes we made and push them to the shaper + if (my $changes = _getLimitChangeset($glimit,$climit)) { + # Post to shaper + $kernel->post("shaper" => "change" => $lid => $changes); + } + + # Remove from change queue + delete($changeQueue->{$lid}); + + # Offline notification, this we going to treat specially + } elsif ($climit->{'Status'} eq "offline") { + + # We first check if this update was received some time ago, and if it exceeds our expire time + # We don't want to immediately remove a limit, only for him to come back on a few seconds later, the cost in exec()'s + # would be pretty high + if ($now - $climit->{'LastUpdate'} > TIMEOUT_EXPIRE_OFFLINE) { + + # Remove entry if no longer live + if ($glimit->{'_shaper.state'} == SHAPER_NOTLIVE) { + $logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid] offline and removed from shaper"); + + # Remove from system + delete($limits->{$lid}); + # Remove from change queue + delete($changeQueue->{$lid}); + # Set this UID as no longer using this IP + # NK: If we try remove it before the limit is actually removed we could get a reconnection causing this value + # to be totally gone, which means we not tracking this limit using this IP anymore, not easily solved!! + delete($limitIPMap->{$glimit->{'IP'}}->{$lid}); + # Check if we can delete the IP too + if (keys %{$limitIPMap->{$glimit->{'IP'}}} == 0) { + delete($limitIPMap->{$glimit->{'IP'}}); + } + + # Next record, we don't want to do any updates below + next; + + # Push to shaper + } elsif ($glimit->{'_shaper.state'} == SHAPER_LIVE) { + $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid] offline, queue remove from shaper"); + + # Post removal to shaper + $kernel->post("shaper" => "remove" => $lid); + + } else { + $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Limit '$climit->{'Username'}' [$lid], limit in list, but offline now and". + " expired, still live, waiting for shaper"); + } + } + } + + # Update the limit data + $glimit->{'Status'} = $climit->{'Status'}; + $glimit->{'LastUpdate'} = $climit->{'LastUpdate'}; + $glimit->{'Expires'} = $climit->{'Expires'}; + + # Set these if they exist + if (defined($climit->{'FriendlyName'})) { + $glimit->{'FriendlyName'} = $climit->{'FriendlyName'}; + } + if (defined($climit->{'Notes'})) { + $glimit->{'Notes'} = $climit->{'Notes'}; + } + + # + # LIMIT NOT IN LIST + # + } else { + # We take new and online notifications the same way here if the limit is not in our global limit list already + if (($climit->{'Status'} eq "new" || $climit->{'Status'} eq "online")) { + $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Processing new limit '$climit->{'Username'}' [$lid]"); + + # We first going to look for IP conflicts... + my @ipLimits = keys %{$limitIPMap->{$climit->{'IP'}}}; + if ( + # If there is already an entry and its not us ... + ( @ipLimits == 1 && !defined($limitIPMap->{$climit->{'IP'}}->{$lid}) ) + # Or if there is more than 1 entry... + || @ipLimits > 1 + ) { + # We not going to post this to the shaper, but we are going to override the status + $climit->{'Status'} = 'conflict'; + $climit->{'_shaper.state'} = SHAPER_NOTLIVE; + # Give a bit of info + my @conflictUsernames; + foreach my $lid (@ipLimits) { + push(@conflictUsernames,$limits->{$lid}->{'Username'}); + } + # Output log line + $logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit '".$climit->{'Username'}."' IP '$climit->{'IP'}' conflicts with users '". + join(',',@conflictUsernames)."'."); + + # We cannot trust shaping when there is more than 1 limit on the IP, so we going to remove all limits with this + # IP from the shaper below... + foreach my $lid2 (@ipLimits) { + # Check if the limit has been setup already (all but the limit we busy with, as its setup below) + if (defined($limitIPMap->{$climit->{'IP'}}->{$lid2})) { + my $glimit2 = $limits->{$lid2}; + + # If the limit is active or pending on the shaper, remove it + if ($glimit2->{'_shaper.state'} == SHAPER_LIVE || $glimit2->{'_shaper.state'} == SHAPER_PENDING) { + $logger->log(LOG_WARN,"[CONFIGMANAGER] Removing conflicted limit '".$glimit2->{'Username'}."' [$lid2] from shaper'"); + # Post removal to shaper + $kernel->post("shaper" => "remove" => $lid2); + # Update that we're offline directly to global limit table + $glimit2->{'Status'} = 'conflict'; + } + } + } + + # All looks good, no conflicts, we're set to add this limit! + } else { + # Post to the limit to the shaper + $climit->{'_shaper.state'} = SHAPER_PENDING; + $kernel->post("shaper" => "add" => $lid); + + } + + # Set this UID as using this IP + $limitIPMap->{$climit->{'IP'}}->{$lid} = 1; + + # This is now live + $limits->{$lid} = $climit; + + # Limit is not in our list and this is an unknown state we're trasitioning to + } else { + $logger->log(LOG_DEBUG,"[CONFIGMANAGER] Ignoring limit '$climit->{'Username'}' [$lid] state '$climit->{'Status'}', not in our". + " global list"); + } + + # Remove from change queue + delete($changeQueue->{$lid}); + } + + } + + + # + # CHECK OUT CONNECTED LIMITS + # + foreach my $lid (keys %{$limits}) { + # Global limit + my $glimit = $limits->{$lid}; + + # Check for expired limits + if ($glimit->{'Expires'} && $glimit->{'Expires'} < $now) { + $logger->log(LOG_INFO,"[CONFIGMANAGER] Limit '$glimit->{'Username'}' has expired, marking offline"); + # Looks like this limit has expired? + my $climit = { + 'Username' => $glimit->{'Username'}, + 'IP' => $glimit->{'IP'}, + 'Status' => 'offline', + 'LastUpdate' => $glimit->{'LastUpdate'}, + }; + # Add to change queue + $changeQueue->{$lid} = $climit; + } + } +} + + 1; # vim: ts=4 -- GitLab