Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • opentrafficshaper/opentrafficshaper
  • eezysol/opentrafficshaper
  • sudesh/opentrafficshaper
  • Yuriy/opentrafficshaper
  • nkukard/opentrafficshaper
  • fcardoso/opentrafficshaper
6 results
Show changes
Showing
with 1440 additions and 1412 deletions
[Unit]
Description=opensource traffic shaping system
ConditionPathExists=/etc/opentrafficshaper/opentrafficshaper.conf
After=network.target shorewall.service shorewall6.service mariadb.service
[Service]
ExecStart=/opt/opentrafficshaper/opentrafficshaperd --fg --config=/etc/opentrafficshaper/opentrafficshaper.conf
RestartSec=10
Restart=on-failure
ProtectSystem=full
ProtectHome=on
PrivateTmp=on
PrivateDevices=on
ProtectKernelTunables=on
ProtectControlGroups=on
ExecPaths=/opt/opentrafficshaper/opentrafficshaperd
ReadOnlyPaths=/opt/opentrafficshaper
ReadWritePaths=/run/mysqld
ConfigurationDirectory=opentrafficshaper
StateDirectory=opentrafficshaper
RuntimeDirectory=opentrafficshaper
LogsDirectory=opentrafficshaper
StateDirectoryMode=0750
LogsDirectoryMode=0750
ConfigurationDirectoryMode=0750
[Install]
WantedBy=multi-user.target
\ No newline at end of file
......@@ -5,7 +5,7 @@ DROP TABLE IF EXISTS identifiers;
CREATE TABLE identifiers (
`ID` SERIAL,
`Identifier` VARCHAR(255) NOT NULL
) Engine=MyISAM;
) Engine=InnoDB;
/* For queries */
CREATE INDEX identifiers_idx1 ON identifiers (`Identifier`);
......@@ -28,12 +28,12 @@ CREATE TABLE stats (
`Limit` MEDIUMINT UNSIGNED NOT NULL,
`Rate` MEDIUMINT UNSIGNED NOT NULL,
`PPS` MEDIUMINT UNSIGNED NOT NULL,
`Queue_Len` MEDIUMINT UNSIGNED NOT NULL,
`Total_Bytes` BIGINT UNSIGNED NOT NULL,
`Total_Packets` BIGINT UNSIGNED NOT NULL,
`Total_Overlimits` BIGINT UNSIGNED NOT NULL,
`Total_Dropped` BIGINT UNSIGNED NOT NULL
) Engine=MyISAM;
`QueueLen` MEDIUMINT UNSIGNED NOT NULL,
`TotalBytes` BIGINT UNSIGNED NOT NULL,
`TotalPackets` BIGINT UNSIGNED NOT NULL,
`TotalOverlimits` BIGINT UNSIGNED NOT NULL,
`TotalDropped` BIGINT UNSIGNED NOT NULL
) Engine=InnoDB;
/* For queries */
CREATE INDEX stats_idx1 ON stats (`IdentifierID`);
......@@ -56,7 +56,7 @@ CREATE TABLE stats_basic (
`Timestamp` INTEGER UNSIGNED NOT NULL,
`Counter` BIGINT UNSIGNED NOT NULL
) Engine=MyISAM;
) Engine=InnoDB;
/* For queries */
CREATE INDEX stats_basic_idx1 ON stats (`IdentifierID`);
......
# Basic radius dictionary
# Copyright (C) 2009-2013, AllWorldIT
# Copyright (C) 2009-2015, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......
# AllWorldIT vendor radius dictionary
# Copyright (C) 2009-2013, AllWorldIT
# Copyright (C) 2009-2015, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -21,4 +21,5 @@ VENDOR AllWorldIT 42109
ATTRIBUTE OpenTrafficShaper-Traffic-Limit 1 string AllWorldIT
ATTRIBUTE OpenTrafficShaper-Traffic-Group 2 integer AllWorldIT
ATTRIBUTE OpenTrafficShaper-Traffic-Class 3 integer AllWorldIT
ATTRIBUTE OpenTrafficShaper-Traffic-Pool 4 string AllWorldIT
......@@ -19,7 +19,7 @@
# PID file to write our PID to
#
# default:
# pid_file=/var/run/opentrafficshaper/opentrafficshaper.pid
# pid_file=/run/opentrafficshaper/opentrafficshaper.pid
# State file, this file is used to store persistent information
......
# POE::Filter::HybridHTTP - Copyright 2013, AllworldIT
# POE::Filter::HybridHTTP - Copyright 2007-2023, AllworldIT
# Hybrid HTTP filter supporting websockets too.
#
# This program is free software: you can redistribute it and/or modify
......@@ -28,7 +28,7 @@
# and from HTTPD filters, they should submit their request as a patch.
##
package POE::Filter::HybridHTTP;
package opentrafficshaper::POE::Filter::HybridHTTP;
use warnings;
use strict;
......@@ -36,11 +36,11 @@ use strict;
use bytes;
use POE::Filter;
use POE::Filter::HybridHTTP::WebSocketFrame;
use opentrafficshaper::POE::Filter::HybridHTTP::WebSocketFrame;
use vars qw($VERSION @ISA);
# NOTE - Should be #.### (three decimal places)
$VERSION = '1.100';
$VERSION = '2.000';
@ISA = qw(POE::Filter);
......@@ -65,11 +65,11 @@ my $HTTP_1_1 = _http_version("HTTP/1.1");
# Class instantiation
sub new
sub new
{
my $class = shift;
# These are our internal properties
# These are our internal properties
my $self = { };
# Build our class
bless($self, $class);
......@@ -84,7 +84,7 @@ sub new
# From the docs:
# get_one_start() accepts an array reference containing unprocessed stream chunks. The chunks are added to the filter's Internal
# buffer for parsing by get_one().
sub get_one_start
sub get_one_start
{
my ($self, $stream) = @_;
......@@ -97,7 +97,7 @@ sub get_one_start
# This is called to see if we can grab records/items
sub get_one
sub get_one
{
my $self = shift;
......@@ -109,7 +109,7 @@ sub get_one
# Waiting for content.
} elsif ($self->{'state'} == ST_HTTP_CONTENT) {
return $self->_get_one_http_content();
# Websocket
} elsif ($self->{'state'} == ST_WEBSOCKET_STREAM) {
return $self->_get_one_websocket_record();
......@@ -122,7 +122,7 @@ sub get_one
# Function to push data to the socket
sub put
sub put
{
my ($self, $responses) = @_;
my @results;
......@@ -137,7 +137,7 @@ sub put
# Check if its a websocket upgrade
if (
# Is it a request and do we have a original request?
$h_upgrade eq "websocket" && defined($self->{'last_request'}) &&
$h_upgrade eq "websocket" && defined($self->{'last_request'}) &&
# If so was there a websocket-key?
(my $websocketKey = $self->{'last_request'}->header('Sec-WebSocket-Key'))
) {
......@@ -145,7 +145,7 @@ sub put
# GUID for this protocol as per RFC6455 Section 1.3
my $websocketKeyResponseRaw = $websocketKey."258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
my $websocketKeyResponse = sha1_base64($websocketKeyResponseRaw);
# Pad up to base64 length 4[N/3]
# Pad up to base64 length 4[N/3]
$websocketKeyResponse .= "=" x ((length($websocketKeyResponse) * 3) % 4);
$response->push_header('Sec-WebSocket-Accept',$websocketKeyResponse);
}
......@@ -160,7 +160,7 @@ sub put
foreach my $response (@{$responses}) {
# If we don't have a websocket state, create one
if (!$self->{'websocket_state'}) {
$self->{'websocket_state'} = new POE::Filter::HybridHTTP::WebSocketFrame();
$self->{'websocket_state'} = new opentrafficshaper::POE::Filter::HybridHTTP::WebSocketFrame();
}
# Don't mask replies from server to client RFC6455 secion 5.1.
$self->{'websocket_state'}->masked(0);
......@@ -201,7 +201,7 @@ sub _reset
# Internal function to parse an HTTP status line and return the HTTP
# protocol version.
sub _http_version
sub _http_version
{
my $version = shift;
......@@ -230,7 +230,7 @@ sub _get_one_http_headers
if ($self->{'buffer'} !~ s/^(\S.*?(?:\r?\n){2})//s) {
return [ ];
}
# Pull the headers as a string off the buffer
# Pull the headers as a string off the buffer
my $header_str = $1;
# Parse the request line.
......@@ -274,7 +274,7 @@ sub _get_one_http_headers
# We no longer matching, so this is the last header?
} else {
last HEADER;
}
}
}
# Push on the last header if we had one...
$request->push_header($key, $val) if $key;
......@@ -288,7 +288,7 @@ sub _get_one_http_headers
$content_length = int($content_length);
}
my $content_encoding = $request->content_encoding();
# The presence of a message-body in a request is signaled by the
# inclusion of a Content-Length or Transfer-Encoding header field in
# the request's message-headers. A message-body MUST NOT be included in
......@@ -322,17 +322,17 @@ sub _get_one_http_headers
# the server SHOULD respond with 400 (bad request) if it cannot
# determine the length of the message, or with 411 (length required) if
# it wishes to insist on receiving a valid Content-Length.
# - RFC2616
# - RFC2616
# PG- This seems to imply that we can either detect the length (but how
# would one do that?) or require a Content-Length header. We do the
# latter.
#
#
# PG- Dispite all the above, I'm not fully sure this implements RFC2616
# properly. There's something about transfer-coding that I don't fully
# understand.
if (!$content_length) {
if (!$content_length) {
# assume a Content-Length of 0 is valid pre 1.1
if ($proto >= $HTTP_1_1 && !defined($content_length)) {
# We have Content-Encoding, but not Content-Length.
......@@ -344,7 +344,7 @@ sub _get_one_http_headers
$self->{'content_length'} = $content_length;
$self->{'state'} = ST_HTTP_CONTENT;
$self->{'request'} = $request;
$self->{'request'} = $request;
$self->_get_one_http_content();
}
......@@ -404,7 +404,7 @@ sub _get_one_websocket_record
# If we don't have a websocket state, create one
if (!$self->{'websocket_state'}) {
$self->{'websocket_state'} = new POE::Filter::HybridHTTP::WebSocketFrame();
$self->{'websocket_state'} = new opentrafficshaper::POE::Filter::HybridHTTP::WebSocketFrame();
}
$self->{'websocket_state'}->append($self->{'buffer'});
# Blank our buffer
......@@ -480,7 +480,7 @@ sub _build_raw_response
# Set our content length
# - This is required even if the content length is 0, for instance when we doing a REDIRECT with no content some browsers hang if
# - there is no content length set.
$response->push_header("Content-Length",length($response->content));
$response->header("Content-Length" => length($response->content));
# Setup our output
my $output = sprintf("%s %s",$self->{'protocol'},$response->status_line);
......
# POE::Filter::HybridHTTP::WebSocketFrame - Copyright 2013-2014, AllworldIT
# POE::Filter::HybridHTTP::WebSocketFrame - Copyright 2007-2023, AllworldIT
# Hybrid HTTP filter support for WebSocketFrames
#
# This program is free software: you can redistribute it and/or modify
......@@ -33,7 +33,7 @@
# the same terms as Perl 5.10.
##
package POE::Filter::HybridHTTP::WebSocketFrame;
package opentrafficshaper::POE::Filter::HybridHTTP::WebSocketFrame;
use bytes;
......@@ -110,7 +110,7 @@ sub next {
return Encode::decode('UTF-8', $bytes);
}
return;
return;
}
sub fin { @_ > 1 ? $_[0]->{fin} = $_[1] : $_[0]->{fin} }
......@@ -189,7 +189,7 @@ sub next_bytes {
$offset += 8;
}
# XXX - not sure how to return this sanely
if ($payload_len > $self->{'max_payload_size'}) {
if ($payload_len > $self->{'max_payload_size'}) {
$self->{'buffer'} = '';
return;
}
......
# OpenTrafficShaper POE::Filter::TCStatistics TC stats filter
# OpenTrafficShaper webserver module: limits page
# Copyright (C) 2007-2013, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -30,26 +30,27 @@
# and from HTTPD filters, they should submit their request as a patch.
##
package POE::Filter::TCStatistics;
package opentrafficshaper::POE::Filter::TCStatistics;
use warnings;
use strict;
use JSON qw(decode_json);
use POE::Filter;
use vars qw($VERSION @ISA);
# NOTE - Should be #.### (three decimal places)
$VERSION = '1.300';
$VERSION = '3.000';
@ISA = qw(POE::Filter);
# Class instantiation
sub new
sub new
{
my $class = shift;
# These are our internal properties
# These are our internal properties
my $self = { };
# Build our class
bless($self, $class);
......@@ -61,10 +62,11 @@ sub new
}
# From the docs:
# get_one_start() accepts an array reference containing unprocessed stream chunks. The chunks are added to the filter's Internal
# buffer for parsing by get_one().
sub get_one_start
sub get_one_start
{
my ($self, $stream) = @_;
......@@ -76,71 +78,75 @@ sub get_one_start
}
# This is called to see if we can grab records/items
sub get_one
sub get_one
{
my $self = shift;
my @results = ();
# Pull of blocks of class info's
while ($self->{'buffer'} =~ s/^(class.+)\n\s+(.+\n\s+.+)\n.+\n.+\n\n//m) {
my $curstat;
my ($classStr,$statsStr) = ($1,$2);
# Strip off the line into an array
my @classArray = split(/\s+/,$classStr);
my @statsArray = split(/[\s,\(\)]+/,$statsStr);
# Pull in all the items
# class htb 1:1 root rate 100000Kbit ceil 100000Kbit burst 51800b cburst 51800b
if (@classArray == 12) {
$curstat->{'CIR'} = _getKNumber($classArray[5]);
$curstat->{'Limit'} = _getKNumber($classArray[7]);
# class htb 1:d parent 1:1 rate 10000Kbit ceil 100000Kbit burst 6620b cburst 51800b
} elsif (@classArray == 13) {
$curstat->{'CIR'} = _getKNumber($classArray[6]);
$curstat->{'Limit'} = _getKNumber($classArray[8]);
# class htb 1:3 parent 1:1 prio 7 rate 10000Kbit ceil 100000Kbit burst 6620b cburst 51800b
} elsif (@classArray == 15) {
$curstat->{'Priority'} = int($classArray[6]);
$curstat->{'CIR'} = _getKNumber($classArray[8]);
$curstat->{'Limit'} = _getKNumber($classArray[10]);
# class htb 1:3 parent 1:1 leaf 3: prio 7 rate 10000Kbit ceil 100000Kbit burst 6620b cburst 51800b
} elsif (@classArray == 17) {
$curstat->{'Priority'} = int($classArray[8]);
$curstat->{'CIR'} = _getKNumber($classArray[10]);
$curstat->{'Limit'} = _getKNumber($classArray[12]);
} else {
next;
}
($curstat->{'TCClassParent'},$curstat->{'TCClassChild'}) = split(/:/,$classArray[2]);
# Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
# rate 0bit 0pps backlog 0b 0p requeues 0
if (@statsArray == 19) {
$curstat->{'TotalBytes'} = int($statsArray[1]);
$curstat->{'TotalPackets'} = int($statsArray[3]);
$curstat->{'TotalDropped'} = int($statsArray[6]);
$curstat->{'TotalOverlimits'} = int($statsArray[8]);
$curstat->{'Rate'} = _getKNumber($statsArray[12]);
$curstat->{'PPS'} = int(substr($statsArray[13],0,-3));
$curstat->{'QueueSize'} = int(substr($statsArray[15],0,-1));
$curstat->{'QueueLen'} = int(substr($statsArray[16],0,-1));
} else {
next;
# If we have a empty buffer we can just return
return [] if ($self->{'buffer'} eq '');
chomp($self->{'buffer'});
# NK: It seems we may not get the terminating ] at the end if the output is still busy?
return [] if (substr($self->{'buffer'},-1) ne "]");
# Try decode JSON payload
my $items;
eval {
$items = decode_json($self->{'buffer'});
$self->{'buffer'} = '';
1;
} or do {
print(STDERR "FAILED TO DECODE JSON: >" . $self->{'buffer'} . "<\n");
return [];
};
# Loop with each item and generate a current stat
for my $item (@{$items}) {
my $curstat = {};
# Skip everything except HTB
if ($item->{'class'} ne "htb") {
continue;
}
push(@results,$curstat);
# Split off the handle into the parent and child
( $curstat->{'TCClassParent'}, $curstat->{'TCClassChild'} ) = split( /:/, $item->{'handle'} );
# The rate and ceil is represented in bytes/s, so it needs to be multiplied by 8 and divided by 1000 to get Kbit/s
$curstat->{'CIR'} = (int($item->{'rate'}) * 8) / 1000;
$curstat->{'Limit'} = (int($item->{'ceil'}) * 8) / 1000;
# If we have a prio, we need to add this too
if (defined($item->{'prio'})) {
$curstat->{'Priority'} = int($item->{'prio'});
}
# We should probably always have these
$curstat->{'TotalBytes'} = int($item->{'stats'}->{'bytes'}) // 0;
$curstat->{'TotalPackets'} = int($item->{'stats'}->{'packets'}) // 0;
$curstat->{'TotalDropped'} = int($item->{'stats'}->{'drops'}) // 0;
$curstat->{'TotalOverLimits'} = int($item->{'stats'}->{'overlimits'}) // 0;
$curstat->{'QueueSize'} = int( $item->{'stats'}->{'backlog'} ) // 0;
$curstat->{'QueueLen'} = int( $item->{'stats'}->{'qlen'} ) // 0;
# These are HTB specific if stats are enabled
$curstat->{'Rate'} = 0;
if (defined($item->{'stats'}->{'rate'})) {
$curstat->{'Rate'} = (int($item->{'stats'}->{'rate'}) * 8) / 1000;
}
$curstat->{'PPS'} = 0;
if (defined($item->{'stats'}->{'pps'})) {
$curstat->{'PPS'} = int($item->{'stats'}->{'pps'});
}
push(@results,$curstat);
}
return [ @results ];
}
# Function to push data to the socket
sub put
sub put
{
my ($self, $data) = @_;
......@@ -166,6 +172,7 @@ sub _reset
}
# Get rate...
sub _getKNumber
{
......@@ -179,9 +186,9 @@ sub _getKNumber
} elsif ($multiplier eq "K") {
# noop
} elsif ($multiplier eq "M") {
$num *= 1000000;
$num *= 1000;
} elsif ($multiplier eq "G") {
$num *= 1000000000;
$num *= 1000000;
}
return int($num);
......
# OpenTrafficShaper constants package
# Copyright (C) 2013-2014, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......
# Logging functionality
# Copyright (C) 2007-2014, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......
# OpenTrafficShaper Plugin Handler
# Copyright (C) 2007-2014, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......
# OpenTrafficShaper configuration manager
# Copyright (C) 2007-2014, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -32,8 +32,7 @@ use Time::HiRes qw(
use awitpt::util qw(
isNumber ISNUMBER_ALLOW_ZERO
isIPv4
isUsername
isUsername ISUSERNAME_ALLOW_ATSIGN
prettyUndef
......@@ -41,6 +40,9 @@ use awitpt::util qw(
);
use opentrafficshaper::constants;
use opentrafficshaper::logger;
use opentrafficshaper::util qw(
isIPv46 isIPv46CIDR
);
......@@ -93,6 +95,7 @@ our (@ISA,@EXPORT,@EXPORT_OK);
setPoolShaperState
unsetPoolShaperState
isPoolIDValid
isPoolOverridden
isPoolReady
getEffectivePool
......@@ -131,10 +134,10 @@ our (@ISA,@EXPORT,@EXPORT_OK);
);
use constant {
VERSION => '0.2.3',
VERSION => '1.0.0',
# After how long does a limit get removed if its's deemed offline
TIMEOUT_EXPIRE_OFFLINE => 300,
TIMEOUT_EXPIRE_OFFLINE => 10,
# How often our config check ticks
TICK_PERIOD => 5,
......@@ -193,6 +196,15 @@ sub CLASS_OVERRIDE_CHANGESET_ATTRIBUTES {
)
}
# Class attributes that can be overidden
sub CLASS_OVERRIDE_PERSISTENT_ATTRIBUTES {
qw(
InterfaceID
TrafficClassID
CIR Limit
)
}
# Mandatory pool member attributes
sub POOLMEMBER_REQUIRED_ATTRIBUTES {
qw(
......@@ -218,6 +230,8 @@ sub POOLMEMBER_PERSISTENT_ATTRIBUTES {
qw(
FriendlyName
Username IPAddress
IPNATAddress
IPNATInbound
MatchPriorityID
PoolID
GroupID
......@@ -357,25 +371,25 @@ our $config = {
# Parameters:
# * ID
# * FriendlyName
# - Used for display purposes
# - Used for display purposes
# * Name
# - Unix timestamp when this entry expires, 0 if never
# - Unix timestamp when this entry expires, 0 if never
# * TrafficClassID
# - Traffic class ID
# - Traffic class ID
# * InterfaceGroupID
# - Interface group this pool is attached to
# - Interface group this pool is attached to
# * TxCIR
# - Traffic limit in kbps
# - Traffic limit in kbps
# * RxCIR
# - Traffic limit in kbps
# - Traffic limit in kbps
# * TxLimit
# - Traffic bursting limit in kbps
# - Traffic bursting limit in kbps
# * RxLimit
# - Traffic bursting limit in kbps
# - Traffic bursting limit in kbps
# * Notes
# - Notes on this limit
# - Notes on this limit
# * Source
# - This is the source of the limit, typically plugin.ModuleName
# - This is the source of the limit, typically plugin.ModuleName
#
# $globals->{'Pools'}
# $globals->{'PoolNameMap'}
......@@ -388,30 +402,30 @@ our $config = {
# Supoprted user attributes:
# * ID
# * PoolID
# - Pool ID
# - Pool ID
# * Username
# - Users username
# - Users username
# * IPAddress
# - Users IP address
# - Users IP address
# * GroupID
# - Group ID
# - Group ID
# * MatchPriorityID
# - Match priority on the backend of this limit
# - Match priority on the backend of this limit
# * TrafficClassID
# - Class ID
# - Class ID
# * Expires
# - Unix timestamp when this entry expires, 0 if never
# - Unix timestamp when this entry expires, 0 if never
# * FriendlyName
# - Used for display purposes instead of username if specified
# - Used for display purposes instead of username if specified
# * Notes
# - Notes on this limit
# - Notes on this limit
# * Status
# - new
# - offline
# - online
# - unknown
# - new
# - offline
# - online
# - unknown
# * Source
# - This is the source of the limit, typically plugin.ModuleName
# - This is the source of the limit, typically plugin.ModuleName
#
# $globals->{'PoolMembers'}
# $globals->{'PoolMemberIDCounter'}
......@@ -423,36 +437,36 @@ our $config = {
#
# Selection criteria:
# * PoolName
# - Pool name
# - Pool name
# * Username
# - Users username
# - Users username
# * IPAddress
# - Users IP address
# - Users IP address
# * GroupID
# - Group ID
# - Group ID
#
# Pool Overrides:
# * TrafficClassID
# - Class ID
# - Class ID
# * TxCIR
# - Traffic limit in kbps
# - Traffic limit in kbps
# * RxCIR
# - Traffic limit in kbps
# - Traffic limit in kbps
# * TxLimit
# - Traffic bursting limit in kbps
# - Traffic bursting limit in kbps
# * RxLimit
# - Traffic bursting limit in kbps
# - Traffic bursting limit in kbps
#
# Parameters:
# * ID
# * FriendlyName
# - Used for display purposes
# - Used for display purposes
# * Expires
# - Unix timestamp when this entry expires, 0 if never
# - Unix timestamp when this entry expires, 0 if never
# * Notes
# - Notes on this limit
# - Notes on this limit
# * Source
# - This is the source of the limit, typically plugin.ModuleName
# - This is the source of the limit, typically plugin.ModuleName
#
# $globals->{'PoolOverrides'}
# $globals->{'PoolOverrideIDCounter'}
......@@ -902,8 +916,6 @@ sub plugin_init
}
}
# TODO - loop and queue init interfaces?
# Check if we have a state file
if (defined(my $statefile = $system->{'file.config'}->{'system'}->{'statefile'})) {
$config->{'statefile'} = $statefile;
......@@ -1381,7 +1393,52 @@ sub _session_tick
# Remove pool member
delete($globals->{'PoolMembers'}->{$pmid});
# FIXME - Recheck IP conflicts and mark at least one pool member as unconflicted
# Check if we have/had conflicts
if ((my @conflicts = keys
%{$globals->{'InterfaceGroups'}->{$pool->{'InterfaceGroupID'}}->{'IPMap'}->{$poolMember->{'IPAddress'}}}) > 0)
{
# We can only re-tag a pool member for adding if we have 1 pool member
if (@conflicts == 1) {
# Grab conflicted pool member, its index 0 in the conflicts array
my $cPoolMember = $globals->{'PoolMembers'}->{$conflicts[0]};
my $cPoolMemberShaperState = getPoolMemberShaperState($cPoolMember->{'ID'});
# We only want to work with conflicts
if ($cPoolMemberShaperState & SHAPER_CONFLICT) {
# Grab pool
my $cPool = $globals->{'Pools'}->{$cPoolMember->{'PoolID'}};
# Unset conflict state
unsetPoolMemberShaperState($cPoolMember->{'ID'},SHAPER_CONFLICT);
# Add to change queue
$globals->{'PoolMemberChangeQueue'}->{$cPoolMember->{'ID'}} = $cPoolMember;
$logger->log(LOG_NOTICE,"[CONFIGMANAGER] IP '%s' is no longer conflicted, removing conflict from ".
"pool '%s' member '%s' [%s], was conflicted with pool '%s' member '%s' [%s]",
$cPoolMember->{'IPAddress'},
$cPool->{'Name'},
$cPoolMember->{'Username'},
$cPoolMember->{'ID'},
$pool->{'Name'},
$poolMember->{'Username'},
$poolMember->{'ID'}
);
}
} else {
# Loop wiht conflicts and build some log items to use
my @logItems;
foreach my $pmid (@conflicts) {
my $cPoolMember = $globals->{'PoolMembers'}->{$pmid};
my $cPool = $globals->{'Pools'}->{$cPoolMember->{'PoolID'}};
push(@logItems,sprintf("Pool:%s/Member:%s",$cPool->{'Name'},$cPoolMember->{'Username'}));
}
$logger->log(LOG_NOTICE,"[CONFIGMANAGER] IP '%s' is still in conflict: %s",
$poolMember->{'IPAddress'},
join(", ",@logItems)
);
}
}
}
} else {
......@@ -2466,6 +2523,8 @@ sub createPool
# Assign pool ID
$pool->{'ID'} = $globals->{'PoolIDCounter'}++;
# NK: Need better pool ID determination, check what ID is available
# Add pool
$globals->{'Pools'}->{$pool->{'ID'}} = $pool;
......@@ -2751,6 +2810,26 @@ sub isPoolReady
# Function to check if pool is being overridden or not
sub isPoolOverridden
{
my $pid = shift;
if (!isPoolIDValid($pid)) {
return;
}
# Set a property based on if this pool is overridden or not
if (defined($globals->{'Pools'}->{$pid}->{'.applied_overrides'}) &&
(keys %{$globals->{'Pools'}->{$pid}->{'.applied_overrides'}}) > 0) {
return 1;
}
return 0;
}
# Function to return a pool with any items changed as per pool overrides
sub getEffectivePool
{
......@@ -2795,7 +2874,7 @@ sub getEffectivePool
# Function to create a pool member
sub createPoolMember
sub createPoolMember
{
my $poolMemberData = shift;
......@@ -2818,12 +2897,21 @@ sub createPoolMember
my $now = time();
# Check if IP address is defined
if (!defined(isIPv4($poolMember->{'IPAddress'} = $poolMemberData->{'IPAddress'}))) {
if (!defined(isIPv46CIDR($poolMember->{'IPAddress'} = $poolMemberData->{'IPAddress'}))) {
$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool member add as the IPAddress is invalid");
return;
}
if (defined($poolMemberData->{'IPNATAddress'}) && $poolMemberData->{'IPNATAddress'} ne "") {
if (!defined(isIPv46($poolMember->{'IPNATAddress'} = $poolMemberData->{'IPNATAddress'}))) {
$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool member add as the IPNATAddress is invalid");
return;
} elsif (defined($poolMemberData->{'IPNATInbound'}) && $poolMemberData->{'IPNATInbound'} eq "yes") {
$poolMember->{'IPNATInbound'} = "yes";
}
}
# Now check if Username its valid
if (!defined(isUsername($poolMember->{'Username'} = $poolMemberData->{'Username'}))) {
if (!defined(isUsername($poolMember->{'Username'} = $poolMemberData->{'Username'}, ISUSERNAME_ALLOW_ATSIGN))) {
$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool member add as Username is invalid");
return;
}
......@@ -2906,15 +2994,15 @@ sub createPoolMember
join(", ",@logItems)
);
# We don't have to add it to the queue, as its in a conflicted state
setPoolMemberShaperState($poolMember->{'ID'},SHAPER_CONFLICT);
# We don't have to add it to the queue
} else {
# Pool member needs updating
$globals->{'PoolMemberChangeQueue'}->{$poolMember->{'ID'}} = $poolMember;
}
# Link interface IP address map, we must do the check above FIRST, and that neds the pool to be added to the pool map
# Link interface IP address map, we must do the check above FIRST, as that needs the pool to be added to the pool map
$globals->{'InterfaceGroups'}->{$pool->{'InterfaceGroupID'}}->{'IPMap'}->{$poolMember->{'IPAddress'}}
->{$poolMember->{'ID'}} = $poolMember;
......@@ -3292,14 +3380,23 @@ sub createLimit
}
# Check if IP address is defined
if (!defined(isIPv4($limitData->{'IPAddress'} = $limitData->{'IPAddress'}))) {
$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit add as the IP address is invalid");
if (!defined(isIPv46CIDR($limitData->{'IPAddress'} = $limitData->{'IPAddress'}))) {
$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit add as the IPAddress is invalid");
return;
}
if (defined($limitData->{'IPNATAddress'}) && $limitData->{'IPNATAddress'} ne "") {
if (!defined(isIPv46($limitData->{'IPNATAddress'} = $limitData->{'IPNATAddress'}))) {
$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process limit add as the IPNATAddress is invalid");
return;
}
}
if (defined($limitData->{'IPNATInbound'}) && $limitData->{'IPNATInbound'} eq "yes") {
$limitData->{'IPNATInbound'} = "yes";
}
my $poolName = $limitData->{'Username'};
my $poolData = {
'FriendlyName' => $limitData->{'IPAddress'},
'FriendlyName' => $limitData->{'FriendlyName'} || $limitData->{'IPAddress'},
'Name' => $poolName,
'InterfaceGroupID' => $limitData->{'InterfaceGroupID'},
'TrafficClassID' => $limitData->{'TrafficClassID'},
......@@ -3350,7 +3447,10 @@ sub createPoolOverride
# Check that we have at least one match attribute
my $isValid = 0;
foreach my $item (POOL_OVERRIDE_MATCH_ATTRIBUTES) {
$isValid++;
# Bump up $isValid if we have a match attribute
if (defined($poolOverrideData->{$item})) {
$isValid++;
}
}
if (!$isValid) {
$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot process pool override as there is no selection attribute");
......@@ -3569,14 +3669,15 @@ sub _resolve_pool_override
# Loop with the attributes and check for a full match
foreach my $attr (POOL_OVERRIDE_MATCH_ATTRIBUTES) {
# If this attribute in the pool override is set, then lets check it
if (defined($poolOverride->{$attr}) && $poolOverride->{$attr} ne "") {
# Check for match or mismatch
if (defined($candidate->{$attr}) && $candidate->{$attr} eq $poolOverride->{$attr}) {
$numMatches++;
} else {
$numMismatches++;
# Check for match or mismatch, only if candidate attribute is defined
if (defined($candidate->{$attr})) {
if ($candidate->{$attr} eq $poolOverride->{$attr}) {
$numMatches++;
} else {
$numMismatches++;
}
}
}
}
......@@ -3614,6 +3715,7 @@ sub _resolve_pool_override
$pool->{'ID'}
);
# We didn't match, but we may of matched before?
} else {
# There was a pool override before, so something changed now that there is none
......@@ -3706,6 +3808,26 @@ sub _load_statefile
# Grab the object handle
my $state = tied( %stateHash );
# Loop with interface traffic class overrides
foreach my $section ($state->GroupMembers('interface_traffic_class.override')) {
my $classOverride = $stateHash{$section};
# Loop with the persistent attributes and create our hash
my $cClassOverride;
foreach my $attr (CLASS_OVERRIDE_PERSISTENT_ATTRIBUTES) {
if (defined($classOverride->{$attr})) {
# If its an array, join all the items
if (ref($classOverride->{$attr}) eq "ARRAY") {
$classOverride->{$attr} = join("\n",@{$classOverride->{$attr}});
}
$cClassOverride->{$attr} = $classOverride->{$attr};
}
}
# XXX - Hack, Proces this class override
changeInterfaceTrafficClass($cClassOverride);
}
# Loop with user pool overrides
foreach my $section ($state->GroupMembers('pool.override')) {
my $poolOverride = $stateHash{$section};
......@@ -3820,6 +3942,34 @@ sub _write_statefile
# Create new state file object
my $state = new Config::IniFiles();
# XXX - Hack, loop with class overrides
while ((my $itcid, my $interfaceTrafficClass) = each(%{$globals->{'InterfaceTrafficClasses'}})) {
# Skip over non-overridden classes
if (!defined($interfaceTrafficClass->{'.applied_overrides'})) {
next;
}
# Create a section name
my $section = "interface_traffic_class.override " . $itcid;
# Add a section for this class override
$state->AddSection($section);
# XXX - Hack, Attributes we want to save for this traffic class override
foreach my $attr (CLASS_OVERRIDE_PERSISTENT_ATTRIBUTES) {
# Set items up
if (defined(my $value = $interfaceTrafficClass->{$attr})) {
$state->newval($section,$attr,$value);
}
}
# XXX - Hack, loop with the override
foreach my $attr (CLASS_OVERRIDE_PERSISTENT_ATTRIBUTES) {
# Set items up
if (defined(my $value = $interfaceTrafficClass->{'.applied_overrides'}->{'change'}->{$attr})) {
$state->newval($section,$attr,$value);
}
}
}
# Loop with pool overrides
while ((my $poid, my $poolOverride) = each(%{$globals->{'PoolOverrides'}})) {
# Create a section name
......@@ -3834,7 +3984,6 @@ sub _write_statefile
$state->newval($section,$attr,$value);
}
}
}
# Loop with pools
......
# OpenTrafficShaper radius module
# Copyright (C) 2007-2014, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -61,7 +61,7 @@ our (@ISA,@EXPORT,@EXPORT_OK);
);
use constant {
VERSION => '0.2.1',
VERSION => '1.0.0',
DATAGRAM_MAXLEN => 8192,
......@@ -125,8 +125,8 @@ sub plugin_init
foreach my $dict (@dicts) {
$dict =~ s/\s+//g;
# Skip comments
next if ($dict =~ /^#/);
# Skip comments
next if ($dict =~ /^#/);
# Check if we have a path, if we do use it
if (defined($system->{'file.config'}->{'plugin.radius'}->{'dictionary_path'})) {
$dict = $system->{'file.config'}->{'plugin.radius'}->{'dictionary_path'} . "/$dict";
......@@ -362,39 +362,70 @@ sub _session_socket_read
if (my $attrRawVal = $pkt->vsattr(IANA_PEN,'OpenTrafficShaper-Traffic-Limit')) {
$trafficLimit = @{ $attrRawVal }[0];
}
# Grab rate limits from the string we got
# We assume below that we will have limits
if (!defined($trafficLimit)) {
$logger->log(LOG_NOTICE,"[RADIUS] No traffic limit set for user '%s', ignoring",$username);
return;
}
# Grab rate limits below from the string we got
my $rxCIR; my $txCIR;
my $rxLimit; my $txLimit;
if (defined($trafficLimit)) {
# Match rx-rate[/tx-rate] rx-burst-rate[/tx-burst-rate]
if ($trafficLimit =~ /^(\d+)([km])(?:\/(\d+)([km]))?(?: (\d+)([km])(?:\/(\d+)([km]))?)?/) {
$rxCIR = getKbit($1,$2);
$txCIR = getKbit($3,$4);
$rxLimit = getKbit($5,$6);
$txLimit = getKbit($7,$8);
} else {
$logger->log(LOG_DEBUG,"[RADIUS] The 'OpenTrafficShaper-Traffic-Limit' attribute appears to be invalid for user '%s'".
": '%s'",
$username,
$trafficLimit
);
return;
# Match rx-rate[/tx-rate] rx-burst-rate[/tx-burst-rate]
if ($trafficLimit =~ /^(\d+)([km])(?:\/(\d+)([km]))?(?: (\d+)([km])(?:\/(\d+)([km]))?)?/) {
$rxCIR = getKbit($1,$2);
$txCIR = getKbit($3,$4);
$rxLimit = getKbit($5,$6);
$txLimit = getKbit($7,$8);
# Set our limits if they not defined
if (!defined($rxLimit)) {
$rxLimit = $rxCIR;
$rxCIR = $rxCIR / 4;
}
if (!defined($txLimit)) {
$txLimit = $txCIR;
$txCIR = $txCIR / 4;
}
} else {
$logger->log(LOG_WARN,"[RADIUS] The 'OpenTrafficShaper-Traffic-Limit' attribute appears to be invalid for user '%s'".
": '%s'",
$username,
$trafficLimit
);
return;
}
# Check if we have a pool transform
my $tPoolName;
my $poolName;
if (defined($config->{'username_to_pool_transform'})) {
# Check if transform matches, if it does set pool name
if ($username =~ $config->{'username_to_pool_transform'}) {
my $tPoolName = $1;
$poolName = $1;
}
}
# Check if the pool name is being overridden
if (my $attrRawVal = $pkt->vsattr(IANA_PEN,'OpenTrafficShaper-Traffic-Pool')) {
$poolName = @{ $attrRawVal }[0];
}
# If we got a pool name, check if it exists
if (defined($poolName)) {
if (!defined(getPoolByName($config->{'interface_group'},$poolName))) {
$logger->log(LOG_NOTICE,"[RADIUS] Pool '%s' not found, using username '%s' instead",
$poolName,
$username
);
$poolName = $username;
}
# If we didn't get the pool name, just use the username
} else {
$poolName = $username;
}
# Check what to use for the pool name, by default its the username
my $poolName = $tPoolName || $username;
# Try grab the pool
my $pool = getPoolByName($poolName);
my $pool = getPoolByName($config->{'interface_group'},$poolName);
my $pid = defined($pool) ? $pool->{'ID'} : undef;
my $ipAddress = $pkt->attr('Framed-IP-Address');
......@@ -428,11 +459,14 @@ sub _session_socket_read
# Change the details
my $changes = changePool({
'ID' => $pid,
'FriendlyName' => $ipAddress,
'TrafficClassID' => $trafficClassID,
'TxCIR' => $txCIR,
'RxCIR' => $rxCIR,
# These MUST be defined
'TxLimit' => $txLimit,
'RxLimit' => $rxLimit,
'Expires' => $now + DEFAULT_EXPIRY_PERIOD
});
......@@ -515,6 +549,12 @@ sub _session_socket_read
);
}
# TODO: Add output of updated items here too?
changePool({
'ID' => $pid,
'FriendlyName' => $ipAddress
});
# If not display message
} else {
......@@ -537,6 +577,12 @@ sub _session_socket_read
'Expires' => $now + $config->{'expiry_period'},
'Source' => "plugin.radius",
});
# TODO: Add output of updated items here too?
changePool({
'ID' => $pid,
'FriendlyName' => $ipAddress
});
}
# Radius user going offline
......@@ -552,14 +598,20 @@ sub _session_socket_read
# If there is only 1 pool member, then lets expire the pool in the removal expiry period
if (@poolMembers == 1) {
$logger->log(LOG_INFO,"[RADIUS] Expiring pool '$poolName'");
changePool($pool->{'ID'},{ 'Expires' => $now + REMOVE_EXPIRY_PERIOD });
changePool({
'ID' => $pool->{'ID'},
'Expires' => $now + REMOVE_EXPIRY_PERIOD
});
}
}
# Check if we have a pool member with this username and IP
if (my $pmid = getPoolMemberByUsernameIP($pool->{'ID'},$username,$ipAddress)) {
$logger->log(LOG_INFO,"[RADIUS] Expiring pool '$poolName' member '$username'");
changePoolMember($pmid,{ 'Expires' => $now + REMOVE_EXPIRY_PERIOD });
changePoolMember({
'ID' => $pmid,
'Expires' => $now + REMOVE_EXPIRY_PERIOD
});
}
$logger->log(LOG_INFO,"[RADIUS] Pool '$poolName' member '$username' set to expire as they're offline");
......@@ -602,10 +654,10 @@ sub getKbit
my ($counter,$quantifier) = @_;
# If there is no counter
return undef if (!defined($counter));
return if (!defined($counter));
# We need a quantifier
return undef if (!defined($quantifier));
return if (!defined($quantifier));
# Initialize counter
my $newCounter = $counter;
......@@ -615,7 +667,7 @@ sub getKbit
} elsif ($quantifier =~ /^k$/i) {
$newCounter = $counter * 1;
} else {
return undef;
return;
}
return $newCounter;
......
# OpenTrafficShaper Traffic shaping statistics
# Copyright (C) 2007-2014, AllWorldIT
# Copyright (C) 2007-2015, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -21,10 +21,11 @@ package opentrafficshaper::plugins::statistics;
use strict;
use warnings;
use DBI;
use Data::Dumper;
use POE;
use Storable qw( dclone );
use awitpt::db::dblayer;
use opentrafficshaper::constants;
use opentrafficshaper::logger;
......@@ -67,16 +68,100 @@ our (@ISA,@EXPORT,@EXPORT_OK);
use constant {
VERSION => '0.2.2',
# How often our config check ticks
TICK_PERIOD => 5,
TICK_PERIOD => 2,
STATISTICS_PERIOD => 60,
STATISTICS_PERIOD => 5,
STATISTICS_DIR_TX => 1,
STATISTICS_DIR_RX => 2,
STATISTICS_MAXFLUSH_PER_PERIOD => 10000,
# SQL Statements
SQL_ADD_IDENTIFIER => 'INSERT INTO identifiers (`Identifier`) VALUES (?)',
SQL_GET_IDENTIFIER => 'SELECT ID FROM identifiers WHERE `Identifier` = ?',
SQL_CONSOLIDATE_STATS => '
SELECT
`IdentifierID`, `Timestamp` - (`Timestamp` % ?) AS Timestamp,
`Direction`,
MAX(`CIR`) AS `CIR`, MAX(`Limit`) AS `Limit`, MAX(`Rate`) AS `Rate`, MAX(`PPS`) AS `PPS`,
MAX(`QueueLen`) AS `QueueLen`, MAX(`TotalBytes`) AS `TotalBytes`, MAX(`TotalPackets`) AS `TotalPackets`,
MAX(`TotalOverLimits`) AS `TotalOverLimits`, MAX(`TotalDropped`) AS `TotalDropped`
FROM
stats
WHERE
`Key` = ?
AND `Timestamp` > ?
AND `Timestamp` < ?
GROUP BY
`IdentifierID`, `Timestamp`, `Direction`
',
SQL_CONSOLIDATE_STATS_BASIC => '
SELECT
`IdentifierID`, `Timestamp` - (`Timestamp` % ?) AS Timestamp,
MAX(`Counter`) AS `Counter`
FROM
stats_basic
WHERE
`Key` = ?
AND `Timestamp` > ?
AND `Timestamp` < ?
GROUP BY
`IdentifierID`, `Timestamp`
',
SQL_GET_STATS => '
SELECT
`Timestamp`, `Direction`, `Rate`, `PPS`, `CIR`, `Limit`
FROM
stats
WHERE
`IdentifierID` = ?
AND `Key` = ?
AND `Timestamp` > ?
AND `Timestamp` < ?
',
SQL_GET_STATS_BASIC => '
SELECT
`Timestamp`, `Counter`
FROM
stats_basic
WHERE
`IdentifierID` = ?
AND `Key` = ?
AND `Timestamp` > ?
AND `Timestamp` < ?
',
SQL_CLEANUP_STATS => 'DELETE FROM stats WHERE `Key` = ? AND `Timestamp` < ?',
SQL_CLEANUP_STATS_BASIC => 'DELETE FROM stats_basic WHERE `Key` = ? AND `Timestamp` < ?'
};
sub STATS_CONFIG
{
{
1 => {
'precision' => 300, # 5min
'retention' => 4, # 4 days
},
2 => {
'precision' => 900, # 15min
'retention' => 14, # 14 days
},
3 => {
'precision' => 3600, # 1hr
'retention' => 28 * 2, # 2 months
},
4 => {
'precision' => 21600, # 6hr
'retention' => 28 * 6, # 6 months
},
5 => {
'precision' => 86400, # 24hr
'retention' => 28 * 12 * 2, # 2 years
}
}
}
# Plugin info
our $pluginInfo = {
......@@ -92,42 +177,13 @@ our $pluginInfo = {
my $globals;
my $logger;
# Our configuration
my $config = {
'db_dsn' => undef,
'db_username' => "",
'db_password' => "",
};
# Stats configuration
my $statsConfig = {
1 => {
'precision' => 300, # 5min
'retention' => 4, # 4 days
},
2 => {
'precision' => 900, # 15min
'retention' => 14, # 14 days
},
3 => {
'precision' => 3600, # 1hr
'retention' => 28 * 2, # 2 months
},
4 => {
'precision' => 21600, # 6hr
'retention' => 28 * 6, # 6 months
},
5 => {
'precision' => 86400, # 24hr
'retention' => 28 * 12 * 2, # 2 years
}
};
# Handle of DBI
#
# $globals->{'DBHandle'}
# $globals->{'DBPreparedStatements'}
# $globals->{'Database'}->{'Handle'}
# $globals->{'Database'}->{'DSN'}
# $globals->{'Database'}->{'Username'}
# $globals->{'Database'}->{'Password'}
# DB identifier map
#
......@@ -159,8 +215,7 @@ sub plugin_init
$logger->log(LOG_NOTICE,"[STATISTICS] OpenTrafficShaper Statistics v%s - Copyright (c) 2007-2014, AllWorldIT",VERSION);
# Initialize
$globals->{'DBHandle'} = undef;
$globals->{'DBPreparedStatements'} = { };
$globals->{'Database'} = undef;
$globals->{'IdentifierMap'} = { };
......@@ -176,18 +231,19 @@ sub plugin_init
# Check our interfaces
if (defined(my $dbdsn = $globals->{'file.config'}->{'plugin.statistics'}->{'db_dsn'})) {
$logger->log(LOG_INFO,"[STATISTICS] Set db_dsn to '%s'",$dbdsn);
$config->{'db_dsn'} = $dbdsn;
$logger->log(LOG_INFO,"[STATISTICS] Set database DSN to '%s'",$dbdsn);
$globals->{'Database'}->{'DSN'} = $dbdsn;
if (defined(my $dbuser = $globals->{'file.config'}->{'plugin.statistics'}->{'db_username'})) {
$logger->log(LOG_INFO,"[STATISTICS] Set database username to '%s'",$dbuser);
$globals->{'Database'}->{'Username'} = $dbuser;
}
if (defined(my $dbpass = $globals->{'file.config'}->{'plugin.statistics'}->{'db_password'})) {
$logger->log(LOG_INFO,"[STATISTICS] Set database password to '%s'",$dbpass);
$globals->{'Database'}->{'Password'} = $dbpass;
}
} else {
$logger->log(LOG_WARN,"[STATISTICS] No db_dsn to specified in configuration file. Stats storage disabled!");
}
if (defined(my $dbuser = $globals->{'file.config'}->{'plugin.statistics'}->{'db_username'})) {
$logger->log(LOG_INFO,"[STATISTICS] Set db_username to '%s'",$dbuser);
$config->{'db_username'} = $dbuser;
}
if (defined(my $dbpass = $globals->{'file.config'}->{'plugin.statistics'}->{'db_password'})) {
$logger->log(LOG_INFO,"[STATISTICS] Set db_password to '%s'",$dbpass);
$config->{'db_password'} = $dbpass;
$logger->log(LOG_WARN,"[STATISTICS] No database DSN to specified in configuration file. Stats storage disabled!");
}
# This is our main stats session
......@@ -203,100 +259,30 @@ sub plugin_init
);
# Create DBI agent
if (defined($config->{'db_dsn'})) {
$globals->{'DBHandle'} = DBI->connect(
$config->{'db_dsn'}, $config->{'db_username'}, $config->{'db_password'},
{
'AutoCommit' => 1,
'RaiseError' => 0,
'FetchHashKeyName' => 'NAME_lc'
}
);
if (!defined($globals->{'DBHandle'})) {
$logger->log(LOG_ERR,"[STATISTICS] Failed to connect to database: %s",$DBI::errstr);
}
# Prepare identifier add statement
if ($globals->{'DBHandle'} && (my $res = $globals->{'DBHandle'}->prepare('INSERT INTO identifiers (`Identifier`) VALUES (?)'))) {
$globals->{'DBPreparedStatements'}->{'identifier_add'} = $res;
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to prepare statement 'identifier_add': %s",$DBI::errstr);
$globals->{'DBHandle'}->disconnect();
$globals->{'DBHandle'} = undef;
}
# Prepare identifier get statement
if ($globals->{'DBHandle'} && (my $res = $globals->{'DBHandle'}->prepare('SELECT ID FROM identifiers WHERE `Identifier` = ?'))) {
$globals->{'DBPreparedStatements'}->{'identifier_get'} = $res;
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to prepare statement 'identifier_get': %s",$DBI::errstr);
$globals->{'DBHandle'}->disconnect();
$globals->{'DBHandle'} = undef;
}
# Prepare stats consolidation statements
if ($globals->{'DBHandle'} && (my $res = $globals->{'DBHandle'}->prepare('
SELECT
`IdentifierID`, `Timestamp` - (`Timestamp` % ?) AS TimestampM,
`Direction`,
MAX(`CIR`) AS `CIR`, MAX(`Limit`) AS `Limit`, MAX(`Rate`) AS `Rate`, MAX(`PPS`) AS `PPS`,
MAX(`Queue_Len`) AS `Queue_Len`, MAX(`Total_Bytes`) AS `Total_Bytes`, MAX(`Total_Packets`) AS `Total_Packets`,
MAX(`Total_Overlimits`) AS `Total_Overlimits`, MAX(`Total_Dropped`) AS `Total_Dropped`
FROM
stats
WHERE
`Key` = ?
AND `Timestamp` > ?
AND `Timestamp` < ?
GROUP BY
`IdentifierID`, `TimestampM`, `Direction`
'))) {
$globals->{'DBPreparedStatements'}->{'stats_consolidate'} = $res;
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to prepare statement 'stats_consolidate': %s",$DBI::errstr);
$globals->{'DBHandle'}->disconnect();
$globals->{'DBHandle'} = undef;
}
if ($globals->{'DBHandle'} && (my $res = $globals->{'DBHandle'}->prepare('
SELECT
`IdentifierID`, `Timestamp` - (`Timestamp` % ?) AS TimestampM,
MAX(`Counter`) AS `Counter`
FROM
stats_basic
WHERE
`Key` = ?
AND `Timestamp` > ?
AND `Timestamp` < ?
GROUP BY
`IdentifierID`, `TimestampM`
'))) {
$globals->{'DBPreparedStatements'}->{'stats_basic_consolidate'} = $res;
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to prepare statement 'stats_basic_consolidate': %s",$DBI::errstr);
$globals->{'DBHandle'}->disconnect();
$globals->{'DBHandle'} = undef;
}
# Prepare stats cleanup statements
if ($globals->{'DBHandle'} && (my $res = $globals->{'DBHandle'}->prepare('DELETE FROM stats WHERE `Key` = ? AND `Timestamp` < ?'))) {
$globals->{'DBPreparedStatements'}->{'stats_cleanup'} = $res;
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to prepare statement 'stats_cleanup': %s",$DBI::errstr);
$globals->{'DBHandle'}->disconnect();
$globals->{'DBHandle'} = undef;
}
if ($globals->{'DBHandle'} && (my $res = $globals->{'DBHandle'}->prepare('DELETE FROM stats_basic WHERE `Key` = ? AND `Timestamp` < ?'))) {
$globals->{'DBPreparedStatements'}->{'stats_basic_cleanup'} = $res;
if (defined($globals->{'Database'})) {
$globals->{'Database'}->{'Handle'} = DBInit($globals->{'Database'});
# Check if handle is defined
if (defined($globals->{'Database'}->{'Handle'})) {
# Try connect (0 is success)
if (!DBConnect()) {
$logger->log(LOG_INFO,"[STATISTICS] Connected to database");
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to connect to database: %s (DATABASE DISABLED)",
awitpt::db::dblayer::Error());
# Don't try again
delete($globals->{'Database'});
}
# If the handle is not defined, the database won't work
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to prepare statement 'stats_basic_cleanup': %s",$DBI::errstr);
$globals->{'DBHandle'}->disconnect();
$globals->{'DBHandle'} = undef;
$logger->log(LOG_ERR,"[STATISTICS] Failed to initailize database: %s (DATABASE DISABLED)",
awitpt::db::dblayer::Error());
}
# Set last cleanup to now
my $now = time();
foreach my $key (keys %{$statsConfig}) {
foreach my $key (keys %{STATS_CONFIG()}) {
# Get aligned time so we cleanup sooner
$globals->{'LastCleanup'}->{$key} = _getAlignedTime($now,$statsConfig->{$key}->{'precision'});
$globals->{'LastCleanup'}->{$key} = _getAlignedTime($now,STATS_CONFIG()->{$key}->{'precision'});
}
$globals->{'LastConfigManagerStats'} = $now;
}
......@@ -357,20 +343,14 @@ sub _session_tick
my ($kernel,$heap) = @_[KERNEL,HEAP];
# If we don't have a DB handle, just skip...
if (!$globals->{'DBHandle'}) {
# If we don't have a database, just skip...
if (!$globals->{'Database'}) {
return;
}
my $now = time();
my $timer1 = [gettimeofday];
# Pull in statements
my $sthStatsConsolidate = $globals->{'DBPreparedStatements'}->{'stats_consolidate'};
my $sthStatsCleanup = $globals->{'DBPreparedStatements'}->{'stats_cleanup'};
my $sthStatsBasicConsolidate = $globals->{'DBPreparedStatements'}->{'stats_basic_consolidate'};
my $sthStatsBasicCleanup = $globals->{'DBPreparedStatements'}->{'stats_basic_cleanup'};
# Even out flushing over 10s to absorb spikes
my $totalFlush = @{$globals->{'StatsQueue'}};
my $maxFlush = int($totalFlush / 10) + 100;
......@@ -399,7 +379,7 @@ sub _session_tick
$stat->{'IdentifierID'}, $stat->{'Key'}, $stat->{'Timestamp'},
$stat->{'Direction'},
$stat->{'CIR'}, $stat->{'Limit'}, $stat->{'Rate'}, $stat->{'PPS'}, $stat->{'QueueLen'},
$stat->{'TotalBytes'}, $stat->{'TotalPackets'}, $stat->{'TotalOverlimits'}, $stat->{'TotalDropped'}
$stat->{'TotalBytes'}, $stat->{'TotalPackets'}, $stat->{'TotalOverLimits'}, $stat->{'TotalDropped'}
);
}
......@@ -408,36 +388,36 @@ sub _session_tick
# If we got things to insert, do it
if (@insertBasicHolders > 0) {
my $res = $globals->{'DBHandle'}->do('
INSERT DELAYED INTO stats_basic
my $res = DBDo('
INSERT INTO stats_basic
(
`IdentifierID`, `Key`, `Timestamp`,
`Counter`
)
VALUES
'.join(',',@insertBasicHolders),undef,@insertBasicData
'.join(',',@insertBasicHolders),@insertBasicData
);
# Check for error
if (!defined($res)) {
$logger->log(LOG_ERR,"[STATISTICS] Failed to execute delayed stats_basic insert: %s",$DBI::errstr);
$logger->log(LOG_ERR,"[STATISTICS] Failed to execute stats_basic insert: %s",awitpt::db::dblayer::Error());
}
}
# And normal stats...
if (@insertHolders > 0) {
my $res = $globals->{'DBHandle'}->do('
INSERT DELAYED INTO stats
my $res = DBDo('
INSERT INTO stats
(
`IdentifierID`, `Key`, `Timestamp`,
`Direction`,
`CIR`, `Limit`, `Rate`, `PPS`, `Queue_Len`,
`Total_Bytes`, `Total_Packets`, `Total_Overlimits`, `Total_Dropped`
`CIR`, `Limit`, `Rate`, `PPS`, `QueueLen`,
`TotalBytes`, `TotalPackets`, `TotalOverLimits`, `TotalDropped`
)
VALUES
'.join(',',@insertHolders),undef,@insertData
'.join(',',@insertHolders),@insertData
);
# Check for error
if (!defined($res)) {
$logger->log(LOG_ERR,"[STATISTICS] Failed to execute delayed stats insert: %s",$DBI::errstr);
$logger->log(LOG_ERR,"[STATISTICS] Failed to execute stats insert: %s",awitpt::db::dblayer::Error());
}
}
......@@ -446,19 +426,19 @@ sub _session_tick
if ($numFlush) {
my $timediff2 = tv_interval($timer1,$timer2);
$logger->log(LOG_INFO,"[STATISTICS] Total stats flush time %s/%s records: %s",
$numFlush,
$totalFlush,
sprintf('%.3fs',$timediff2)
$numFlush,
$totalFlush,
sprintf('%.3fs',$timediff2)
);
}
my $res;
# Loop with our stats consolidation configuration
foreach my $key (sort keys %{$statsConfig}) {
foreach my $key (sort keys %{STATS_CONFIG()}) {
my $timerA = [gettimeofday];
my $precision = $statsConfig->{$key}->{'precision'};
my $precision = STATS_CONFIG()->{$key}->{'precision'};
my $thisPeriod = _getAlignedTime($now,$precision);
my $lastPeriod = $thisPeriod - $precision;
my $prevKey = $key - 1;
......@@ -475,60 +455,44 @@ sub _session_tick
my $consolidateUpTo = $lastPeriod - $precision;
# Execute and pull in consolidated stats
$res = $sthStatsBasicConsolidate->execute($precision,$prevKey,$consolidateFrom,$consolidateUpTo);
$res = DBSelect(SQL_CONSOLIDATE_STATS_BASIC,$precision,$prevKey,$consolidateFrom,$consolidateUpTo);
if ($res) {
# Loop with items returned
while (my $item = $sthStatsBasicConsolidate->fetchrow_hashref()) {
my $stat = {
'IdentifierID' => $item->{'identifierid'},
'Key' => $key,
'Timestamp' => $item->{'timestampm'},
'Counter' => $item->{'counter'}
};
while (my $item = hashifyLCtoMC($res->fetchrow_hashref(),'IdentifierID','Timestamp','Counter')) {
$item->{'Key'} = $key;
# Queue for insert
push(@{$globals->{'StatsQueue'}},$stat);
push(@{$globals->{'StatsQueue'}},$item);
$numStatsBasicConsolidated++;
}
DBFreeRes($res);
# If there was an error, make sure we report it
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to execute stats_basic consolidation statement: %s",
$sthStatsBasicConsolidate->errstr()
);
awitpt::db::dblayer::Error());
}
# And the normal stats...
$res = $sthStatsConsolidate->execute($precision,$prevKey,$consolidateFrom,$consolidateUpTo);
$res = DBSelect(SQL_CONSOLIDATE_STATS,$precision,$prevKey,$consolidateFrom,$consolidateUpTo);
if ($res) {
# Loop with items returned
while (my $item = $sthStatsConsolidate->fetchrow_hashref()) {
my $stat = {
'IdentifierID' => $item->{'identifierid'},
'Key' => $key,
'Timestamp' => $item->{'timestampm'},
'Direction' => $item->{'direction'},
'CIR' => $item->{'cir'},
'Limit' => $item->{'limit'},
'Rate' => $item->{'rate'},
'PPS' => $item->{'pps'},
'QueueLen' => $item->{'queue_len'},
'TotalBytes' => $item->{'total_bytes'},
'TotalPackets' => $item->{'total_packets'},
'TotalOverLimits' => $item->{'total_overlimits'},
'TotalDropped' => $item->{'total_dropped'}
};
while (my $item = hashifyLCtoMC(
$res->fetchrow_hashref(),
'IdentifierID','Timestamp','Direction','CIR','Limit','Rate','PPS','QueueLen','TotalBytes','TotalPackets',
'TotalOverLimits','TotalDropped'
)) {
$item->{'Key'} = $key;
# Queue for insert
push(@{$globals->{'StatsQueue'}},$stat);
push(@{$globals->{'StatsQueue'}},$item);
$numStatsConsolidated++;
}
DBFreeRes($res);
# If there was an error, make sure we report it
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to execute stats consolidation statement: %s",
$sthStatsConsolidate->errstr()
);
awitpt::db::dblayer::Error());
}
# Set last cleanup to now
......@@ -538,14 +502,14 @@ sub _session_tick
my $timediffB = tv_interval($timerA,$timerB);
$logger->log(LOG_INFO,"[STATISTICS] Stats consolidation: key %s in %s (%s basic, %s normal), period %s - %s [%s - %s]",
$key,
sprintf('%.3fs',$timediffB),
$numStatsBasicConsolidated,
$numStatsConsolidated,
$consolidateFrom,
$consolidateUpTo,
scalar(localtime($consolidateFrom)),
scalar(localtime($consolidateUpTo))
$key,
sprintf('%.3fs',$timediffB),
$numStatsBasicConsolidated,
$numStatsConsolidated,
$consolidateFrom,
$consolidateUpTo,
scalar(localtime($consolidateFrom)),
scalar(localtime($consolidateUpTo))
);
}
......@@ -555,98 +519,97 @@ sub _session_tick
# We only need to run as often as the first precision
# - If cleanup has not yet run?
# - or if the 0 cleanup plus precision of the first key is in the past (data is now stale?)
if (!defined($globals->{'LastCleanup'}->{'0'}) || $globals->{'LastCleanup'}->{'0'} + $statsConfig->{1}->{'precision'} < $now) {
if (!defined($globals->{'LastCleanup'}->{'0'}) || $globals->{'LastCleanup'}->{'0'} + STATS_CONFIG()->{1}->{'precision'} < $now) {
# We're going to clean up for the first stats precision * 3, which should be enough
my $cleanUpTo = $now - ($statsConfig->{1}->{'precision'} * 3);
my $cleanUpTo = $now - (STATS_CONFIG()->{1}->{'precision'} * 3);
# Streamed stats is removed 3 time periods past the first precision
my $timerA = [gettimeofday];
if ($res = $sthStatsBasicCleanup->execute(0, $cleanUpTo)) {
if ($res = DBDo(SQL_CLEANUP_STATS_BASIC,0,$cleanUpTo)) {
my $timerB = [gettimeofday];
my $timerdiffA = tv_interval($timerA,$timerB);
# We get 0E0 for 0 when none were removed
if ($res ne "0E0") {
$logger->log(LOG_INFO,"[STATISTICS] Cleanup streamed stats_basic, %s items in %s, up to %s [%s]",
$res,
sprintf('%.3fs',$timerdiffA),
$cleanUpTo,
scalar(localtime($cleanUpTo)),
$res,
sprintf('%.3fs',$timerdiffA),
$cleanUpTo,
scalar(localtime($cleanUpTo)),
);
}
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to execute stats_basic cleanup statement: %s",
$sthStatsBasicCleanup->errstr()
);
awitpt::db::dblayer::Error());
}
# And the normal stats...
$timerA = [gettimeofday];
if ($res = $sthStatsCleanup->execute(0, $cleanUpTo)) {
if ($res = DBDo(SQL_CLEANUP_STATS,0,$cleanUpTo)) {
my $timerB = [gettimeofday];
my $timerdiffA = tv_interval($timerA,$timerB);
# We get 0E0 for 0 when none were removed
if ($res ne "0E0") {
$logger->log(LOG_INFO,"[STATISTICS] Cleanup streamed stats, %s items in %s, up to %s [%s]",
$res,
sprintf('%.3fs',$timerdiffA),
$cleanUpTo,scalar(localtime($cleanUpTo))
$res,
sprintf('%.3fs',$timerdiffA),
$cleanUpTo,scalar(localtime($cleanUpTo))
);
}
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to execute stats cleanup statement: %s",
$sthStatsCleanup->errstr()
awitpt::db::dblayer::Error()
);
}
# Loop and remove retained stats
foreach my $key (keys %{$statsConfig}) {
foreach my $key (keys %{STATS_CONFIG()}) {
# Work out timestamp to clean up to by multiplying the retention period by days
$cleanUpTo = $now - ($statsConfig->{$key}->{'retention'} * 86400);
$cleanUpTo = $now - (STATS_CONFIG()->{$key}->{'retention'} * 86400);
# Retention period is in # days
my $timerA = [gettimeofday];
if ($res = $sthStatsBasicCleanup->execute($key, $cleanUpTo)) {
if ($res = DBDo(SQL_CLEANUP_STATS_BASIC,$key,$cleanUpTo)) {
# We get 0E0 for 0 when none were removed
if ($res ne "0E0") {
my $timerB = [gettimeofday];
my $timerdiffA = tv_interval($timerA,$timerB);
$logger->log(LOG_INFO,"[STATISTICS] Cleanup stats_basic key %s in %s, %s items up to %s [%s]",
$key,
sprintf('%.3fs',$timerdiffA),
$res,
$cleanUpTo,
scalar(localtime($cleanUpTo))
$key,
sprintf('%.3fs',$timerdiffA),
$res,
$cleanUpTo,
scalar(localtime($cleanUpTo))
);
}
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to execute stats_basic cleanup statement for key %s: %s",
$key,
$sthStatsBasicCleanup->errstr()
$key,
awitpt::db::dblayer::Error()
);
}
# And normal stats...
$timerA = [gettimeofday];
if ($res = $sthStatsCleanup->execute($key, $cleanUpTo)) {
if ($res = DBDo(SQL_CLEANUP_STATS,$key,$cleanUpTo)) {
# We get 0E0 for 0 when none were removed
if ($res ne "0E0") {
my $timerB = [gettimeofday];
my $timerdiffA = tv_interval($timerA,$timerB);
$logger->log(LOG_INFO,"[STATISTICS] Cleanup stats key %s in %s, %s items up to %s [%s]",
$key,
sprintf('%.3fs',$timerdiffA),
$res,
$cleanUpTo,
scalar(localtime($cleanUpTo))
$key,
sprintf('%.3fs',$timerdiffA),
$res,
$cleanUpTo,
scalar(localtime($cleanUpTo))
);
}
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to execute stats cleanup statement for key %s: %s",
$key,
$sthStatsCleanup->errstr()
$key,
awitpt::db::dblayer::Error()
);
}
}
......@@ -657,7 +620,7 @@ sub _session_tick
my $timer4 = [gettimeofday];
my $timediff4 = tv_interval($timer3,$timer4);
$logger->log(LOG_INFO,"[STATISTICS] Total stats cleanup time: %s",
sprintf('%.3fs',$timediff4)
sprintf('%.3fs',$timediff4)
);
}
......@@ -684,11 +647,6 @@ sub _session_update
my ($kernel, $statsData) = @_[KERNEL, ARG0];
# TODO? This requires DB access
if (!$globals->{'DBHandle'}) {
return;
}
_processStatistics($kernel,$statsData);
}
......@@ -701,9 +659,9 @@ sub subscribe
$logger->log(LOG_INFO,"[STATISTICS] Got subscription request for '%s': handler='%s', event='%s'",
$sid,
$handler,
$event
$sid,
$handler,
$event
);
# Grab next SSID
......@@ -737,13 +695,13 @@ sub unsubscribe
my $item = $globals->{'SSIDMap'}->{$ssid};
if (!defined($item)) {
$logger->log(LOG_ERR,"[STATISTICS] Got unsubscription request for SSID '%s' that doesn't exist",
$ssid
$ssid
);
return
}
$logger->log(LOG_INFO,"[STATISTICS] Got unsubscription request for SSID '%s'",
$ssid
$ssid
);
# Remove subscriber
......@@ -848,7 +806,7 @@ sub getSIDFromCID
# Grab identifier based on class ID
my $identifier = _getIdentifierFromCID($iface,$cid);
if (!defined($identifier)) {
return undef;
return;
}
# Return the SID fo the identifier
......@@ -869,7 +827,7 @@ sub setSIDFromCID
# If not, grab the identifier
my $identifier = _getIdentifierFromCID($iface,$cid);
if (!defined($identifier)) {
return undef;
return;
}
# And setup a new SID
$sid = _setSIDFromIdentifier($identifier);
......@@ -889,7 +847,7 @@ sub getSIDFromPID
# Grab identifier from a PID
my $identifier = _getIdentifierFromPID($pid);
if (!defined($identifier)) {
return undef;
return;
}
# Return the SID for the PID
......@@ -909,7 +867,7 @@ sub setSIDFromPID
# If we can't, grab the identifier instead
my $identifier = _getIdentifierFromPID($pid);
if (!defined($identifier)) {
return undef;
return;
}
# And setup the SID
$sid = _setSIDFromIdentifier($identifier);
......@@ -929,7 +887,7 @@ sub getSIDFromCounter
# Grab identifier from a counter
my $identifier = _getIdentifierFromCounter($counter);
if (!defined($identifier)) {
return undef;
return;
}
# Return the SID for the counter
......@@ -950,7 +908,7 @@ sub setSIDFromCounter
# If we can't, grab the identifier instead
my $identifier = _getIdentifierFromCounter($counter);
if (!defined($identifier)) {
return undef;
return;
}
# And setup the SID
$sid = _setSIDFromIdentifier($identifier);
......@@ -978,7 +936,7 @@ sub getTrafficDirection
return STATISTICS_DIR_RX;
}
return undef;
return;
}
......@@ -1140,7 +1098,7 @@ sub _getIdentifierFromPID
my $pool = getPool($pid);
if (!defined($pool)) {
return undef;
return;
}
return sprintf("Pool:%s/%s",$pool->{'InterfaceGroupID'},$pool->{'Name'});
......@@ -1180,18 +1138,24 @@ sub _getSIDFromIdentifier
return $sid;
}
# We need the DB to be alive to do this...
if (!defined($globals->{'Database'})) {
return;
}
# Try grab it from DB
my $identifierGetSTH = $globals->{'DBPreparedStatements'}->{'identifier_get'};
if (my $res = $identifierGetSTH->execute($identifier)) {
if (my $res = DBSelect(SQL_GET_IDENTIFIER,$identifier)) {
# Grab first row and return
if (my $row = $identifierGetSTH->fetchrow_hashref()) {
if (my $row = $res->fetchrow_hashref()) {
DBFreeRes($res);
return $globals->{'IdentifierMap'}->{$identifier} = $row->{'id'};
}
DBFreeRes($res);
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to get SID from identifier '%s': %s",$identifier,$identifierGetSTH->errstr);
$logger->log(LOG_ERR,"[STATISTICS] Failed to get SID from identifier '%s': %s",$identifier,awitpt::db::dblayer::Error());
}
return undef;
return;
}
......@@ -1202,15 +1166,19 @@ sub _setSIDFromIdentifier
my $identifier = shift;
# We need the DB to be alive to do this...
if (!defined($globals->{'Database'})) {
return;
}
# Try add it to the DB
my $identifierAddSTH = $globals->{'DBPreparedStatements'}->{'identifier_add'};
if (my $res = $identifierAddSTH->execute($identifier)) {
return $globals->{'IdentifierMap'}->{$identifier} = $globals->{'DBHandle'}->last_insert_id("","","","");
if (my $res = DBDo(SQL_ADD_IDENTIFIER,$identifier)) {
return $globals->{'IdentifierMap'}->{$identifier} = DBLastInsertID("","");
} else {
$logger->log(LOG_ERR,"[STATISTICS] Failed to get SID from identifier '%s': %s",$identifier,$identifierAddSTH->errstr);
$logger->log(LOG_ERR,"[STATISTICS] Failed to set SID from identifier '%s': %s",$identifier,awitpt::db::dblayer::Error());
}
return undef;
return;
}
......@@ -1245,33 +1213,29 @@ sub _getStatsBySID
# Find the best key to use...
my $statsKey = 0;
foreach my $key (sort {$b <=> $a} keys %{$statsConfig}) {
foreach my $key (sort {$b <=> $a} keys %{STATS_CONFIG()}) {
# Grab first key that will hve 50+ entries
if ($timespan / $statsConfig->{$key}->{'precision'} > 50) {
if ($timespan / STATS_CONFIG()->{$key}->{'precision'} > 50) {
$statsKey = $key;
last;
}
}
# Prepare query
my $sth = $globals->{'DBHandle'}->prepare('
SELECT
`Timestamp`, `Direction`, `Rate`, `PPS`, `CIR`, `Limit`
FROM
stats
WHERE
`IdentifierID` = ?
AND `Key` = ?
AND `Timestamp` > ?
AND `Timestamp` < ?
ORDER BY
`Timestamp` DESC
');
my $statistics = { };
# We need the DB below this point
if (!defined($globals->{'Database'})) {
return $statistics;
}
# Grab last 60 mins of data
$sth->execute($sid,$statsKey,$startTimestamp,$endTimestamp);
my $res = DBSelect(SQL_GET_STATS,$sid,$statsKey,$startTimestamp,$endTimestamp);
if (!defined($res)) {
$logger->log(LOG_ERR,"[STATISTICS] Failed to get stats for SID '%s': %s",$sid,awitpt::db::dblayer::Error());
return $statistics;
}
my $statistics;
while (my $item = $sth->fetchrow_hashref()) {
while (my $item = $res->fetchrow_hashref()) {
$statistics->{$item->{'timestamp'}}->{$item->{'direction'}} = {
'rate' => $item->{'rate'},
'pps' => $item->{'pps'},
......@@ -1279,6 +1243,7 @@ sub _getStatsBySID
'limit' => $item->{'limit'},
}
}
DBFreeRes($res);
return $statistics;
}
......@@ -1306,37 +1271,30 @@ sub _getStatsBasicBySID
# Find the best key to use...
my $statsKey = 0;
foreach my $key (sort {$b <=> $a} keys %{$statsConfig}) {
foreach my $key (sort {$b <=> $a} keys %{STATS_CONFIG()}) {
# Grab first key that will hve 50+ entries
if ($timespan / $statsConfig->{$key}->{'precision'} > 50) {
if ($timespan / STATS_CONFIG()->{$key}->{'precision'} > 50) {
$statsKey = $key;
last;
}
}
my $statistics = { };
# We need the DB below this point
if (!defined($globals->{'Database'})) {
return $statistics;
}
# Prepare query
my $sth = $globals->{'DBHandle'}->prepare('
SELECT
`Timestamp`, `Counter`
FROM
stats_basic
WHERE
`IdentifierID` = ?
AND `Key` = ?
AND `Timestamp` > ?
AND `Timestamp` < ?
ORDER BY
`Timestamp` DESC
');
# Grab last 60 mins of data
$sth->execute($sid,$statsKey,$startTimestamp,$endTimestamp);
my $res = DBSelect(SQL_GET_STATS_BASIC,$sid,$statsKey,$startTimestamp,$endTimestamp);
my $statistics;
while (my $item = $sth->fetchrow_hashref()) {
while (my $item = $res->fetchrow_hashref()) {
$statistics->{$item->{'timestamp'}} = {
'counter' => $item->{'counter'},
}
}
DBFreeRes($res);
return $statistics;
}
......
# OpenTrafficShaper Linux tc traffic shaping
# Copyright (C) 2007-2014, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -21,6 +21,8 @@ package opentrafficshaper::plugins::tc;
use strict;
use warnings;
use JSON;
use List::Util qw(min max);
use POE qw(
Wheel::Run Filter::Line
);
......@@ -57,6 +59,8 @@ use opentrafficshaper::plugins::configmanager qw(
getAllTrafficClasses
getInterface
getInterfaceGroup
getInterfaceGroups
getInterfaces
getInterfaceDefaultPool
getEffectiveInterfaceTrafficClass2
......@@ -76,7 +80,7 @@ our (@ISA,@EXPORT,@EXPORT_OK);
);
use constant {
VERSION => '0.1.2',
VERSION => '1.0.1',
# 5% of a link can be used for very high priority traffic
PROTO_RATE_LIMIT => 5,
......@@ -135,7 +139,7 @@ sub plugin_init
# Setup our environment
$logger = $system->{'logger'};
$logger->log(LOG_NOTICE,"[TC] OpenTrafficShaper tc Integration v%s - Copyright (c) 2007-2014, AllWorldIT",VERSION);
$logger->log(LOG_NOTICE,"[TC] OpenTrafficShaper tc Integration v%s - Copyright (c) 2007-2023, AllWorldIT",VERSION);
# Initialize
$globals->{'TaskQueue'} = [ ];
......@@ -156,6 +160,255 @@ sub plugin_init
# We going to queue the initialization in plugin initialization so nothing at all can come before us
my $changeSet = TC::ChangeSet->new();
# Loop with protocols
for my $ipv ("", "6") {
#
# Traffic Shaping
#
# First the Cleanup
#
foreach my $interfaceID (getInterfaces()) {
my $interface = getInterface($interfaceID);
# Flush inteface chain
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-F',"ots-tcfor-$interfaceID",
]);
# Delete jump
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-D','ots-tcfor',
'-o',$interface->{'Device'},
'-j',"ots-tcfor-$interfaceID",
]);
# Delete interface chain
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-X',"ots-tcfor-$interfaceID",
]);
}
# Flush ots-tcfor
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-F','ots-tcfor',
]);
# Delete jump to ots-tcfor
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-D','FORWARD',
'-j','ots-tcfor',
]);
# Delete ots-tcfor
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-X','ots-tcfor',
]);
# Then the setup
#
# Create ots-tcfor
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-N','ots-tcfor',
]);
# Add jump to ots-tcfor
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-I','FORWARD',
'-j','ots-tcfor',
]);
# Add interface chains
foreach my $interfaceID (getInterfaces()) {
my $interface = getInterface($interfaceID);
# Create ots-tcfor
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-N',"ots-tcfor-$interfaceID",
]);
# Add jump to ots-tcfor
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-A','ots-tcfor',
'-o',$interface->{'Device'},
'-j',"ots-tcfor-$interfaceID",
]);
}
#
# NAT
#
# First the Cleanup
#
foreach my $interfaceGroupName (getInterfaceGroups()) {
my $interfaceGroup = getInterfaceGroup($interfaceGroupName);
my $txInterfaceID = $interfaceGroup->{'TxInterface'};
my $rxInterfaceID = $interfaceGroup->{'RxInterface'};
my $txInterface = getInterface($txInterfaceID);
my $rxInterface = getInterface($rxInterfaceID);
# Flush inteface chains
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-F',"ots-snat-$txInterfaceID",
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-F',"ots-dnat-$rxInterfaceID",
]);
# Delete interface chains
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-X',"ots-snat-$txInterfaceID",
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-X',"ots-dnat-$rxInterfaceID",
]);
}
# Flush ots-snat and ots-dnat
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-F','ots-snat',
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-F','ots-dnat',
]);
# Delete jump to ots-snat and ots-dnat
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-D','POSTROUTING',
'-j','ots-snat',
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-D','OUTPUT',
'-j','ots-dnat',
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-D','PREROUTING',
'-j','ots-dnat',
]);
# Delete ots-tcfor
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-X','ots-snat',
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-X','ots-dnat',
]);
# Then the setup
#
# Create ots-snat and ots-dnat
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-N','ots-snat',
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-N','ots-dnat',
]);
# Add jump to ots-snat and ots-dnat
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-I','POSTROUTING',
'-j','ots-snat',
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-I','OUTPUT',
'-j','ots-dnat',
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-A','PREROUTING',
'-j','ots-dnat',
]);
foreach my $interfaceGroupName (getInterfaceGroups()) {
my $interfaceGroup = getInterfaceGroup($interfaceGroupName);
my $txInterfaceID = $interfaceGroup->{'TxInterface'};
my $rxInterfaceID = $interfaceGroup->{'RxInterface'};
my $txInterface = getInterface($txInterfaceID);
my $rxInterface = getInterface($rxInterfaceID);
# Flush inteface chain
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-N',"ots-snat-$txInterfaceID",
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-N',"ots-dnat-$rxInterfaceID",
]);
# Add interface chains
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-A','ots-snat',
'-o',$rxInterface->{'Device'},
'-j',"ots-snat-$txInterfaceID",
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-A','ots-snat',
'-o',$txInterface->{'Device'},
'-j',"ots-snat-$txInterfaceID",
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-A','ots-dnat',
'-i',$rxInterface->{'Device'},
'-j',"ots-dnat-$rxInterfaceID",
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-A','ots-dnat',
'-i',$txInterface->{'Device'},
'-j',"ots-dnat-$rxInterfaceID",
]);
}
}
# Loop with the configured interfaces and initialize them
foreach my $interfaceID (getInterfaces()) {
my $interface = getInterface($interfaceID);
......@@ -315,14 +568,14 @@ sub _session_pool_add
# Grab pool
my $pool;
if (!defined($pool = getPool($pid))) {
$logger->log(LOG_ERR,"[TC] Shaper 'remove' event with non existing pool '%s'",$pid);
$logger->log(LOG_ERR,"[TC] Shaper 'add' event with non existing pool '%s'",$pid);
return;
}
$logger->log(LOG_INFO,"[TC] Add pool '%s' to interface group '%s' [%s]",
$logger->log(LOG_INFO,"[TC] Add pool '%s' [%s] to interface group '%s'",
$pool->{'Name'},
$pool->{'ID'},
$pool->{'InterfaceGroupID'},
$pool->{'ID'}
);
# Grab our effective pool
......@@ -445,11 +698,11 @@ sub _session_pool_remove
]);
# And recycle the classs
_disposePoolTcClass($txInterface,$txPoolTcClass);
_disposePoolTcClass($rxInterface,$rxPoolTcClass);
_disposePoolTcClass($txInterface->{'Device'},$txPoolTcClass);
_disposePoolTcClass($rxInterface->{'Device'},$rxPoolTcClass);
_disposePrioTcClass($txInterface,$txPoolTcClass);
_disposePrioTcClass($rxInterface,$rxPoolTcClass);
_disposePrioTcClass($txInterface->{'Device'},$txPoolTcClass);
_disposePrioTcClass($rxInterface->{'Device'},$rxPoolTcClass);
# Post changeset
$kernel->post("_tc" => "queue" => $changeSet);
......@@ -541,187 +794,101 @@ sub _session_poolmember_add
return;
}
$logger->log(LOG_INFO,"[TC] Add pool member '%s' to pool '%s' [%s]",
$poolMember->{'IPAddress'},
$poolMember->{'PoolID'},
$poolMember->{'ID'}
);
my $changeSet = TC::ChangeSet->new();
# Filter levels for the IP components
my @components = split(/\./,$poolMember->{'IPAddress'});
my $ip1 = $components[0];
my $ip2 = $components[1];
my $ip3 = $components[2];
my $ip4 = $components[3];
# Grab the pool members associated pool
my $pool;
if (!defined($pool = getPool($poolMember->{'PoolID'}))) {
$logger->log(LOG_ERR,"[TC] Shaper 'poolmember_add' event with invalid PoolID");
return;
}
# Grab some variables we going to need below
$logger->log(LOG_INFO,"[TC] Add pool member '%s' [%s] with IP '%s', NAT '%s' (inbound: %s) to pool '%s' [%s]",
$poolMember->{'Username'},
$poolMember->{'ID'},
$poolMember->{'IPAddress'},
$poolMember->{'IPNATAddress'} // "",
$poolMember->{'IPNATInbound'} // "",
$pool->{'Name'},
$pool->{'ID'}
);
my $changeSet = TC::ChangeSet->new();
my $txInterfaceID = getPoolTxInterface($pool->{'ID'});
my $rxInterfaceID = getPoolRxInterface($pool->{'ID'});
my $trafficPriority = getTrafficClassPriority($pool->{'TrafficClassID'});
my $matchPriority = getPoolMemberMatchPriority($poolMember->{'ID'});
# Check if we have a entry for the /8, if not we must create our 2nd level hash table and link it
if (!defined($globals->{'TcFilterMappings'}->{$txInterfaceID}->{'dst'}->{$matchPriority}->{$ip1})) {
# Grab filter ID's for 2nd level
my $filterID = _reserveTcFilter($txInterfaceID,$matchPriority,$pool->{'ID'});
# Track our mapping
$globals->{'TcFilterMappings'}->{$txInterfaceID}->{'dst'}->{$matchPriority}->{$ip1}->{'id'} = $filterID;
$logger->log(LOG_DEBUG,"[TC] Linking 2nd level TX hash table to '%s' to '%s.0.0.0/8', priority '%s'",
$filterID,
$ip1,
$matchPriority
);
_tc_filter_add_dstlink($changeSet,$txInterfaceID,TC_ROOT_CLASS,$matchPriority,$filterID,$config->{'ip_protocol'},800,"",
"$ip1.0.0.0/8","00ff0000");
}
if (!defined($globals->{'TcFilterMappings'}->{$rxInterfaceID}->{'src'}->{$matchPriority}->{$ip1})) {
# Grab filter ID's for 2nd level
my $filterID = _reserveTcFilter($rxInterfaceID,$matchPriority,$pool->{'ID'});
# Track our mapping
$globals->{'TcFilterMappings'}->{$rxInterfaceID}->{'src'}->{$matchPriority}->{$ip1}->{'id'} = $filterID;
$logger->log(LOG_DEBUG,"[TC] Linking 2nd level RX hash table to '%s' to '%s.0.0.0/8', priority '%s'",
$filterID,
$ip1,
$matchPriority
);
_tc_filter_add_srclink($changeSet,$rxInterfaceID,TC_ROOT_CLASS,$matchPriority,$filterID,$config->{'ip_protocol'},800,"",
"$ip1.0.0.0/8","00ff0000");
}
# Check if we have our /16 hash entry, if not we must create the 3rd level hash table
if (!defined($globals->{'TcFilterMappings'}->{$txInterfaceID}->{'dst'}->{$matchPriority}->{$ip1}->{$ip2})) {
# Grab filter ID's for 3rd level
my $filterID = _reserveTcFilter($txInterfaceID,$matchPriority,$pool->{'ID'});
# Track our mapping
$globals->{'TcFilterMappings'}->{$txInterfaceID}->{'dst'}->{$matchPriority}->{$ip1}->{$ip2}->{'id'} = $filterID;
# Grab some hash table ID's we need
my $ip1HtHex = $globals->{'TcFilterMappings'}->{$txInterfaceID}->{'dst'}->{$matchPriority}->{$ip1}->{'id'};
# And hex our IP component
my $ip2Hex = toHex($ip2);
$logger->log(LOG_DEBUG,"[TC] Linking 3rd level TX hash table to '%s' to '%s.%s.0.0/16', priority '%s'",
$filterID,
$ip1,
$ip2,
$matchPriority
);
_tc_filter_add_dstlink($changeSet,$txInterfaceID,TC_ROOT_CLASS,$matchPriority,$filterID,$config->{'ip_protocol'},$ip1HtHex,
$ip2Hex,"$ip1.$ip2.0.0/16","0000ff00");
}
if (!defined($globals->{'TcFilterMappings'}->{$rxInterfaceID}->{'src'}->{$matchPriority}->{$ip1}->{$ip2})) {
# Grab filter ID's for 3rd level
my $filterID = _reserveTcFilter($rxInterfaceID,$matchPriority,$pool->{'ID'});
# Track our mapping
$globals->{'TcFilterMappings'}->{$rxInterfaceID}->{'src'}->{$matchPriority}->{$ip1}->{$ip2}->{'id'} = $filterID;
# Grab some hash table ID's we need
my $ip1HtHex = $globals->{'TcFilterMappings'}->{$rxInterfaceID}->{'src'}->{$matchPriority}->{$ip1}->{'id'};
# And hex our IP component
my $ip2Hex = toHex($ip2);
$logger->log(LOG_DEBUG,"[TC] Linking 3rd level RX hash table to '%s' to '%s.%s.0.0/16', priority '%s'",
$filterID,
$ip1,
$ip2,
$matchPriority
);
_tc_filter_add_srclink($changeSet,$rxInterfaceID,TC_ROOT_CLASS,$matchPriority,$filterID,$config->{'ip_protocol'},$ip1HtHex,
$ip2Hex,"$ip1.$ip2.0.0/16","0000ff00");
}
my $rxPoolTcClass = getPoolAttribute($pool->{'ID'},'tc.rxclass');
my $txPoolTcClass = getPoolAttribute($pool->{'ID'},'tc.txclass');
# Check if we have our /24 hash entry, if not we must create the 4th level hash table
if (!defined($globals->{'TcFilterMappings'}->{$txInterfaceID}->{'dst'}->{$matchPriority}->{$ip1}->{$ip2}->{$ip3})) {
# Grab filter ID's for 4th level
my $filterID = _reserveTcFilter($txInterfaceID,$matchPriority,$pool->{'ID'});
# Track our mapping
$globals->{'TcFilterMappings'}->{$txInterfaceID}->{'dst'}->{$matchPriority}->{$ip1}->{$ip2}->{$ip3}->{'id'} = $filterID;
# Grab some hash table ID's we need
my $ip2HtHex = $globals->{'TcFilterMappings'}->{$txInterfaceID}->{'dst'}->{$matchPriority}->{$ip1}->{$ip2}->{'id'};
# And hex our IP component
my $ip3Hex = toHex($ip3);
$logger->log(LOG_DEBUG,"[TC] Linking 4th level TX hash table to '%s' to '%s.%s.%s.0/24', priority '%s'",
$filterID,
$ip1,
$ip2,
$ip3,
$matchPriority
);
_tc_filter_add_dstlink($changeSet,$txInterfaceID,TC_ROOT_CLASS,$matchPriority,$filterID,$config->{'ip_protocol'},$ip2HtHex,
$ip3Hex,"$ip1.$ip2.$ip3.0/24","000000ff");
# Check what IP version we're dealing with
my $ipv = "";
if ($poolMember->{'IPAddress'} =~ /:/) {
$ipv = "6";
}
if (!defined($globals->{'TcFilterMappings'}->{$rxInterfaceID}->{'src'}->{$matchPriority}->{$ip1}->{$ip2}->{$ip3})) {
# Grab filter ID's for 4th level
my $filterID = _reserveTcFilter($rxInterfaceID,$matchPriority,$pool->{'ID'});
# Track our mapping
$globals->{'TcFilterMappings'}->{$rxInterfaceID}->{'src'}->{$matchPriority}->{$ip1}->{$ip2}->{$ip3}->{'id'} = $filterID;
# Grab some hash table ID's we need
my $ip2HtHex = $globals->{'TcFilterMappings'}->{$rxInterfaceID}->{'src'}->{$matchPriority}->{$ip1}->{$ip2}->{'id'};
# And hex our IP component
my $ip3Hex = toHex($ip3);
$logger->log(LOG_DEBUG,"[TC] Linking 4th level RX hash table to '%s' to '%s.%s.%s.0/24', priority '%s'",
$filterID,
$ip1,
$ip2,
$ip3,
$matchPriority
);
_tc_filter_add_srclink($changeSet,$rxInterfaceID,TC_ROOT_CLASS,$matchPriority,$filterID,$config->{'ip_protocol'},$ip2HtHex,
$ip3Hex,"$ip1.$ip2.$ip3.0/24","000000ff");
}
#
# For sake of simplicity and so things loook all nice and similar, we going to do these 2 blocks in { }
#
# Only if we have TX limits setup process them
{
# Get the TX class
my $tcClass_trafficClass = getPoolAttribute($pool->{'ID'},'tc.txclass');
# Grab some hash table ID's we need
my $ip3HtHex = $globals->{'TcFilterMappings'}->{$txInterfaceID}->{'dst'}->{$matchPriority}->{$ip1}->{$ip2}->{$ip3}->{'id'};
# And hex our IP component
my $ip4Hex = toHex($ip4);
$logger->log(LOG_DEBUG,"[TC] Linking pool member IP '%s' to class '%s' at hash endpoint '%s:%s'",
$poolMember->{'IPAddress'},
$tcClass_trafficClass,
$ip3HtHex,
$ip4Hex
);
# Link filter to traffic flow (class)
_tc_filter_add_flowlink($changeSet,$txInterfaceID,TC_ROOT_CLASS,$trafficPriority,$config->{'ip_protocol'},$ip3HtHex,
$ip4Hex,"dst",16,$poolMember->{'IPAddress'},$tcClass_trafficClass);
# Save pool member filter ID
setPoolMemberAttribute($poolMember->{'ID'},'tc.txfilter',"${ip3HtHex}:${ip4Hex}:1");
# Add traffic classification
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-A',"ots-tcfor-$rxInterfaceID",
'-s',$poolMember->{'IPAddress'},
'-m','comment',
'--comment', "pool: ".$pool->{'Name'}.", member: ".$poolMember->{'Username'},
'-j','CLASSIFY',
'--set-class',"1:$rxPoolTcClass",
]);
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-A',"ots-tcfor-$txInterfaceID",
'-d',$poolMember->{'IPAddress'},
'-m','comment',
'--comment', "pool: ".$pool->{'Name'}.", member: ".$poolMember->{'Username'},
'-j','CLASSIFY',
'--set-class',"1:$txPoolTcClass",
]);
# Add NAT if we have any
if (defined($poolMember->{'IPNATAddress'}) && defined($poolMember->{'IPNATAddress'}) ne "") {
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-A',"ots-snat-$txInterfaceID",
'-s',$poolMember->{'IPAddress'},
'-m','comment',
'--comment', "pool: ".$pool->{'Name'}.", member: ".$poolMember->{'Username'},
'-j','SNAT',
'--to-source', $poolMember->{'IPNATAddress'},
]);
# If we're dealing with IPv4, clear the connection tracking
if ($ipv eq "") {
$changeSet->add(["/sbin/conntrack",'-D','-s',$poolMember->{'IPAddress'}]);
}
if (defined($poolMember->{'IPNATInbound'}) && $poolMember->{'IPNATInbound'} eq "yes") {
my @ipComponents = split(/\//,$poolMember->{'IPAddress'});
my $dnatTo = $ipComponents[0];
if (@ipComponents < 2 || $ipComponents[1] eq "32" || $ipComponents[1] eq "128") {
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-A',"ots-dnat-$rxInterfaceID",
'-d',$poolMember->{'IPNATAddress'},
'-m','comment',
'--comment', "pool: ".$pool->{'Name'}.", member: ".$poolMember->{'Username'},
'-j','DNAT',
'--to-destination', $dnatTo,
]);
} else {
$logger->log(LOG_WARN,"[TC] Cannot add inbound NAT for pool member '%s' [%s] with IP '%s', NAT '%s' (inbound: %s) to pool '%s' [%s]",
$poolMember->{'Username'},
$poolMember->{'ID'},
$poolMember->{'IPAddress'},
$poolMember->{'IPNATAddress'} // "",
$poolMember->{'IPNATInbound'} // "",
$pool->{'Name'},
$pool->{'ID'}
);
}
}
}
# Only if we have RX limits setup process them
{
# Generate our limit TC class
my $tcClass_trafficClass = getPoolAttribute($pool->{'ID'},'tc.rxclass');
# Grab some hash table ID's we need
my $ip3HtHex = $globals->{'TcFilterMappings'}->{$rxInterfaceID}->{'src'}->{$matchPriority}->{$ip1}->{$ip2}->{$ip3}->{'id'};
# And hex our IP component
my $ip4Hex = toHex($ip4);
$logger->log(LOG_DEBUG,"[TC] Linking RX IP '%s' to class '%s' at hash endpoint '%s:%s'",
$poolMember->{'IPAddress'},
$tcClass_trafficClass,
$ip3HtHex,
$ip4Hex
);
# Link filter to traffic flow (class)
_tc_filter_add_flowlink($changeSet,$rxInterfaceID,TC_ROOT_CLASS,$trafficPriority,$config->{'ip_protocol'},$ip3HtHex,
$ip4Hex,"src",12,$poolMember->{'IPAddress'},$tcClass_trafficClass);
# Save pool member filter ID
setPoolMemberAttribute($poolMember->{'ID'},'tc.rxfilter',"${ip3HtHex}:${ip4Hex}:1");
}
# Post changeset
$kernel->post("_tc" => "queue" => $changeSet);
......@@ -751,65 +918,109 @@ sub _session_poolmember_remove
# Make sure its not NOTLIVE
if (getPoolMemberShaperState($pmid) & SHAPER_NOTLIVE) {
$logger->log(LOG_WARN,"[TC] Ignoring remove for pool member '%s' with IP '%s' [%s] from pool '%s'",
$logger->log(LOG_WARN,"[TC] Ignoring remove for pool member '%s' with IP '%s', NAT '%s' (inbound: %s) [%s] from pool '%s'",
$poolMember->{'Username'},
$poolMember->{'IPAddress'},
$poolMember->{'IPNATAddress'} // "",
$poolMember->{'IPNATInbound'} // "",
$poolMember->{'ID'},
$pool->{'Name'}
);
return;
}
$logger->log(LOG_INFO,"[TC] Removing pool member '%s' with IP '%s' [%s] from pool '%s'",
$logger->log(LOG_INFO,"[TC] Remove pool member '%s' [%s] with IP '%s', NAT '%s' (inbound: %s) from pool '%s' [%s]",
$poolMember->{'Username'},
$poolMember->{'IPAddress'},
$poolMember->{'ID'},
$pool->{'Name'}
$poolMember->{'IPAddress'},
$poolMember->{'IPNATAddress'} // "",
$poolMember->{'IPNATInbound'} // "",
$pool->{'Name'},
$pool->{'ID'}
);
# Grab our interfaces
my $changeSet = TC::ChangeSet->new();
my $txInterfaceID = getPoolTxInterface($pool->{'ID'});
my $rxInterfaceID = getPoolRxInterface($pool->{'ID'});
# Grab the filter ID's from the pool member which is linked to the traffic class
my $txFilter = getPoolMemberAttribute($poolMember->{'ID'},'tc.txfilter');
my $rxFilter = getPoolMemberAttribute($poolMember->{'ID'},'tc.rxfilter');
# Grab current class ID
my $trafficClassID = getPoolAttribute($pool->{'ID'},'shaper.live.ClassID');
my $trafficPriority = getTrafficClassPriority($trafficClassID);
my $txInterface = getInterface($txInterfaceID);
my $rxInterface = getInterface($rxInterfaceID);
my $changeSet = TC::ChangeSet->new();
my $rxPoolTcClass = getPoolAttribute($pool->{'ID'},'tc.rxclass');
my $txPoolTcClass = getPoolAttribute($pool->{'ID'},'tc.txclass');
# Clear up the filter
# Check what IP version we're dealing with
my $ipv = "";
if ( $poolMember->{'IPAddress'} =~ /:/ ) {
$ipv = 6;
}
# Remove traffic classification
$changeSet->add([
'/sbin/tc','filter','del',
'dev',$txInterface->{'Device'},
'parent','1:',
'prio',$trafficPriority,
'handle',$txFilter,
'protocol',$config->{'ip_protocol'},
'u32',
"/sbin/ip${ipv}tables",
'-t','mangle',
'-D',"ots-tcfor-$rxInterfaceID",
'-s',$poolMember->{'IPAddress'},
'-m','comment',
'--comment', "pool: ".$pool->{'Name'}.", member: ".$poolMember->{'Username'},
'-j','CLASSIFY',
'--set-class',"1:$rxPoolTcClass",
]);
$changeSet->add([
'/sbin/tc','filter','del',
'dev',$rxInterface->{'Device'},
'parent','1:',
'prio',$trafficPriority,
'handle',$rxFilter,
'protocol',$config->{'ip_protocol'},
'u32',
"/sbin/ip${ipv}tables",
'-t','mangle',
'-D',"ots-tcfor-$txInterfaceID",
'-d',$poolMember->{'IPAddress'},
'-m','comment',
'--comment', "pool: ".$pool->{'Name'}.", member: ".$poolMember->{'Username'},
'-j','CLASSIFY',
'--set-class',"1:$txPoolTcClass",
]);
# Remove NAT if we have any
if (defined($poolMember->{'IPNATAddress'}) && defined($poolMember->{'IPNATAddress'}) ne "") {
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-D',"ots-snat-$txInterfaceID",
'-s',$poolMember->{'IPAddress'},
'-m','comment',
'--comment', "pool: ".$pool->{'Name'}.", member: ".$poolMember->{'Username'},
'-j','SNAT',
'--to-source', $poolMember->{'IPNATAddress'},
]);
if (defined($poolMember->{'IPNATInbound'}) && $poolMember->{'IPNATInbound'} eq "yes") {
my @ipComponents = split(/\//,$poolMember->{'IPAddress'});
my $dnatTo = $ipComponents[0];
if (@ipComponents < 2 || $ipComponents[1] eq "32" || $ipComponents[1] eq "128") {
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','nat',
'-D',"ots-dnat-$rxInterfaceID",
'-d',$poolMember->{'IPNATAddress'},
'-m','comment',
'--comment', "pool: ".$pool->{'Name'}.", member: ".$poolMember->{'Username'},
'-j','DNAT',
'--to-destination', $dnatTo,
]);
} else {
$logger->log(LOG_WARN,"[TC] Cannot remove inbound NAT for pool member '%s' [%s] with IP '%s', NAT '%s' (inbound: %s) to pool '%s' [%s]",
$poolMember->{'Username'},
$poolMember->{'ID'},
$poolMember->{'IPAddress'},
$poolMember->{'IPNATAddress'} // "",
$poolMember->{'IPNATInbound'} // "",
$pool->{'Name'},
$pool->{'ID'}
);
}
}
}
# Post changeset
$kernel->post("_tc" => "queue" => $changeSet);
# Cleanup attributes
removePoolMemberAttribute($poolMember->{'ID'},'tc.txfilter');
removePoolMemberAttribute($poolMember->{'ID'},'tc.rxfilter');
# Mark as not live
unsetPoolMemberShaperState($poolMember->{'ID'},SHAPER_LIVE|SHAPER_PENDING);
setPoolMemberShaperState($poolMember->{'ID'},SHAPER_NOTLIVE);
......@@ -826,7 +1037,7 @@ sub getPIDFromTcClass
# Return the pool ID if found
my $ref = __getRefByMinorTcClass($interfaceID,$majorTcClass,$minorTcClass);
if (!defined($ref) || substr($ref,0,13) ne "_pool_class_:") {
return undef;
return;
}
return substr($ref,13);
......@@ -842,7 +1053,7 @@ sub isPoolTcClass
my $pid = getPIDFromTcClass($interfaceID,$majorTcClass,$minorTcClass);
if (!defined($pid)) {
return undef;
return;
}
return $minorTcClass;
......@@ -859,9 +1070,15 @@ sub getCIDFromTcClass
# Grab ref
my $ref = __getRefByMinorTcClass($interfaceID,$majorTcClass,$minorTcClass);
# If we're undefined return
if (!defined($ref)) {
return;
}
# If we're not a traffic class, just return
if (substr($ref,0,16) ne "_traffic_class_:") {
return undef;
return;
}
# Else return the part after the above tag
......@@ -909,17 +1126,6 @@ sub _tc_iface_init
my $interfaceTrafficClassTcClass = _reserveMinorTcClassByTrafficClassID($interfaceID,$trafficClassID);
}
# Do we have a default pool? if so we must direct traffic there
my @qdiscOpts = ( );
my $defaultPool = getInterfaceDefaultPool($interfaceID);
my $defaultPoolTcClass;
if (defined($defaultPool)) {
# Push unclassified traffic to this class
$defaultPoolTcClass = _getTcClassFromTrafficClassID($interfaceID,$defaultPool);
push(@qdiscOpts,'default',$defaultPoolTcClass);
}
### --- Interface Setup Part 2
# Add root qdisc
......@@ -928,11 +1134,11 @@ sub _tc_iface_init
'dev',$interface->{'Device'},
'root',
'handle','1:',
'htb',
@qdiscOpts
'htb'
]);
# Attach our main limit on the qdisc
my $burst = int( ($interface->{'Limit'} / 8) * 1024 * 10); # Allow the entire interface to be emptied with a burst
$changeSet->add([
'/sbin/tc','class','add',
'dev',$interface->{'Device'},
......@@ -940,7 +1146,6 @@ sub _tc_iface_init
'classid','1:1',
'htb',
'rate',"$interface->{'Limit'}kbit",
'burst',"$interface->{'Limit'}kb",
]);
# Class 0 is our interface, it points to 1 (the major TcClass)) : 1 (class below)
......@@ -970,7 +1175,6 @@ sub _tc_iface_init
'rate',"$interfaceTrafficClass->{'CIR'}kbit",
'ceil',"$interfaceTrafficClass->{'Limit'}kbit",
'prio',$trafficPriority,
'burst', "$interfaceTrafficClass->{'Limit'}kb",
]);
# Setup interface traffic class details
......@@ -982,76 +1186,28 @@ sub _tc_iface_init
}
# Process our default pool traffic optimizations
my $defaultPool = getInterfaceDefaultPool($interfaceID);
if (defined($defaultPool)) {
my $interfaceTrafficClassID = isInterfaceTrafficClassValid($interfaceID,$defaultPool);
my $interfaceTrafficClass = getEffectiveInterfaceTrafficClass2($interfaceTrafficClassID);
my $defaultPoolTcClass = _getTcClassFromTrafficClassID($interfaceID, $defaultPool);
# Loop with IP versions
for my $ipv ("", "6") {
$changeSet->add([
"/sbin/ip${ipv}tables",
'-t','mangle',
'-A',"ots-tcfor-$interfaceID",
'-m','comment',
'--comment', "Default",
'-j','CLASSIFY',
'--set-class',"1:$defaultPoolTcClass",
]);
}
# If we have a rate for this iface, then use it
_tc_class_optimize($changeSet,$interfaceID,$defaultPoolTcClass,$interfaceTrafficClass->{'Limit'});
# Make the queue size big enough
my $queueSize = ($interface->{'Limit'} * 1024) / 8;
# RED metrics (sort of as per manpage)
my $redAvPkt = 1000;
my $redMax = int($queueSize / 4); # 25% mark at 100% probabilty
my $redMin = int($redMax / 3); # Max/3 is when the probability starts
my $redBurst = int( ($redMin+$redMax) / (2*$redAvPkt));
my $redLimit = $queueSize;
my $prioTcClass = _getPrioTcClass($interfaceID,$defaultPoolTcClass);
# Priority band
my $prioBand = 1;
$changeSet->add([
'/sbin/tc','qdisc','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:".toHex($prioBand),
'handle',_reserveMajorTcClass($interfaceID,"_default_pool_:$defaultPoolTcClass=>$prioBand").":",
'bfifo',
'limit',$queueSize,
]);
$prioBand++;
$changeSet->add([
'/sbin/tc','qdisc','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:".toHex($prioBand),
'handle',_reserveMajorTcClass($interfaceID,"_default_pool_:$defaultPoolTcClass=>$prioBand").":",
# TODO: NK - try enable the below
# 'estimator','1sec','4sec', # Quick monitoring, every 1s with 4s constraint
'red',
'min',$redMin,
'max',$redMax,
'limit',$redLimit,
'burst',$redBurst,
'avpkt',$redAvPkt,
# NK: ECN may cause excessive dips in traffic if there is an exceptional amount of traffic
# 'ecn'
# XXX: Very new kernels only ... use redflowlimit in future
# 'sfq',
# 'divisor','16384',
# 'headdrop',
# 'redflowlimit',$queueSize,
# 'ecn',
]);
$prioBand++;
$changeSet->add([
'/sbin/tc','qdisc','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:".toHex($prioBand),
'handle',_reserveMajorTcClass($interfaceID,"_default_pool_:$defaultPoolTcClass=>$prioBand").":",
'red',
'min',$redMin,
'max',$redMax,
'limit',$redLimit,
'burst',$redBurst,
'avpkt',$redAvPkt,
# NK: ECN may cause excessive dips in traffic if there is an exceptional amount of traffic
# 'ecn'
]);
}
}
......@@ -1065,21 +1221,13 @@ sub _tc_class_optimize
my $interface = getInterface($interfaceID);
# Rate for things like ICMP , ACK, SYN ... etc
my $rateBand1 = int($rate * (PROTO_RATE_LIMIT / 100));
$rateBand1 = PROTO_RATE_BURST_MIN if ($rateBand1 < PROTO_RATE_BURST_MIN);
my $rateBand1Burst = ($rateBand1 / 8) * PROTO_RATE_BURST_MAXM;
# Rate for things like VoIP/SSH/Telnet
my $rateBand2 = int($rate * (PRIO_RATE_LIMIT / 100));
$rateBand2 = PRIO_RATE_BURST_MIN if ($rateBand2 < PRIO_RATE_BURST_MIN);
my $rateBand2Burst = ($rateBand2 / 8) * PRIO_RATE_BURST_MAXM;
my $prioTcClass = _reserveMajorTcClassByPrioClass($interfaceID,$poolTcClass);
#
# DEFINE 3 PRIO BANDS
#
# Mbyte/s rate divided by average packet size to get packet limit, with a minimum of 127
my $sfqQueueLength = max(int((($rate / 8) * 1024) / 1000), 127);
# Flows is Mbit/s divided by 20, with minimum of 127
my $sfqFlows = max(int($rate / 1000 / 20) * 127, 127);
my $sfqRedFlowLimit = ($sfqQueueLength / 4) * 1500; # Take 25% of the queue length and packet size of 1500
# We then prioritize traffic into 3 bands based on TOS
$changeSet->add([
......@@ -1087,445 +1235,15 @@ sub _tc_class_optimize
'dev',$interface->{'Device'},
'parent',"1:$poolTcClass",
'handle',"$prioTcClass:",
'prio',
'bands','3',
'priomap','2','2','2','2','2','2','2','2','2','2','2','2','2','2','2','2',
]);
#
# CLASSIFICATIONS
#
# Prioritize ICMP up to a certain limit
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x1','0xff', # ICMP
'at',9+$config->{'iphdr_offset'},
'police',
'rate',"${rateBand1}kbit",'burst',"${rateBand1Burst}k",'continue',
'flowid',"$prioTcClass:1",
]);
# Prioritize ACK up to a certain limit
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u8','0x10','0xff', # ACK
'at',33+$config->{'iphdr_offset'},
'police',
'rate',"${rateBand1}kbit",'burst',"${rateBand1Burst}k",'continue',
'flowid',"$prioTcClass:1",
]);
# Prioritize SYN-ACK up to a certain limit
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u8','0x12','0xff', # SYN-ACK
'at',33+$config->{'iphdr_offset'},
'police',
'rate',"${rateBand1}kbit",'burst',"${rateBand1Burst}k",'continue',
'flowid',"$prioTcClass:1",
]);
# Prioritize FIN up to a certain limit
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u8','0x1','0xff', # FIN
'at',33+$config->{'iphdr_offset'},
'police',
'rate',"${rateBand1}kbit",'burst',"${rateBand1Burst}k",'continue',
'flowid',"$prioTcClass:1",
]);
# Prioritize RST up to a certain limit
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u8','0x4','0xff', # RST
'at',33+$config->{'iphdr_offset'},
'police',
'rate',"${rateBand1}kbit",'burst',"${rateBand1Burst}k",'continue',
'flowid',"$prioTcClass:1",
]);
# DNS
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u16','0x0035','0xffff', # SPORT 53
'at',20+$config->{'iphdr_offset'},
'police',
'rate',"${rateBand2}kbit",'burst',"${rateBand2Burst}k",'continue',
'flowid',"$prioTcClass:1",
]);
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u16','0x0035','0xffff', # DPORT 53
'at',22+$config->{'iphdr_offset'},
'police',
'rate',"${rateBand2}kbit",'burst',"${rateBand2Burst}k",'continue',
'flowid',"$prioTcClass:1",
]);
# VOIP
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u16','0x13c4','0xffff', # SPORT 5060
'at',20+$config->{'iphdr_offset'},
'police',
'rate',"${rateBand2}kbit",'burst',"${rateBand2Burst}k",'continue',
'flowid',"$prioTcClass:1",
]);
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u16','0x13c4','0xffff', # DPORT 5060
'at',22+$config->{'iphdr_offset'},
'police',
'rate',"${rateBand2}kbit",'burst',"${rateBand2Burst}k",'continue',
'flowid',"$prioTcClass:1",
]);
# SNMP
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0xa1','0xffff', # SPORT 161
'at',20+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:1",
]);
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0xa1','0xffff', # DPORT 161
'at',22+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:1",
]);
# TODO: Make this customizable not hard coded?
# Mikrotik Management Port
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u16','0x2063','0xffff', # SPORT 8291
'at',20+$config->{'iphdr_offset'},
'police',
'rate',"${rateBand2}kbit",'burst',"${rateBand2Burst}k",'continue',
'flowid',"$prioTcClass:1",
]);
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u16','0x2063','0xffff', # DPORT 8291
'at',22+$config->{'iphdr_offset'},
'police',
'rate',"${rateBand2}kbit",'burst',"${rateBand2Burst}k",'continue',
'flowid',"$prioTcClass:1",
]);
# SMTP
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0x19','0xffff', # SPORT 25
'at',20+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:2",
]);
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0x19','0xffff', # DPORT 25
'at',22+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:2",
]);
# POP3
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0x6e','0xffff', # SPORT 110
'at',20+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:2",
]);
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0x6e','0xffff', # DPORT 110
'at',22+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:2",
]);
# IMAP
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0x8f','0xffff', # SPORT 143
'at',20+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:2",
]);
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0x8f','0xffff', # DPORT 143
'at',22+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:2",
]);
# HTTP
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0x50','0xffff', # SPORT 80
'at',20+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:2",
]);
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0x50','0xffff', # DPORT 80
'at',22+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:2",
]);
# HTTPS
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0x1bb','0xffff', # SPORT 443
'at',20+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:2",
]);
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$prioTcClass:",
'prio','1',
'protocol',$config->{'ip_protocol'},
'u32',
'match','u8','0x6','0xff', # TCP
'at',9+$config->{'iphdr_offset'},
'match','u16','0x1bb','0xffff', # DPORT 443
'at',22+$config->{'iphdr_offset'},
'flowid',"$prioTcClass:2",
'sfq',
'limit', $sfqQueueLength, # first work out byte rate, then divide by average packet size of 1000
'flows', $sfqFlows,
'headdrop',
'perturb', 10,
'redflowlimit', $sfqRedFlowLimit,
'perturb', 10,
]);
}
# Function to easily add a hash table
sub _tc_filter_add_dstlink
{
my ($changeSet,$interfaceID,$parentID,$priority,$filterID,$protocol,$htHex,$ipHex,$cidr,$mask) = @_;
# Add hash table
_tc_filter_hash_add($changeSet,$interfaceID,$parentID,$priority,$filterID,$config->{'ip_protocol'});
# Add filter to it
_tc_filter_add($changeSet,$interfaceID,$parentID,$priority,$filterID,$protocol,$htHex,$ipHex,"dst",16,$cidr,$mask);
}
# Function to easily add a hash table
sub _tc_filter_add_srclink
{
my ($changeSet,$interfaceID,$parentID,$priority,$filterID,$protocol,$htHex,$ipHex,$cidr,$mask) = @_;
# Add hash table
_tc_filter_hash_add($changeSet,$interfaceID,$parentID,$priority,$filterID,$config->{'ip_protocol'});
# Add filter to it
_tc_filter_add($changeSet,$interfaceID,$parentID,$priority,$filterID,$protocol,$htHex,$ipHex,"src",12,$cidr,$mask);
}
# Function to easily add a hash table
sub _tc_filter_add_flowlink
{
my ($changeSet,$interfaceID,$parentID,$priority,$protocol,$htHex,$ipHex,$type,$offset,$ip,$poolTcClass) = @_;
my $interface = getInterface($interfaceID);
# Link hash table
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$parentID:",
'prio',$priority,
'handle',"$htHex:$ipHex:1",
'protocol',$protocol,
'u32',
# Root hash table
'ht',"$htHex:$ipHex:",
'match','ip',$type,$ip,
'at',$offset+$config->{'iphdr_offset'},
# Link to our flow
'flowid',"1:$poolTcClass",
]);
}
# Function to easily add a hash table
sub _tc_filter_hash_add
{
my ($changeSet,$interfaceID,$parentID,$priority,$filterID,$protocol) = @_;
my $interface = getInterface($interfaceID);
# Create second level hash table for $ip1
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$parentID:",
'prio',$priority,
'handle',"$filterID:",
'protocol',$protocol,
'u32',
'divisor','256',
]);
}
# Function to easily add a hash table
sub _tc_filter_add
{
my ($changeSet,$interfaceID,$parentID,$priority,$filterID,$protocol,$htHex,$ipHex,$type,$offset,$cidr,$mask) = @_;
my $interface = getInterface($interfaceID);
# Link hash table
$changeSet->add([
'/sbin/tc','filter','add',
'dev',$interface->{'Device'},
'parent',"$parentID:",
'prio',$priority,
'protocol',$protocol,
'u32',
# Root hash table
'ht',"$htHex:$ipHex:",
'match','ip',$type,$cidr,
'at',$offset+$config->{'iphdr_offset'},
'hashkey','mask',"0x$mask",
'at',$offset+$config->{'iphdr_offset'},
# Link to our hash table
'link',"$filterID:"
]);
}
......@@ -1538,8 +1256,17 @@ sub _tc_class_add
my $interface = getInterface($interfaceID);
# Set burst to a sane value
my $burst = int($ceil / 8 / 5);
# # Set burst to a sane value, in this case the CIR (or $rate) size
# my $burst = int($rate / 8);
# my $cburst = int(($ceil - $rate) / 8 / 10);
# # NK: cburst should not exceed burst, if it does, just use the burst value
# # this ensures we do not get negative burst
# if ($cburst > $burst) {
# $cburst = $burst;
# }
my $burst = int( ($ceil / 8) * 1024 * 10);
my $cburst = int($rate / 8 / 5) + 2; # +2kb to cover 1600
my $quantum = int($cburst / 2) * 1024; # Burst in bytes
# Create main rate limiting classes
$changeSet->add([
......@@ -1550,8 +1277,9 @@ sub _tc_class_add
'htb',
'rate', "${rate}kbit",
'ceil', "${ceil}kbit",
'prio', $trafficPriority,
'burst', "${burst}kb",
# 'prio', $trafficPriority,
# 'burst', "${burst}kb",
# 'cburst', "${cburst}kb",
]);
}
......@@ -1567,19 +1295,27 @@ sub _tc_class_change
my @args = ();
# Based on if ceil is avaiable, set burst
my $burst;
if (defined($ceil)) {
$burst = int($ceil / 8 / 5);
} else {
# If ceil is not available, set burst and ceil
$burst = $ceil = $rate;
# If ceil is not available, set it to the CIR (or $rate in this case)
if (!defined($ceil)) {
$ceil = $rate;
}
# Check if we have a priority
if (defined($trafficPriority)) {
push(@args,'prio',$trafficPriority);
}
# # Set the burst rate to the CIR (or $rate in this case)
# my $burst = int($rate / 8);
# my $cburst = int(($ceil - $rate) / 8 / 10);
# # NK: cburst should not exceed burst, if it does, just use the burst value
# # this ensures we do not get negative burst
# if ($cburst > $burst) {
# $cburst = $burst;
# }
my $burst = int( ($ceil / 8) * 1024 * 10);
my $cburst = int($rate / 8 / 5) + 2; # +2kb to cover 1600
my $quantum = int($cburst / 2) * 1024; # Burst in bytes
# # Check if we have a priority
# if (defined($trafficPriority)) {
# push(@args,'prio',$trafficPriority);
# }
# Create main rate limiting classes
$changeSet->add([
......@@ -1590,7 +1326,8 @@ sub _tc_class_change
'htb',
'rate', "${rate}kbit",
'ceil', "${ceil}kbit",
'burst', "${burst}kb",
# 'burst', "${burst}kb",
# 'cburst', "${cburst}kb",
@args
]);
}
......@@ -1671,7 +1408,7 @@ sub _disposePrioTcClass
# If we can grab the major class dipose of it
my $majorTcClass = _getPrioTcClass($interfaceID,$tcClass);
if (!defined($majorTcClass)) {
return undef;
return;
}
return __disposeMajorTcClass($interfaceID,$majorTcClass);
......@@ -1756,7 +1493,7 @@ sub __getMinorTcClassByRef
if (!defined($globals->{'TcClasses'}->{$interfaceID}) || !defined($globals->{'TcClasses'}->{$interfaceID}->{$majorTcClass})) {
return undef;
return;
}
return $globals->{'TcClasses'}->{$interfaceID}->{$majorTcClass}->{'Reverse'}->{$ref};
......@@ -1771,7 +1508,7 @@ sub __getMajorTcClassByRef
if (!defined($globals->{'TcClasses'}->{$interfaceID})) {
return undef;
return;
}
return $globals->{'TcClasses'}->{$interfaceID}->{'Reverse'}->{$ref};
......@@ -1786,7 +1523,7 @@ sub __getRefByMinorTcClass
if (!defined($globals->{'TcClasses'}->{$interfaceID}) || !defined($globals->{'TcClasses'}->{$interfaceID}->{$majorTcClass})) {
return undef;
return;
}
return $globals->{'TcClasses'}->{$interfaceID}->{$majorTcClass}->{'Track'}->{$minorTcClass};
......@@ -1906,11 +1643,7 @@ sub _task_add_to_queue
# Extract the changeset into commands
my $numChanges = 0;
foreach my $cmd ($changeSet->extract()) {
# Rip off path to tc command
shift(@{$cmd});
# Build commandline string
my $cmdStr = join(' ',@{$cmd});
push(@{$globals->{'TaskQueue'}},$cmdStr);
push(@{$globals->{'TaskQueue'}},$cmd);
$numChanges++;
}
......@@ -1919,31 +1652,6 @@ sub _task_add_to_queue
# Send the next command in the task direction
sub _task_put_next
{
my ($heap,$task) = @_;
# Task was busy, this signifies its done, so lets take the next command
if (my $cmdStr = shift(@{$globals->{'TaskQueue'}})) {
# Remove off idle task list if its there
delete($heap->{'idle_tasks'}->{$task->ID});
$task->put($cmdStr);
$logger->log(LOG_DEBUG,"[TC] TASK/%s: Starting '%s' as %s with PID %s",$task->ID,$cmdStr,$task->ID,$task->PID);
$heap->{'task_line_num'}->{$task->ID} = $cmdStr;
# If there is no commands in the queue, set it to idle
} else {
# Set task to idle
$heap->{'idle_tasks'}->{$task->ID} = $task;
}
}
# Queue a task
sub _task_queue
{
......@@ -1966,51 +1674,41 @@ sub _task_run_next
{
my ($kernel,$heap) = @_[KERNEL,HEAP];
# If we already have children processing tasks, don't create another
if (keys %{$heap->{'task_by_wid'}}) {
# Loop with idle tasks ... return if we found one
foreach my $task_id (keys %{$heap->{'idle_tasks'}}) {
_task_put_next($heap,$heap->{'idle_tasks'}->{$task_id});
# XXX: Limit concurrency to 1
last;
}
# XXX: Limit concurrency to 1
# NK: Limit concurrency to 1
return;
}
# Check if we have a task coming off the top of the task queue
if (@{$globals->{'TaskQueue'}}) {
if (my $cmd = shift(@{$globals->{'TaskQueue'}})) {
my $cmdStr = encode_json($cmd);
# Create task
my $task = POE::Wheel::Run->new(
Program => [ '/sbin/tc', '-force', '-batch' ],
Conduit => 'pipe',
StdioFilter => POE::Filter::Line->new( Literal => "\n" ),
Program => $cmd,
StdoutFilter => POE::Filter::Line->new( Literal => "\n" ),
StderrFilter => POE::Filter::Line->new( Literal => "\n" ),
StdoutEvent => '_task_child_stdout',
StderrEvent => '_task_child_stderr',
CloseEvent => '_task_child_close',
StdinEvent => '_task_child_stdin',
ErrorEvent => '_task_child_error',
) or $logger->log(LOG_ERR,"[TC] TASK: Unable to start task");
# ErrorEvent => '_task_child_error',
) or $logger->log(LOG_ERR,"[TC] TASK: Unable to start task: $cmdStr");
# Set task ID
my $task_id = $task->ID;
# Intercept SIGCHLD
$kernel->sig_child($task->PID, "_SIGCHLD");
# Wheel events include the wheel's ID.
$heap->{'task_by_wid'}->{$task_id} = $task;
# Signal events include the process ID.
$heap->{'task_by_pid'}->{$task_id} = $task;
# Set line number to 0
$heap->{'task_line_num'}->{$task_id} = 0;
$heap->{'task_by_pid'}->{$task->PID} = $task;
_task_put_next($heap,$task);
# Build commandline string
$logger->log( LOG_DEBUG, "[TC] TASK/%s: Starting '%s' as %s with PID %s", $task->ID, $cmdStr, $task->ID, $task->PID );
}
}
......@@ -2036,22 +1734,7 @@ sub _task_child_stderr
my $task = $heap->{'task_by_wid'}->{$task_id};
$logger->log(LOG_WARN,"[TC] TASK/%s: STDERR '%s' => %s",$task_id,$heap->{'task_line_num'}->{$task_id},$stdout);
}
# Child flushed to STDIN
sub _task_child_stdin
{
my ($kernel,$heap,$task_id) = @_[KERNEL,HEAP,ARG0];
my $task = $heap->{'task_by_wid'}->{$task_id};
$logger->log(LOG_DEBUG,"[TC] TASK/%s is READY",$task_id);
# And shove another queued command its direction
_task_put_next($heap,$task);
$logger->log(LOG_WARN,"[TC] TASK/%s: STDERR => %s",$task_id,$stdout);
}
......@@ -2075,8 +1758,6 @@ sub _task_child_close
# Remove other references
delete($heap->{'task_by_wid'}->{$task_id});
delete($heap->{'task_by_pid'}->{$task->PID});
delete($heap->{'idle_tasks'}->{$task_id});
delete($heap->{'task_line_num'}->{$task_id});
# Start next one, if there is a next one
if (@{$globals->{'TaskQueue'}}) {
......@@ -2106,8 +1787,6 @@ sub _task_child_error
# Remove other references
delete($heap->{'task_by_wid'}->{$task_id});
delete($heap->{'task_by_pid'}->{$task->PID});
delete($heap->{'idle_tasks'}->{$task_id});
delete($heap->{'task_line_num'}->{$task_id});
# Start next one, if there is a next one
if (@{$globals->{'TaskQueue'}}) {
......@@ -2133,8 +1812,6 @@ sub _task_SIGCHLD
# Remove other references
delete($heap->{'task_by_wid'}->{$task->ID});
delete($heap->{'task_by_pid'}->{$pid});
delete($heap->{'idle_tasks'}->{$task->ID});
delete($heap->{'task_line_num'}->{$task->ID});
}
......@@ -2199,5 +1876,20 @@ sub extract
# Return the list
sub debug
{
my $self = shift;
my @debug = ();
foreach my $item ($self->extract) {
push(@debug,join(' ',@{$item}));
}
return @debug;
}
1;
# vim: ts=4
# OpenTrafficShaper Linux tcstats traffic shaping statistics
# Copyright (C) 2007-2014, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -24,7 +24,7 @@ use warnings;
use POE qw( Wheel::Run Filter::Line );
use POE::Filter::TCStatistics;
use opentrafficshaper::POE::Filter::TCStatistics;
use opentrafficshaper::constants;
use opentrafficshaper::logger;
......@@ -45,7 +45,7 @@ our (@ISA,@EXPORT,@EXPORT_OK);
);
use constant {
VERSION => '0.1.2',
VERSION => '1.0.0',
# How often we tick
TICK_PERIOD => 5,
......@@ -116,6 +116,16 @@ sub plugin_init
# Start the plugin
sub plugin_start
{
my @interfaces = getInterfaces();
my $now = time();
# Initialize last stats
foreach my $interfaceID (@interfaces) {
$globals->{'LastStats'}->{$interfaceID} = $now;
}
$logger->log(LOG_INFO,"[TCSTATS] Started");
}
......@@ -164,30 +174,26 @@ sub _session_tick
# Now
my $now = time();
my @interfaces = getInterfaces();
# Get sorted list of interfaces
my @interfaces = sort { $globals->{'LastStats'}->{$a} <=> $globals->{'LastStats'}->{$b} } getInterfaces();
# Loop with interfaces that need stats
my $interfaceCount = 0;
foreach my $interfaceID (@interfaces) {
my $interface = getInterface($interfaceID);
# Grab the first interface in the list to process
my $interfaceID = shift(@interfaces);
# Skip to next if we've already run for this interface
if (defined($globals->{'LastStats'}->{$interfaceID}) &&
$globals->{'LastStats'}->{$interfaceID} + opentrafficshaper::plugins::statistics::STATISTICS_PERIOD > $now
) {
next;
}
# Check if its old enough to process stats for
if ($now - $globals->{'LastStats'}->{$interfaceID} > opentrafficshaper::plugins::statistics::STATISTICS_PERIOD) {
my $interface = getInterface($interfaceID);
$logger->log(LOG_INFO,"[TCSTATS] Generating stats for '%s'",$interfaceID);
# TC commands to run
my $cmd = [ '/sbin/tc', '-s', 'class', 'show', 'dev', $interface->{'Device'}, 'parent', '1:' ];
my $cmd = [ '/sbin/tc', '-j', '-s', 'class', 'show', 'dev', $interface->{'Device'}, 'parent', '1:' ];
# Create task
my $task = POE::Wheel::Run->new(
Program => $cmd,
StdinFilter => POE::Filter::Line->new(),
StdoutFilter => POE::Filter::TCStatistics->new(),
StdoutFilter => opentrafficshaper::POE::Filter::TCStatistics->new(),
StderrFilter => POE::Filter::Line->new(),
StdoutEvent => '_task_child_stdout',
StderrEvent => '_task_child_stderr',
......@@ -213,17 +219,28 @@ sub _session_tick
$logger->log(LOG_DEBUG,"[TCSTATS] TASK/%s: Starting '%s' as %s with PID %s",$task->ID,$cmdStr,$task->ID,$task->PID);
# Set last time we were run to now
$globals->{'LastStats'}->{$interface} = $now;
$globals->{'LastStats'}->{$interfaceID} = $now;
# NK: Space the stats out, this will cause TICK_PERIOD to elapse before we do another interface
$interfaceCount++;
last;
# Grab next one for below calcs...
$interfaceID = shift(@interfaces);
}
# If we didn't fire up any stats, re-tick
if (!$interfaceCount) {
$kernel->delay('_tick' => TICK_PERIOD);
# Set default tick period
my $tickPeriod = opentrafficshaper::plugins::statistics::STATISTICS_PERIOD;
# Calculate optimal wait time
if (defined($interfaceID)) {
$tickPeriod = opentrafficshaper::plugins::statistics::STATISTICS_PERIOD - ($now - $globals->{'LastStats'}->{$interfaceID});
}
# Make sure wait time is not too long
if ($tickPeriod > opentrafficshaper::plugins::statistics::STATISTICS_PERIOD) {
$tickPeriod = opentrafficshaper::plugins::statistics::STATISTICS_PERIOD;
}
# Make sure wait time is not too short
if ($tickPeriod < opentrafficshaper::plugins::statistics::STATISTICS_PERIOD) {
$tickPeriod = opentrafficshaper::plugins::statistics::STATISTICS_PERIOD;
}
$kernel->delay('_tick' => $tickPeriod);
};
......@@ -333,9 +350,6 @@ sub _task_child_close
delete($heap->{task_by_pid}->{$task->PID});
delete($heap->{task_by_wid}->{$task_id});
delete($heap->{task_data}->{$task_id});
# Fire up next tick
$kernel->delay('_tick' => TICK_PERIOD);
}
......
# OpenTrafficShaper webserver module: configmanager page
# Copyright (C) 2007-2014, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......
# OpenTrafficShaper webserver module: index page
# Copyright (C) 2007-2014, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -48,12 +48,7 @@ sub _catchall
goto END;
}
($res,$content,$opts) = opentrafficshaper::plugins::webserver::pages::statistics::_dashboard($kernel,$globals,
$client_session_id,$request
);
END:
return ($res,$content,$opts);
return (HTTP_TEMPORARY_REDIRECT,"statistics/dashboard");
}
......
# OpenTrafficShaper webserver module: limits page
# Copyright (C) 2007-2014, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -35,6 +35,7 @@ use HTML::Entities;
use HTTP::Status qw(
:constants
);
use NetAddr::IP;
use URI::Escape qw(
uri_escape
);
......@@ -46,9 +47,8 @@ use Storable qw(
use awitpt::util qw(
parseURIQuery
parseFormContent
isUsername
isIPv4
isNumber
isUsername ISUSERNAME_ALLOW_ATSIGN
isNumber ISNUMBER_ALLOW_ZERO
prettyUndef
);
use opentrafficshaper::constants;
......@@ -59,6 +59,7 @@ use opentrafficshaper::plugins::configmanager qw(
getPool
getPoolByName
getPoolShaperState
isPoolOverridden
isPoolReady
getPoolMembers
......@@ -81,7 +82,9 @@ use opentrafficshaper::plugins::configmanager qw(
getTrafficClasses
isTrafficClassIDValid
);
use opentrafficshaper::util qw(
isIPv46 isIPv46CIDR
);
# Sidebar menu options for this module
......@@ -213,9 +216,9 @@ EOF
# if ($pool->{'Status'} eq 'conflict') {
# $icons .= '<span class="glyphicon glyphicon-random" />';
# }
# if ($pool->{'Status'} eq 'conflict') {
# $icons .= '<span class="glyphicon glyphicon-edit" />';
# }
if (isPoolOverridden($pool->{'ID'})) {
$icons .= '<span class="glyphicon glyphicon-edit" />';
}
my $urlStatsPool = sprintf('/statistics/by-pool?pool=%s',uri_escape("$pool->{'InterfaceGroupID'}:$pool->{'Name'}"));
my $urlPoolEdit = sprintf('/limits/pool-edit?pid=%s',uri_escape($pool->{'ID'}));
......@@ -256,10 +259,10 @@ EOF
$content .=<<EOF;
</tbody>
</table>
<span class="glyphicon glyphicon-time" /> - Processing,
<span class="glyphicon glyphicon-edit" /> - Override,
<span class="glyphicon glyphicon-import" /> - Being Added,
<span class="glyphicon glyphicon-trash" /> - Being Removed,
<span class="glyphicon glyphicon-time" /> - Processing <br/>
<span class="glyphicon glyphicon-edit" /> - Override <br/>
<span class="glyphicon glyphicon-import" /> - Being Added <br/>
<span class="glyphicon glyphicon-trash" /> - Being Removed <br/>
<span class="glyphicon glyphicon-random" /> - Conflicts
EOF
......@@ -373,7 +376,7 @@ sub pool_addedit
# Check POST data
my $name;
if (!defined($name = isUsername($formData->{'Name'}))) {
if (!defined($name = isUsername($formData->{'Name'},ISUSERNAME_ALLOW_ATSIGN))) {
push(@errors,"Name is not valid");
}
my $interfaceGroupID;
......@@ -407,7 +410,7 @@ sub pool_addedit
my $expires = 0;
if (defined($formData->{'Expires'}) && $formData->{'Expires'} ne "") {
if (!defined($expires = isNumber($formData->{'Expires'}))) {
if (!defined($expires = isNumber($formData->{'Expires'},ISNUMBER_ALLOW_ZERO))) {
push(@errors,"Expires value is not valid");
# Check the modifier
} else {
......@@ -807,7 +810,7 @@ EOF
my $poolFriendlyNameEncoded = encode_entities($poolFriendlyName);
my $poolNameEncoded = encode_entities($pool->{'Name'});
my $urlPoolMemberAdd = sprintf('/limits/poolmember-add?pid=%s',uri_escape($pool->{'ID'}));
my $urlPoolMemberAdd = sprintf('poolmember-add?pid=%s',uri_escape($pool->{'ID'}));
# Menu
$customMenu = [
......@@ -835,6 +838,7 @@ EOF
<th>Friendly Name</th>
<th>Username</th>
<th>IP</th>
<th>NAT</th>
<th>Created</th>
<th>Updated</th>
<th>Expires</th>
......@@ -859,6 +863,10 @@ EOF
my $poolMemberUsernameEncoded = encode_entities($poolMember->{'Username'});
my $poolMemberIPEncoded = encode_entities($poolMember->{'IPAddress'});
my $poolMemberIPNATEncoded = encode_entities(
(defined($poolMember->{'IPNATAddress'}) && $poolMember->{'IPNATAddress'} ne "") ? $poolMember->{'IPNATAddress'} : '-none-'
);
my $natIcons = (defined($poolMember->{'IPNATInbound'}) && $poolMember->{'IPNATInbound'}) ? '<span class="glyphicon glyphicon-resize-vertical" />' : '';
my $poolMemberCreatedStr = encode_entities(($poolMember->{'Created'} > 0) ?
DateTime->from_epoch( epoch => $poolMember->{'Created'} )->iso8601() : '-never-');
......@@ -867,9 +875,11 @@ EOF
my $poolMemberExpiresStr = encode_entities(($poolMember->{'Expires'} > 0) ?
DateTime->from_epoch( epoch => $poolMember->{'Expires'} )->iso8601() : '-never-');
my $poolMemberShaperState = getPoolMemberShaperState($poolMember->{'ID'});
# Display relevant icons depending on pool status
my $icons = "";
if (!(getPoolMemberShaperState($poolMember->{'ID'}) & SHAPER_LIVE)) {
if (!($poolMemberShaperState & SHAPER_LIVE)) {
$icons .= '<span class="glyphicon glyphicon-time" />';
}
if ($poolMember->{'Status'} == CFGM_NEW) {
......@@ -878,12 +888,9 @@ EOF
if ($poolMember->{'Status'} == CFGM_OFFLINE) {
$icons .= '<span class="glyphicon glyphicon-trash" />';
}
# if ($poolMember->{'Status'} eq 'conflict') {
# $icons .= '<span class="glyphicon glyphicon-random" />';
# }
# if ($pool->{'Status'} eq 'conflict') {
# $icons .= '<span class="glyphicon glyphicon-edit" />';
# }
if ($poolMemberShaperState & SHAPER_CONFLICT) {
$icons .= '<span class="glyphicon glyphicon-random" />';
}
my $urlPoolMemberEdit = sprintf('/limits/poolmember-edit?pmid=%s',uri_escape($poolMember->{'ID'}));
my $urlPoolMemberRemove = sprintf('/limits/poolmember-remove?pmid=%s',uri_escape($poolMember->{'ID'}));
......@@ -894,6 +901,7 @@ EOF
<td>$poolMemberFriendlyNameEncoded</td>
<td>$poolMemberUsernameEncoded</td>
<td>$poolMemberIPEncoded</td>
<td>$natIcons$poolMemberIPNATEncoded</td>
<td>$poolMemberCreatedStr</td>
<td>$poolMemberUpdatedStr</td>
<td>$poolMemberExpiresStr</td>
......@@ -918,10 +926,10 @@ EOF
$content .=<<EOF;
</tbody>
</table>
<span class="glyphicon glyphicon-time" /> - Processing,
<span class="glyphicon glyphicon-edit" /> - Override,
<span class="glyphicon glyphicon-import" /> - Being Added,
<span class="glyphicon glyphicon-trash" /> - Being Removed,
<span class="glyphicon glyphicon-time" /> - Processing <br/>
<span class="glyphicon glyphicon-edit" /> - Override <br/>
<span class="glyphicon glyphicon-import" /> - Being Added <br/>
<span class="glyphicon glyphicon-trash" /> - Being Removed <br/>
<span class="glyphicon glyphicon-random" /> - Conflicts
EOF
......@@ -946,7 +954,7 @@ sub poolmember_addedit
# Items for our form...
my @formElements = qw(
FriendlyName
Username IPAddress
Username IPAddress IPNATAddress IPNATInbound
MatchPriorityID
Expires inputExpires.modifier
Notes
......@@ -964,6 +972,7 @@ sub poolmember_addedit
# Title of the form, by default its an add form
my $formType = "Add";
my $formNoEdit = "";
my $checkboxNoEdit = "";
# Form data
my $formData;
# Pool
......@@ -1000,10 +1009,10 @@ sub poolmember_addedit
if (defined($form->{'cancel'})) {
# If the pool member is defined, rededirect to pool member list
if (defined($poolMember)) {
return (HTTP_TEMPORARY_REDIRECT,sprintf('/limits/poolmember-list?pid=%s',$pool->{'IID'}));
return (HTTP_TEMPORARY_REDIRECT,sprintf('/limits/poolmember-list?pid=%s',$pool->{'ID'}));
# Do same for pool
} elsif (defined($pool)) {
return (HTTP_TEMPORARY_REDIRECT,sprintf('/limits/poolmember-list?pid=%s',$pool->{'IID'}));
return (HTTP_TEMPORARY_REDIRECT,sprintf('/limits/poolmember-list?pid=%s',$pool->{'ID'}));
}
return (HTTP_TEMPORARY_REDIRECT,'/limits');
......@@ -1025,6 +1034,7 @@ sub poolmember_addedit
$formType = "Edit";
$formNoEdit = "readonly";
$checkboxNoEdit = "disabled";
}
# Maybe we were given a pool override key as a parameter? this would be an edit form
......@@ -1039,6 +1049,7 @@ sub poolmember_addedit
# Lastly if we were given a key, this is actually an edit
$formType = "Edit";
$formNoEdit = "readonly";
$checkboxNoEdit = "disabled";
# Woops ... no query string?
} elsif (!defined($pool)) {
......@@ -1052,13 +1063,27 @@ sub poolmember_addedit
# Check POST data
my $username;
if (!defined($username = isUsername($formData->{'Username'}))) {
if (!defined($username = isUsername($formData->{'Username'},ISUSERNAME_ALLOW_ATSIGN))) {
push(@errors,"Username is not valid");
}
my $ipAddress;
if (!defined($ipAddress = isIPv4($formData->{'IPAddress'}))) {
if (!defined($ipAddress = isIPv46CIDR($formData->{'IPAddress'}))) {
push(@errors,"IP address is not valid");
}
my $ipNATAddress;
if (defined($formData->{'IPNATAddress'}) && $formData->{'IPNATAddress'} ne "") {
if (!defined($ipNATAddress = isIPv46($formData->{'IPNATAddress'}))) {
push(@errors,"IP NAT address is not valid");
}
}
my $ipNATInbound;
if (defined($formData->{'IPNATInbound'}) && $formData->{'IPNATInbound'} ne "") {
if (defined($ipNATAddress)) {
$ipNATInbound = "yes";
} else {
push(@errors,"Cannot NAT inbound traffic if no NAT address is set");
}
}
my $matchPriorityID;
if (!defined($matchPriorityID = isMatchPriorityIDValid($formData->{'MatchPriorityID'}))) {
push(@errors,"Match priority is not valid");
......@@ -1076,7 +1101,7 @@ sub poolmember_addedit
my $expires = 0;
if (defined($formData->{'Expires'}) && $formData->{'Expires'} ne "") {
if (!defined($expires = isNumber($formData->{'Expires'}))) {
if (!defined($expires = isNumber($formData->{'Expires'},ISNUMBER_ALLOW_ZERO))) {
push(@errors,"Expires value is not valid");
# Check the modifier
} else {
......@@ -1113,6 +1138,8 @@ sub poolmember_addedit
'FriendlyName' => $friendlyName,
'Username' => $username,
'IPAddress' => $ipAddress,
'IPNATAddress' => $ipNATAddress,
'IPNATInbound' => $ipNATInbound,
'GroupID' => 1,
'MatchPriorityID' => $matchPriorityID,
'Expires' => $expires,
......@@ -1131,10 +1158,12 @@ sub poolmember_addedit
$kernel->post("configmanager" => $cEvent => $poolMemberData);
$logger->log(LOG_INFO,'[WEBSERVER/POOLMEMBER] Account: %s, User: %s, IP: %s, Group: %s, MatchPriority: %s, Pool: %s',
$logger->log(LOG_INFO,'[WEBSERVER/POOLMEMBER] Account: %s, User: %s, IP: %s, NAT: %s (inbound: %s), Group: %s, MatchPriority: %s, Pool: %s',
$formType,
prettyUndef($username),
prettyUndef($ipAddress),
prettyUndef($ipNATAddress),
prettyUndef($ipNATInbound),
prettyUndef(undef),
prettyUndef($matchPriorityID),
prettyUndef($pool->{'ID'}),
......@@ -1216,6 +1245,10 @@ EOF
encode_entities($expiresModifiers->{$expireModifier}).'</option>';
}
# If we have IPNATInbound set, we need to set it to checked
if (defined($formData->{'IPNATInbound'}) && $formData->{'IPNATInbound'} ne "") {
$formData->{'IPNATInbound'} = "checked";
}
# Blank expires if its 0
if (defined($formData->{'Expires'}) && $formData->{'Expires'} eq "0") {
$formData->{'Expires'} = "";
......@@ -1252,6 +1285,16 @@ EOF
</div>
</div>
</div>
<div class="form-group">
<label for="IPNATAddress" class="col-md-2 control-label">NAT Address</label>
<div class="row">
<div class="col-md-4 input-group">
<input name="IPNATAddress" type="text" placeholder="NAT Address" class="form-control"
value="$formData->{'IPNATAddress'}" $formNoEdit />
<input name="IPNATInbound" type="checkbox" $formData->{'IPNATInbound'} $checkboxNoEdit /> NAT Inbound
</div>
</div>
</div>
<div class="form-group">
<label for="MatchPriorityID" class="col-md-2 control-label">Match Priority</label>
<div class="row">
......@@ -1401,7 +1444,7 @@ sub limit_add
# Items for our form...
my @formElements = qw(
FriendlyName
Username IPAddress
Username IPAddress IPNATAddress IPNATInbound
InterfaceGroupID
MatchPriorityID
TrafficClassID
......@@ -1447,13 +1490,27 @@ sub limit_add
# Check POST data
my $username;
if (!defined($username = isUsername($formData->{'Username'}))) {
if (!defined($username = isUsername($formData->{'Username'},ISUSERNAME_ALLOW_ATSIGN))) {
push(@errors,"Username is not valid");
}
my $ipAddress;
if (!defined($ipAddress = isIPv4($formData->{'IPAddress'}))) {
if (!defined($ipAddress = isIPv46CIDR($formData->{'IPAddress'}))) {
push(@errors,"IP address is not valid");
}
my $ipNATAddress;
if (defined($formData->{'IPNATAddress'}) && $formData->{'IPNATAddress'} ne "") {
if (!defined($ipNATAddress = isIPv46($formData->{'IPNATAddress'}))) {
push(@errors,"NAT address is not valid");
}
}
my $ipNATInbound;
if (defined($formData->{'IPNATInbound'}) && $formData->{'IPNATInbound'} ne "") {
if (defined($ipNATAddress)) {
$ipNATInbound = "yes";
} else {
push(@errors,"Cannot NAT inbound traffic if no NAT address is set");
}
}
my $interfaceGroupID;
if (!defined($interfaceGroupID = isInterfaceGroupIDValid($formData->{'InterfaceGroupID'}))) {
push(@errors,"Interface group is not valid");
......@@ -1479,7 +1536,7 @@ sub limit_add
my $expires = 0;
if (defined($formData->{'Expires'}) && $formData->{'Expires'} ne "") {
if (!defined($expires = isNumber($formData->{'Expires'}))) {
if (!defined($expires = isNumber($formData->{'Expires'},ISNUMBER_ALLOW_ZERO))) {
push(@errors,"Expires value is not valid");
# Check the modifier
} else {
......@@ -1516,6 +1573,8 @@ sub limit_add
'FriendlyName' => $friendlyName,
'Username' => $username,
'IPAddress' => $ipAddress,
'IPNATAddress' => $ipNATAddress,
'IPNATInbound' => $ipNATInbound,
'GroupID' => 1,
'InterfaceGroupID' => $interfaceGroupID,
'MatchPriorityID' => $matchPriorityID,
......@@ -1534,10 +1593,12 @@ sub limit_add
$kernel->post("configmanager" => "limit_add" => $limit);
$logger->log(LOG_INFO,"[WEBSERVER/LIMITS] New User: %s, IP: %s, Group: %s, InterfaceGroup: %s, MatchPriority: %s, ".
$logger->log(LOG_INFO,"[WEBSERVER/LIMITS] New User: %s, IP: %s, NAT: %s (inbound: %s), Group: %s, InterfaceGroup: %s, MatchPriority: %s, ".
"Class: %s, Limits: %s/%s, Burst: %s/%s",
prettyUndef($username),
prettyUndef($ipAddress),
prettyUndef($ipNATAddress),
prettyUndef($ipNATInbound),
prettyUndef(undef),
prettyUndef($interfaceGroupID),
prettyUndef($matchPriorityID),
......@@ -1640,6 +1701,11 @@ EOF
encode_entities($expiresModifiers->{$expireModifier}).'</option>';
}
# If we have IPNATInbound set, we need to set it to checked
if (defined($formData->{'IPNATInbound'}) && $formData->{'IPNATInbound'} ne "") {
$formData->{'IPNATInbound'} = "checked";
}
# Blank expires if its 0
if (defined($formData->{'Expires'}) && $formData->{'Expires'} eq "0") {
$formData->{'Expires'} = "";
......@@ -1676,6 +1742,16 @@ EOF
</div>
</div>
</div>
<div class="form-group">
<label for="IPNATAddress" class="col-md-2 control-label">NAT Address</label>
<div class="row">
<div class="col-md-4 input-group">
<input name="IPNATAddress" type="text" placeholder="NAT Address" class="form-control"
value="$formData->{'IPNATAddress'}" />
<input name="IPNATInbound" type="checkbox" $formData->{'IPNATInbound'} /> NAT Inbound
</div>
</div>
</div>
<div class="form-group">
<label for="InterfaceGroupID" class="col-md-2 control-label">Interface Group</label>
<div class="row">
......@@ -1833,6 +1909,7 @@ EOF
my $poolOverridePoolNameEncoded = encode_entities(prettyUndef($poolOverride->{'PoolName'}));
my $poolOverrideUsernameEncoded = encode_entities(prettyUndef($poolOverride->{'Username'}));
my $poolOverrideIPAddressEncoded = encode_entities(prettyUndef($poolOverride->{'IPAddress'}));
my $poolOverrideIPNATAddressEncoded = encode_entities(prettyUndef($poolOverride->{'IPNATAddress'}));
my $poolOverrideExpiresStr = encode_entities(
($poolOverride->{'Expires'} > 0) ?
DateTime->from_epoch( epoch => $poolOverride->{'Expires'} )->iso8601() : '-never-'
......@@ -1851,8 +1928,8 @@ EOF
sprintf('%s/%s',prettyUndef($poolOverride->{'TxLimit'}),prettyUndef($poolOverride->{'RxLimit'}))
);
my $urlPoolOverrideEdit = sprintf('/limits/pool-override-edit?oid=',encode_entities($poolOverride->{'ID'}));
my $urlPoolOverrideRemove = sprintf('/limits/pool-override-remove?oid=',encode_entities($poolOverride->{'ID'}));
my $urlPoolOverrideEdit = sprintf('/limits/pool-override-edit?poid=%s',encode_entities($poolOverride->{'ID'}));
my $urlPoolOverrideRemove = sprintf('/limits/pool-override-remove?poid=%s',encode_entities($poolOverride->{'ID'}));
$content .= <<EOF;
<tr>
......@@ -1861,6 +1938,7 @@ EOF
<td>$poolOverridePoolNameEncoded</td>
<td>$poolOverrideUsernameEncoded</td>
<td>$poolOverrideIPAddressEncoded</td>
<td>$poolOverrideIPNATAddressEncoded</td>
<td>$poolOverrideExpiresStr</td>
<td><span class="glyphicon glyphicon-arrow-right" /></td>
<td class="align-center">$poolOverrideTrafficClassStr</td>
......@@ -1910,7 +1988,7 @@ sub pool_override_addedit
# Items for our form...
my @formElements = qw(
FriendlyName
PoolName Username IPAddress
PoolName Username IPAddress IPNATAddress IPNATInbound
TrafficClassID
TxCIR TxLimit
RxCIR RxLimit
......@@ -1934,6 +2012,7 @@ sub pool_override_addedit
# Title of the form, by default its an add form
my $formType = "Add";
my $formNoEdit = "";
my $checkboxNoEdit = "";
# Form data
my $formData;
# If we have a pool override, this is where its kept
......@@ -1977,6 +2056,7 @@ sub pool_override_addedit
$formType = "Edit";
$formNoEdit = "readonly";
$checkboxNoEdit = "disabled";
}
# A GET would indicate that a pool override ID was passed normally
......@@ -1996,6 +2076,7 @@ sub pool_override_addedit
$formType = "Edit";
$formNoEdit = "readonly";
$checkboxNoEdit = "disabled";
# Woops ... no query string?
} elsif (keys %{$queryParams} > 0) {
......@@ -2011,12 +2092,46 @@ sub pool_override_addedit
push(@errors,"Friendly name must be specified");
}
# Make sure we have at least a pool name, username or IP address
my $poolName = isUsername($formData->{'PoolName'});
my $username = isUsername($formData->{'Username'});
my $ipAddress = isIPv4($formData->{'IPAddress'});
# Check the pool name is valid if it was specified
my $poolName;
if (defined($formData->{'PoolName'}) && $formData->{'PoolName'} ne "") {
if (!defined($poolName = isUsername($formData->{'PoolName'},ISUSERNAME_ALLOW_ATSIGN))) {
push(@errors,"Pool name is not valid");
}
}
# Next check the username
my $username;
if (defined($formData->{'Username'}) && $formData->{'Username'} ne "") {
if (!defined($username = isUsername($formData->{'Username'},ISUSERNAME_ALLOW_ATSIGN))) {
push(@errors,"Username is not valid");
}
}
# Then the IP
my $ipAddress;
if (defined($formData->{'IPAddress'}) && $formData->{'IPAddress'} ne "") {
if (!defined($ipAddress = isIPv46CIDR($formData->{'IPAddress'}))) {
push(@errors,"IP address is not valid");
}
}
# And NAT
my $ipNATAddress;
if (defined($formData->{'IPNATAddress'}) && $formData->{'IPNATAddress'} ne "") {
if (!defined($ipNATAddress = isIPv46($formData->{'IPNATAddress'}))) {
push(@errors,"NAT address is not valid");
}
}
my $ipNATInbound;
if (defined($formData->{'IPNATInbound'}) && $formData->{'IPNATInbound'} ne "") {
if (defined($ipNATAddress)) {
$ipNATInbound = "yes";
} else {
push(@errors,"Cannot NAT inbound traffic if no NAT address is set");
}
}
# Then confirm we have at least one of the above
if (!defined($poolName) && !defined($username) && !defined($ipAddress)) {
push(@errors,"A pool name and/or IP address and/or Username must be specified");
push(@errors,"At least a valid pool name, username or IP address must be specified to match");
}
# If the traffic class is ticked, process it
......@@ -2058,12 +2173,12 @@ sub pool_override_addedit
!defined($txCIR) && !defined($txLimit) &&
!defined($rxCIR) && !defined($rxLimit)
) {
push(@errors,"Something must be specified to pool override");
push(@errors,"Something must be specified to override");
}
my $expires = 0;
if (defined($formData->{'Expires'}) && $formData->{'Expires'} ne "") {
if (!defined($expires = isNumber($formData->{'Expires'}))) {
if (!defined($expires = isNumber($formData->{'Expires'},ISNUMBER_ALLOW_ZERO))) {
push(@errors,"Expires value is not valid");
# Check the modifier
} else {
......@@ -2102,6 +2217,7 @@ sub pool_override_addedit
'PoolName' => $poolName,
'Username' => $username,
'IPAddress' => $ipAddress,
# 'IPNATAddress' => $ipNATAddress,
# 'GroupID' => 1,
'TrafficClassID' => $trafficClassID,
'TxCIR' => $txCIR,
......@@ -2123,11 +2239,13 @@ sub pool_override_addedit
$kernel->post("configmanager" => $cEvent => $poolOverrideData);
$logger->log(LOG_INFO,"[WEBSERVER/POOL-OVERRIDE/ADD] Pool: %s, User: %s, IP: %s, Group: %s, Class: %s, Limits: %s/%s, ".
$logger->log(LOG_INFO,"[WEBSERVER/POOL-OVERRIDE/ADD] Pool: %s, User: %s, IP: %s, NAT: %s (inbound: %s), Group: %s, Class: %s, Limits: %s/%s, ".
"Burst: %s/%s",
prettyUndef($poolName),
prettyUndef($username),
prettyUndef($ipAddress),
prettyUndef($ipNATAddress),
prettyUndef($ipNATInbound),
"",
prettyUndef($trafficClassID),
prettyUndef($txCIR),
......@@ -2246,6 +2364,16 @@ EOF
</div>
</div>
</div>
<div class="form-group">
<label for="IPNATAddress" class="col-md-2 control-label">NAT Address</label>
<div class="row">
<div class="col-md-4">
<input name="IPNATAddress" type="text" placeholder="- - - override not implemented - - -" class="form-control"
value="$formData->{'IPNATAddress'}" $formNoEdit />
<input name="IPNATInbound" type="checkbox" $formData->{'IPNATInbound'} $checkboxNoEdit /> NAT Inbound
</div>
</div>
</div>
<div class="form-group">
<label for="TrafficClassID" class="col-md-2 control-label">Traffic Class</label>
......@@ -2398,7 +2526,7 @@ EOF
# Check if its a success
if ($form->{'confirm'}->{'value'} eq "Yes") {
# Post the removal
$kernel->post("configmanager" => "pool-override_remove" => $poolOverride->{'ID'});
$kernel->post("configmanager" => "pool_override_remove" => $poolOverride->{'ID'});
}
return (HTTP_TEMPORARY_REDIRECT,'/limits/pool-override-list');
}
......
# OpenTrafficShaper webserver module: index page
# Copyright (C) 2007-2014, AllWorldIT
# Copyright (C) 2007-2023, AllWorldIT
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -77,7 +77,7 @@ sub _catchall
}
# Stat file first of all
my $stat = stat($filename);
my $stat = stat($filename);
if (!$stat) {
$logger->log(LOG_WARN,"[WEBSERVER/STATIC] Unable to stat '%s': %s",$resource,$!);
return;
......@@ -108,7 +108,7 @@ sub _catchall
$response->header('Last-Modified', HTTP::Date::time2str($stat->mtime));
# Open file handle
if (!open(FH, "< $filename")) {
if (!open(FH, "< $filename")) {
$logger->log(LOG_WARN,"[WEBSERVER/STATIC] Unable to open '%s': %s",$resource,$!);
}
# Set to binary mode
......@@ -125,7 +125,7 @@ sub _catchall
# Set content
$response->content($buffer);
return $response;
return $response;
}
......