configmanager.pm 99 KB
Newer Older
1
# OpenTrafficShaper configuration manager
2
# Copyright (C) 2007-2015, AllWorldIT
3
#
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.



package opentrafficshaper::plugins::configmanager;

use strict;
use warnings;

use POE;
Nigel Kukard's avatar
Nigel Kukard committed
25
26
27
28
29
30
31
use Storable qw(
	dclone
);
use Time::HiRes qw(
	gettimeofday
	tv_interval
);
32

Nigel Kukard's avatar
Nigel Kukard committed
33
34
35
use awitpt::util qw(
	isNumber ISNUMBER_ALLOW_ZERO
	isIPv4
Nigel Kukard's avatar
Nigel Kukard committed
36
	isUsername ISUSERNAME_ALLOW_ATSIGN
Nigel Kukard's avatar
Nigel Kukard committed
37
38
39
40
41

	prettyUndef

	getHashChanges
);
42
use opentrafficshaper::constants;
43
44
45
46
47
48
49
50
51
52
53
use opentrafficshaper::logger;



# Exporter stuff
require Exporter;
our (@ISA,@EXPORT,@EXPORT_OK);
@ISA = qw(Exporter);
@EXPORT = qw(
);
@EXPORT_OK = qw(
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
	createGroup
	isGroupIDValid

	createTrafficClass
	getTrafficClass
	getTrafficClasses
	getInterfaceTrafficClass
	getAllTrafficClasses
	isTrafficClassIDValid

	isInterfaceIDValid

	createInterface
	createInterfaceClass
	createInterfaceGroup
69
70
71
72
73
	changeInterfaceTrafficClass
	getEffectiveInterfaceTrafficClass2
	isInterfaceTrafficClassValid
	setInterfaceTrafficClassShaperState
	unsetInterfaceTrafficClassShaperState
74

Nigel Kukard's avatar
Nigel Kukard committed
75
76
	createLimit

77
78
	getPoolOverride
	getPoolOverrides
Nigel Kukard's avatar
Nigel Kukard committed
79
80

	createPool
81
82
	removePool
	changePool
Nigel Kukard's avatar
Nigel Kukard committed
83
84
	getPools
	getPool
85
	getPoolByName
Nigel Kukard's avatar
Nigel Kukard committed
86
87
88
89
90
91
92
93
	getPoolTxInterface
	getPoolRxInterface
	getPoolTrafficClassID
	setPoolAttribute
	getPoolAttribute
	removePoolAttribute
	getPoolShaperState
	setPoolShaperState
94
	unsetPoolShaperState
Nigel Kukard's avatar
Nigel Kukard committed
95
	isPoolIDValid
96
	isPoolOverridden
Nigel Kukard's avatar
Nigel Kukard committed
97
98
99
100
	isPoolReady

	getEffectivePool

101
102
103
	createPoolMember
	removePoolMember
	changePoolMember
Nigel Kukard's avatar
Nigel Kukard committed
104
105
	getPoolMembers
	getPoolMember
106
107
	getPoolMemberByUsernameIP
	getAllPoolMembersByInterfaceGroupIP
Nigel Kukard's avatar
Nigel Kukard committed
108
109
	getPoolMemberMatchPriority
	setPoolMemberShaperState
110
	unsetPoolMemberShaperState
Nigel Kukard's avatar
Nigel Kukard committed
111
112
113
114
115
116
117
118
119
120
121
122
123
124
	getPoolMemberShaperState
	getPoolMemberMatchPriority
	setPoolMemberAttribute
	getPoolMemberAttribute
	removePoolMemberAttribute
	isPoolMemberReady

	getTrafficClassPriority

	getTrafficDirection

	getInterface
	getInterfaces
	getInterfaceDefaultPool
125
	getInterfaceLimit
Nigel Kukard's avatar
Nigel Kukard committed
126
127
128
129
130
131
	getInterfaceGroup
	getInterfaceGroups
	isInterfaceGroupIDValid

	getMatchPriorities
	isMatchPriorityIDValid
132
133
134
);

use constant {
135
	VERSION => '0.2.3',
136

137
	# After how long does a limit get removed if its's deemed offline
138
	TIMEOUT_EXPIRE_OFFLINE => 300,
139
140

	# How often our config check ticks
141
	TICK_PERIOD => 5,
142

143
	# Intervals for periodic actions
144
	CLEANUP_INTERVAL => 300,
145
	STATE_SYNC_INTERVAL => 300,
146
147
};

Nigel Kukard's avatar
Nigel Kukard committed
148
149
150

# Mandatory pool attributes
sub POOL_REQUIRED_ATTRIBUTES {
151
	qw(
152
		Name
Nigel Kukard's avatar
Nigel Kukard committed
153
		InterfaceGroupID
154
		TrafficClassID TxCIR RxCIR
Nigel Kukard's avatar
Nigel Kukard committed
155
156
157
158
159
160
161
162
		Source
	)
}

# Pool attributes that can be changed
sub POOL_CHANGE_ATTRIBUTES {
	qw(
		FriendlyName
163
		TrafficClassID TxCIR RxCIR TxLimit RxLimit
Nigel Kukard's avatar
Nigel Kukard committed
164
165
166
167
168
169
170
171
		Expires
		Notes
	)
}

# Pool persistent attributes
sub POOL_PERSISTENT_ATTRIBUTES {
	qw(
172
173
		ID
		Name
Nigel Kukard's avatar
Nigel Kukard committed
174
175
		FriendlyName
		InterfaceGroupID
Nigel Kukard's avatar
Nigel Kukard committed
176
		TrafficClassID TxCIR RxCIR TxLimit RxLimit
Nigel Kukard's avatar
Nigel Kukard committed
177
178
179
180
181
182
		Expires
		Source
		Notes
	)
}

183
184
185
186
187
188
189
190
191
192
193
194
195
# Class attributes that can be changed (overridden)
sub CLASS_CHANGE_ATTRIBUTES {
	qw(
		CIR Limit
	)
}

# Class attributes that can be overidden
sub CLASS_OVERRIDE_CHANGESET_ATTRIBUTES {
	qw(
		CIR Limit
	)
}
Nigel Kukard's avatar
Nigel Kukard committed
196

197
198
199
200
201
202
203
204
205
# Class attributes that can be overidden
sub CLASS_OVERRIDE_PERSISTENT_ATTRIBUTES {
	qw(
		InterfaceID
		TrafficClassID
		CIR Limit
	)
}

Nigel Kukard's avatar
Nigel Kukard committed
206
207
208
209
210
211
212
# Mandatory pool member attributes
sub POOLMEMBER_REQUIRED_ATTRIBUTES {
	qw(
		Username IPAddress
		MatchPriorityID
		PoolID
		GroupID
213
214
215
216
		Source
	)
}

Nigel Kukard's avatar
Nigel Kukard committed
217
218
219
220
221
222
223
224
225
226
227
# Pool member attributes that can be changed
sub POOLMEMBER_CHANGE_ATTRIBUTES {
	qw(
		FriendlyName
		Expires
		Notes
	)
}

# Pool member persistent attributes
sub POOLMEMBER_PERSISTENT_ATTRIBUTES {
228
	qw(
Nigel Kukard's avatar
Nigel Kukard committed
229
230
231
232
233
234
235
236
		FriendlyName
		Username IPAddress
		MatchPriorityID
		PoolID
		GroupID
		Source
		Expires
		Notes
237
238
239
	)
}

Nigel Kukard's avatar
Nigel Kukard committed
240
241
242

# Mandatory limit attributes
sub LIMIT_REQUIRED_ATTRIBUTES {
243
	qw(
Nigel Kukard's avatar
Nigel Kukard committed
244
		Username IPAddress
Nigel Kukard's avatar
Nigel Kukard committed
245
		InterfaceGroupID MatchPriorityID
Nigel Kukard's avatar
Nigel Kukard committed
246
		GroupID
247
		TrafficClassID	TxCIR RxCIR
248
249
250
251
		Source
	)
}

Nigel Kukard's avatar
Nigel Kukard committed
252

253
254
# Pool override match attributes, one is required
sub POOL_OVERRIDE_MATCH_ATTRIBUTES {
255
	qw(
256
		PoolName Username IPAddress
257
258
259
		GroupID
	)
}
Nigel Kukard's avatar
Nigel Kukard committed
260

261
262
# Pool override attributes
sub POOL_OVERRIDE_ATTRIBUTES {
Nigel Kukard's avatar
Nigel Kukard committed
263
264
	qw(
		FriendlyName
265
		PoolName Username IPAddress GroupID
266
		TrafficClassID TxCIR RxCIR TxLimit RxLimit
Nigel Kukard's avatar
Nigel Kukard committed
267
268
269
270
271
		Expires
		Notes
	)
}

272
273
# Pool override attributes that can be changed
sub POOL_OVERRIDE_CHANGE_ATTRIBUTES {
Nigel Kukard's avatar
Nigel Kukard committed
274
275
	qw(
		FriendlyName
276
		TrafficClassID TxCIR RxCIR TxLimit RxLimit
Nigel Kukard's avatar
Nigel Kukard committed
277
278
279
280
281
		Expires
		Notes
	)
}

282
283
# Pool override changeset attributes
sub POOL_OVERRIDE_CHANGESET_ATTRIBUTES {
284
	qw(
285
		TrafficClassID TxCIR RxCIR TxLimit RxLimit
286
287
	)
}
Nigel Kukard's avatar
Nigel Kukard committed
288

289
290
# Pool override attributes supported for persistent storage
sub POOL_OVERRIDE_PERSISTENT_ATTRIBUTES {
291
	qw(
292
		FriendlyName
293
		PoolName Username IPAddress GroupID
294
		TrafficClassID TxCIR RxCIR TxLimit RxLimit
295
		Notes
296
297
298
		Expires Created
		Source
		LastUpdate
299
300
	)
}
Nigel Kukard's avatar
Nigel Kukard committed
301

302

303

304
305
306
307
308

# Plugin info
our $pluginInfo = {
	Name => "Config Manager",
	Version => VERSION,
309

Nigel Kukard's avatar
Nigel Kukard committed
310
311
	Init => \&plugin_init,
	Start => \&plugin_start,
312
313
314
};


315
# This modules globals
316
my $globals;
317
# System logger
318
319
my $logger;

320
# Configuration for this plugin
321
our $config = {
Nigel Kukard's avatar
Nigel Kukard committed
322
323
324
325
326
327
	# Match priorities
	'match_priorities' => {
		1 => 'First',
		2 => 'Default',
		3 => 'Fallthrough'
	},
328
329
	# State file
	'statefile' => '/var/lib/opentrafficshaper/configmanager.state',
330
331
};

332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349

#
# GROUPS - pool members are linked to groups
#
# Attributes:
#  * ID
#  * Name
#
# $globals->{'Groups'}

#
# CLASSES
#
# Attributes:
#  * ID
#  * Name
#
# $globals->{'TrafficClasses'}
350

Nigel Kukard's avatar
Nigel Kukard committed
351

352
#
Nigel Kukard's avatar
Nigel Kukard committed
353
# INTERFACES
354
#
355
356
357
358
359
360
361
# Attributes:
#  * ID
#  * Name
#  * Interface
#  * Limit
#
# $globals->{'Interfaces'}
Nigel Kukard's avatar
Nigel Kukard committed
362
363
364
365
366
367


#
# POOLS
#
# Parameters:
368
# * ID
Nigel Kukard's avatar
Nigel Kukard committed
369
370
# * FriendlyName
#    - Used for display purposes
371
# * Name
Nigel Kukard's avatar
Nigel Kukard committed
372
#    - Unix timestamp when this entry expires, 0 if never
373
374
# * TrafficClassID
#    - Traffic class ID
Nigel Kukard's avatar
Nigel Kukard committed
375
376
# * InterfaceGroupID
#    - Interface group this pool is attached to
377
# * TxCIR
378
#    - Traffic limit in kbps
379
# * RxCIR
380
#    - Traffic limit in kbps
381
# * TxLimit
382
#    - Traffic bursting limit in kbps
383
# * RxLimit
384
#    - Traffic bursting limit in kbps
Nigel Kukard's avatar
Nigel Kukard committed
385
386
387
388
# * Notes
#    - Notes on this limit
# * Source
#    - This is the source of the limit, typically plugin.ModuleName
389
390
391
392
#
# $globals->{'Pools'}
# $globals->{'PoolNameMap'}
# $globals->{'PoolIDCounter'}
Nigel Kukard's avatar
Nigel Kukard committed
393
394
395
396
397
398


#
# POOL MEMBERS
#
# Supoprted user attributes:
399
# * ID
Nigel Kukard's avatar
Nigel Kukard committed
400
401
402
403
404
405
406
407
408
409
# * PoolID
#    - Pool ID
# * Username
#    - Users username
# * IPAddress
#    - Users IP address
# * GroupID
#    - Group ID
# * MatchPriorityID
#    - Match priority on the backend of this limit
410
# * TrafficClassID
Nigel Kukard's avatar
Nigel Kukard committed
411
#    - Class ID
412
413
414
415
416
417
418
419
420
421
422
423
# * Expires
#    - Unix timestamp when this entry expires, 0 if never
# * FriendlyName
#    - Used for display purposes instead of username if specified
# * Notes
#    - Notes on this limit
# * Status
#    - new
#    - offline
#    - online
#    - unknown
# * Source
Nigel Kukard's avatar
Nigel Kukard committed
424
#    - This is the source of the limit, typically plugin.ModuleName
425
426
427
428
#
# $globals->{'PoolMembers'}
# $globals->{'PoolMemberIDCounter'}
# $globals->{'PoolMemberMap'}
Nigel Kukard's avatar
Nigel Kukard committed
429

430
431

#
432
# POOL OVERRIDES
433
434
#
# Selection criteria:
435
436
# * PoolName
#    - Pool name
437
438
# * Username
#    - Users username
Nigel Kukard's avatar
Nigel Kukard committed
439
440
# * IPAddress
#    - Users IP address
441
442
443
# * GroupID
#    - Group ID
#
444
445
# Pool Overrides:
# * TrafficClassID
446
#    - Class ID
447
# * TxCIR
448
#    - Traffic limit in kbps
449
# * RxCIR
450
#    - Traffic limit in kbps
451
# * TxLimit
452
#    - Traffic bursting limit in kbps
453
# * RxLimit
454
455
456
#    - Traffic bursting limit in kbps
#
# Parameters:
457
# * ID
458
459
460
461
462
463
464
# * FriendlyName
#    - Used for display purposes
# * Expires
#    - Unix timestamp when this entry expires, 0 if never
# * Notes
#    - Notes on this limit
# * Source
Nigel Kukard's avatar
Nigel Kukard committed
465
#    - This is the source of the limit, typically plugin.ModuleName
466
467
468
#
# $globals->{'PoolOverrides'}
# $globals->{'PoolOverrideIDCounter'}
469

470

471
472
473
474
475
#
# CHANGE QUEUES
#
# $globals->{'PoolChangeQueue'}
# $globals->{'PoolMemberChangeQueue'}
Nigel Kukard's avatar
Nigel Kukard committed
476

477
478


479
# Initialize plugin
Nigel Kukard's avatar
Nigel Kukard committed
480
sub plugin_init
481
{
482
483
	my $system = shift;

484

485
	my $now = time();
486
487

	# Setup our environment
488
	$logger = $system->{'logger'};
489

Nigel Kukard's avatar
Nigel Kukard committed
490
491
492
	$logger->log(LOG_NOTICE,"[CONFIGMANAGER] OpenTrafficShaper Config Manager v%s - Copyright (c) 2007-2014, AllWorldIT",
			VERSION
	);
493

494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
	# Initialize
	$globals->{'LastCleanup'} = $now;
	$globals->{'StateChanged'} = 0;
	$globals->{'LastStateSync'} = $now;

	$globals->{'Groups'} = { };
	$globals->{'TrafficClasses'} = { };

	$globals->{'Interfaces'} = { };
	$globals->{'InterfaceGroups'} = { };

	$globals->{'Pools'} = { };
	$globals->{'PoolNameMap'} = { };
	$globals->{'PoolIDCounter'} = 1;
	$globals->{'DefaultPool'} = undef;

	$globals->{'PoolMembers'} = { };
	$globals->{'PoolMemberIDCounter'} = 1;
	$globals->{'PoolMemberMap'} = { };

	$globals->{'PoolOverrides'} = { };
	$globals->{'PoolOverrideIDCounter'} = 1;

517
518
519
	$globals->{'InterfaceTrafficClasses'} = { };
	$globals->{'InterfaceTrafficClassCounter'} = 1;

520
521
	$globals->{'PoolChangeQueue'} = { };
	$globals->{'PoolMemberChangeQueue'} = { };
522
	$globals->{'InterfaceTrafficClassChangeQueue'} = { };
523

Nigel Kukard's avatar
Nigel Kukard committed
524
525
	# If we have global config, use it
	my $gconfig = { };
526
527
	if (defined($system->{'file.config'}->{'shaping'})) {
		$gconfig = $system->{'file.config'}->{'shaping'};
Nigel Kukard's avatar
Nigel Kukard committed
528
529
	}

530
531
532
	# Split off groups to load
	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading traffic groups...");
	# Check if we loaded an array or just text
Nigel Kukard's avatar
Nigel Kukard committed
533
534
535
536
537
538
539
540
541
542
543
	my @groups;
	if (defined($gconfig->{'group'})) {
		if (ref($gconfig->{'group'}) eq "ARRAY") {
			@groups = @{$gconfig->{'group'}};
		} else {
			@groups = ( $gconfig->{'group'} );
		}
	} else {
		@groups = ( "1:Default (auto)" );
		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] No groups, setting up defaults");
	}
544
545
	# Loop with groups
	foreach my $group (@groups) {
546
547
		# Skip comments
		next if ($group =~ /^\s*#/);
548
549
550
		# Split off group ID and group name
		my ($groupID,$groupName) = split(/:/,$group);
		if (!defined($groupID) || int($groupID) < 1) {
Nigel Kukard's avatar
Nigel Kukard committed
551
			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic group definition '%s' has invalid ID, ignoring",$group);
552
553
554
			next;
		}
		if (!defined($groupName) || $groupName eq "") {
Nigel Kukard's avatar
Nigel Kukard committed
555
			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic group definition '%s' has invalid name, ignoring",$group);
556
557
			next;
		}
558
559
560
561
562
563
564
565
566
		# Create group
		$groupID = createGroup({
			'ID' => $groupID,
			'Name' => $groupName
		});

		if (defined($groupID)) {
			$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded traffic group '%s' [%s]",$groupName,$groupID);
		}
567
	}
Nigel Kukard's avatar
Nigel Kukard committed
568
	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Traffic groups loaded");
Nigel Kukard's avatar
Nigel Kukard committed
569

570
571
572
573

	# Split off traffic classes
	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading traffic classes...");
	# Check if we loaded an array or just text
Nigel Kukard's avatar
Nigel Kukard committed
574
575
576
577
578
579
580
581
582
583
584
	my @classes;
	if (defined($gconfig->{'class'})) {
		if (ref($gconfig->{'class'}) eq "ARRAY") {
			@classes = @{$gconfig->{'class'}};
		} else {
			@classes = ( $gconfig->{'class'} );
		}
	} else {
		@classes = ( "1:Default (auto)" );
		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] No classes, setting up defaults");
	}
585
586
	# Loop with classes
	foreach my $class (@classes) {
587
588
		# Skip comments
		next if ($class =~ /^\s*#/);
589
		# Split off class ID and class name
590
591
		my ($trafficClassID,$className) = split(/:/,$class);
		if (!defined(isNumber($trafficClassID))) {
Nigel Kukard's avatar
Nigel Kukard committed
592
			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic class definition '%s' has invalid ID, ignoring",$class);
593
594
595
			next;
		}
		if (!defined($className) || $className eq "") {
Nigel Kukard's avatar
Nigel Kukard committed
596
			$logger->log(LOG_WARN,"[CONFIGMANAGER] Traffic class definition '%s' has invalid name, ignoring",$class);
597
598
			next;
		}
599
600
601
602
603
604
605
606
607
608
609
		# Create class
		$trafficClassID = createTrafficClass({
			'ID' => $trafficClassID,
			'Name' => $className
		});

		if (!defined($trafficClassID)) {
			next;
		}

		$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded traffic class '%s' [%s]",$className,$trafficClassID);
610
	}
Nigel Kukard's avatar
Nigel Kukard committed
611
	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Traffic classes loaded");
612

Nigel Kukard's avatar
Nigel Kukard committed
613
614
615
616

	# Load interfaces
	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading interfaces...");
	my @interfaces;
617
618
	if (defined($system->{'file.config'}->{'shaping.interface'})) {
		@interfaces = keys %{$system->{'file.config'}->{'shaping.interface'}};
Nigel Kukard's avatar
Nigel Kukard committed
619
620
621
622
623
624
625
626
627
	} else {
		@interfaces = ( "eth0", "eth1" );
		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] No interfaces defined, using 'eth0' and 'eth1'");
	}
	# Loop with interface
	foreach my $interface (@interfaces) {
		# This is the interface config to make things easier for us
		my $iconfig = { };
		# Check if its defined
628
629
		if (defined($system->{'file.config'}->{'shaping.interface'}) &&
				defined($system->{'file.config'}->{'shaping.interface'}->{$interface})
Nigel Kukard's avatar
Nigel Kukard committed
630
		) {
631
			$iconfig = $system->{'file.config'}->{'shaping.interface'}->{$interface}
Nigel Kukard's avatar
Nigel Kukard committed
632
633
634
		}

		# Check our friendly name for this interface
635
		my $interfaceName = "$interface (auto)";
Nigel Kukard's avatar
Nigel Kukard committed
636
		if (defined($iconfig->{'name'}) && $iconfig->{'name'} ne "") {
637
			$interfaceName = $iconfig->{'name'};
638
		} else {
Nigel Kukard's avatar
Nigel Kukard committed
639
640
641
			$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '%s' has no 'name' attribute, using '%s (auto)'",
					$interface,$interface
			);
642
		}
Nigel Kukard's avatar
Nigel Kukard committed
643
644

		# Check our interface rate
645
		my $interfaceLimit = 100000;
Nigel Kukard's avatar
Nigel Kukard committed
646
		if (defined($iconfig->{'rate'}) && $iconfig->{'rate'} ne "") {
647
			# Check limit is valid
Nigel Kukard's avatar
Nigel Kukard committed
648
			if (defined(my $rate = isNumber($iconfig->{'rate'}))) {
649
				$interfaceLimit = $rate;
Nigel Kukard's avatar
Nigel Kukard committed
650
			} else {
Nigel Kukard's avatar
Nigel Kukard committed
651
652
653
				$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' has invalid 'rate' attribute, using 100000 instead",
						$interface
				);
Nigel Kukard's avatar
Nigel Kukard committed
654
655
			}
		} else {
Nigel Kukard's avatar
Nigel Kukard committed
656
			$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '%s' has no 'rate' attribute specified, using 100000",$interface);
657
		}
Nigel Kukard's avatar
Nigel Kukard committed
658

659
660
661
662
663
664
665
666
		# Create interface
		my $interfaceID = createInterface({
			'ID' => $interface,
			'Name' => $interfaceName,
			'Device' => $interface,
			'Limit' => $interfaceLimit
		});

Nigel Kukard's avatar
Nigel Kukard committed
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683

		# Check if we have a section in our
		if (defined($iconfig->{'class_rate'})) {

			# Lets pull off the class_rate items
			my @iclasses;
			if (ref($iconfig->{'class_rate'}) eq "ARRAY") {
				@iclasses = @{$iconfig->{'class_rate'}};
			} else {
				@iclasses = ( $iconfig->{'class_rate'} );
			}

			# Loop with class_rates and parse
			foreach my $iclass (@iclasses) {
				# Skip comments
				next if ($iclass =~ /^\s*#/);
				# Split off class ID and class name
684
				my ($itrafficClassID,$iclassCIR,$iclassLimit) = split(/[:\/]/,$iclass);
Nigel Kukard's avatar
Nigel Kukard committed
685
686


687
				if (!defined($itrafficClassID = isTrafficClassIDValid($itrafficClassID))) {
Nigel Kukard's avatar
Nigel Kukard committed
688
689
690
691
692
					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class definition '%s' has invalid Class ID, ignoring ".
							"definition",
							$interface,
							$iclass
					);
Nigel Kukard's avatar
Nigel Kukard committed
693
694
695
696
697
698
699
700
701
702
					next;
				}

				# If the CIR is defined, try use it
				if (defined($iclassCIR)) {
					# If its not a number, something is wrong
					if ($iclassCIR =~ /^([1-9][0-9]*)(%)?$/) {
						my ($cir,$percent) = ($1,$2);
						# Check if this is a percentage or an actual kbps value
						if (defined($percent)) {
703
							$iclassCIR = int($interfaceLimit * ($cir / 100));
Nigel Kukard's avatar
Nigel Kukard committed
704
705
706
707
						} else {
							$iclassCIR = $cir;
						}
					} else {
Nigel Kukard's avatar
Nigel Kukard committed
708
709
						$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class '%s' has invalid CIR, ignoring definition",
								$interface,
710
								$itrafficClassID
Nigel Kukard's avatar
Nigel Kukard committed
711
						);
Nigel Kukard's avatar
Nigel Kukard committed
712
713
714
						next;
					}
				} else {
Nigel Kukard's avatar
Nigel Kukard committed
715
716
					$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class '%s' has missing CIR, ignoring definition",
							$interface,
717
							$itrafficClassID
Nigel Kukard's avatar
Nigel Kukard committed
718
					);
Nigel Kukard's avatar
Nigel Kukard committed
719
720
721
722
723
724
725
726
727
728
					next;
				}

				# If the limit is defined, try use it
				if (defined($iclassLimit)) {
					# If its not a number, something is wrong
					if ($iclassLimit =~ /^([1-9][0-9]*)(%)?$/) {
						my ($Limit,$percent) = ($1,$2);
						# Check if this is a percentage or an actual kbps value
						if (defined($percent)) {
729
							$iclassLimit = int($interfaceLimit * ($Limit / 100));
Nigel Kukard's avatar
Nigel Kukard committed
730
731
732
733
						} else {
							$iclassLimit = $Limit;
						}
					} else {
Nigel Kukard's avatar
Nigel Kukard committed
734
735
						$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface '%s' class '%s' has invalid Limit, ignoring",
								$interface,
736
								$itrafficClassID
Nigel Kukard's avatar
Nigel Kukard committed
737
						);
Nigel Kukard's avatar
Nigel Kukard committed
738
739
740
						next;
					}
				} else {
Nigel Kukard's avatar
Nigel Kukard committed
741
742
					$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '%s' class '%s' has missing Limit, using CIR '%s' instead",
							$interface,
743
							$itrafficClassID,
Nigel Kukard's avatar
Nigel Kukard committed
744
745
							$iclassCIR
					);
Nigel Kukard's avatar
Nigel Kukard committed
746
747
748
749
					$iclassLimit = $iclassCIR;
				}

				# Check if rates are below are sane
750
				if ($iclassCIR > $interfaceLimit) {
751
					$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '%s' class '%s' has CIR '%s' > interface speed '%s', ".
Nigel Kukard's avatar
Nigel Kukard committed
752
753
							"adjusting to '%s'",
							$interface,
754
							$itrafficClassID,
Nigel Kukard's avatar
Nigel Kukard committed
755
							$iclassCIR,
756
757
							$interfaceLimit,
							$interfaceLimit
Nigel Kukard's avatar
Nigel Kukard committed
758
					);
759
					$iclassCIR = $interfaceLimit;
Nigel Kukard's avatar
Nigel Kukard committed
760
				}
761
				if ($iclassLimit > $interfaceLimit) {
762
					$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '%s' class '%s' has Limit '%s' > interface speed '%s', ".
Nigel Kukard's avatar
Nigel Kukard committed
763
764
							"adjusting to '%s'",
							$interface,
765
							$itrafficClassID,
Nigel Kukard's avatar
Nigel Kukard committed
766
							$iclassCIR,
767
768
							$interfaceLimit,
							$interfaceLimit
Nigel Kukard's avatar
Nigel Kukard committed
769
					);
770
					$iclassLimit = $interfaceLimit;
Nigel Kukard's avatar
Nigel Kukard committed
771
772
				}
				if ($iclassCIR > $iclassLimit) {
773
					$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '%s' class '%s' has CIR '%s' > Limit '%s', adjusting CIR ".
Nigel Kukard's avatar
Nigel Kukard committed
774
775
							"to '%s'",
							$interface,
776
							$itrafficClassID,
Nigel Kukard's avatar
Nigel Kukard committed
777
778
779
780
							$iclassLimit,
							$iclassLimit,
							$iclassLimit
					);
Nigel Kukard's avatar
Nigel Kukard committed
781
782
783
					$iclassCIR = $iclassLimit;
				}

784
785
786
787
788
789
790
791
792
793
794
				# Create class
				my $interfaceTrafficClassID = createInterfaceTrafficClass({
						'InterfaceID' => $interfaceID,
						'TrafficClassID' => $itrafficClassID,
						'CIR' => $iclassCIR,
						'Limit' => $iclassLimit
				});

				if (!defined($interfaceTrafficClassID)) {
					next;
				}
Nigel Kukard's avatar
Nigel Kukard committed
795

Nigel Kukard's avatar
Nigel Kukard committed
796
797
				$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded interface '%s' class rate for class ID '%s': %s/%s",
						$interface,
798
						$itrafficClassID,
Nigel Kukard's avatar
Nigel Kukard committed
799
800
801
						$iclassCIR,
						$iclassLimit
				);
Nigel Kukard's avatar
Nigel Kukard committed
802
803
804
805
			}

		}

806
807
808
		# Time to check the interface classes
		foreach my $trafficClassID (getAllTrafficClasses()) {
			# Check if we have a rate defined for this class in the interface definition
809
810
			if (!isInterfaceTrafficClassValid($interfaceID,$trafficClassID)) {
				$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Interface '%s' has no class '%s' defined, using interface limit",
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
						$interface,
						$trafficClassID
				);
				# Create the default class
				createInterfaceTrafficClass({
					'InterfaceID' => $interfaceID,
					'TrafficClassID' => $trafficClassID,
					'CIR' => $interfaceLimit,
					'Limit' => $interfaceLimit
				});

				$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded interface '%s' default class rate for class ID '%s': %s/%s",
						$interface,
						$trafficClassID,
						$interfaceLimit,
						$interfaceLimit
				);
			}
		}
830
	}
Nigel Kukard's avatar
Nigel Kukard committed
831
	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading interfaces completed");
832

Nigel Kukard's avatar
Nigel Kukard committed
833
834
835
	# Pull in interface groupings
	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Loading interface groups...");
	# Check if we loaded an array or just text
836
	my @cinterfaceGroups;
Nigel Kukard's avatar
Nigel Kukard committed
837
838
	if (defined($gconfig->{'interface_group'})) {
		if (ref($gconfig->{'interface_group'}) eq "ARRAY") {
839
			@cinterfaceGroups = @{$gconfig->{'interface_group'}};
840
		} else {
841
			@cinterfaceGroups = ( $gconfig->{'interface_group'} );
842
		}
Nigel Kukard's avatar
Nigel Kukard committed
843
	} else {
844
		@cinterfaceGroups = ( "eth1,eth0:Default" );
Nigel Kukard's avatar
Nigel Kukard committed
845
846
847
		$logger->log(LOG_NOTICE,"[CONFIGMANAGER] No interface groups, trying default eth1,eth0");
	}
	# Loop with interface groups
848
	foreach my $interfaceGroup (@cinterfaceGroups) {
Nigel Kukard's avatar
Nigel Kukard committed
849
850
851
		# Skip comments
		next if ($interfaceGroup =~ /^\s*#/);
		# Split off class ID and class name
852
853
		my ($txInterface,$rxInterface,$friendlyName) = split(/[:,]/,$interfaceGroup);
		if (!isInterfaceIDValid($txInterface)) {
Nigel Kukard's avatar
Nigel Kukard committed
854
855
			$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface group definition '%s' has invalid interface '%s', ignoring",
					$interfaceGroup,
856
					$txInterface
Nigel Kukard's avatar
Nigel Kukard committed
857
			);
Nigel Kukard's avatar
Nigel Kukard committed
858
			next;
859
		}
860
		if (!isInterfaceIDValid($rxInterface)) {
Nigel Kukard's avatar
Nigel Kukard committed
861
862
			$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface group definition '%s' has invalid interface '%s', ignoring",
					$interfaceGroup,
863
					$rxInterface
Nigel Kukard's avatar
Nigel Kukard committed
864
			);
Nigel Kukard's avatar
Nigel Kukard committed
865
866
867
			next;
		}
		if (!defined($friendlyName) || $friendlyName eq "") {
Nigel Kukard's avatar
Nigel Kukard committed
868
869
870
			$logger->log(LOG_WARN,"[CONFIGMANAGER] Interface group definition '%s' has invalid friendly name, ignoring",
					$interfaceGroup,
			);
Nigel Kukard's avatar
Nigel Kukard committed
871
872
873
			next;
		}

874
875
876
877
878
879
880
881
882
883
		# Create interface group
		my $interfaceGroupID = createInterfaceGroup({
				'Name' => $friendlyName,
				'TxInterface' => $txInterface,
				'RxInterface' => $rxInterface
		});

		if (!defined($interfaceGroupID)) {
			next;
		}
Nigel Kukard's avatar
Nigel Kukard committed
884

885
		$logger->log(LOG_INFO,"[CONFIGMANAGER] Loaded interface group '%s' [%s] with interfaces '%s/%s'",
Nigel Kukard's avatar
Nigel Kukard committed
886
				$friendlyName,
887
888
889
				$interfaceGroupID,
				$txInterface,
				$rxInterface
Nigel Kukard's avatar
Nigel Kukard committed
890
891
892
893
		);
	}

	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Interface groups loaded");
Nigel Kukard's avatar
Nigel Kukard committed
894
895
896
897
898
899


	# Check if we using a default pool or not
	if (defined($gconfig->{'default_pool'})) {
		# Check if its a number
		if (defined(my $default_pool = isNumber($gconfig->{'default_pool'}))) {
900
901
902
			if (isTrafficClassIDValid($default_pool)) {
				$logger->log(LOG_INFO,"[CONFIGMANAGER] Default pool set to use class '%s'",
						$default_pool
Nigel Kukard's avatar
Nigel Kukard committed
903
				);
904
				$globals->{'DefaultPool'} = $default_pool;
Nigel Kukard's avatar
Nigel Kukard committed
905
			} else {
Nigel Kukard's avatar
Nigel Kukard committed
906
907
908
				$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot enable default pool, class '%s' does not exist",
						$default_pool
				);
Nigel Kukard's avatar
Nigel Kukard committed
909
			}
910
		} else {
Nigel Kukard's avatar
Nigel Kukard committed
911
			$logger->log(LOG_WARN,"[CONFIGMANAGER] Cannot enable default pool, value for 'default_pool' is invalid");
Nigel Kukard's avatar
Nigel Kukard committed
912
913
		}
	}
914

915
916
# TODO - loop and queue init interfaces?

917
	# Check if we have a state file
918
	if (defined(my $statefile = $system->{'file.config'}->{'system'}->{'statefile'})) {
919
		$config->{'statefile'} = $statefile;
Nigel Kukard's avatar
Nigel Kukard committed
920
		$logger->log(LOG_INFO,"[CONFIGMANAGER] Set statefile to '%s'",$statefile);
921
	}
Nigel Kukard's avatar
Nigel Kukard committed
922

923
924
925
	# This is our configuration processing session
	POE::Session->create(
		inline_states => {
Nigel Kukard's avatar
Nigel Kukard committed
926
927
928
929
930
931
			_start => \&_session_start,
			_stop => \&_session_stop,
			_tick => \&_session_tick,
			_SIGHUP => \&_session_SIGHUP,

			limit_add => \&_session_limit_add,
932

933
934
935
			pool_override_add => \&_session_pool_override_add,
			pool_override_change => \&_session_pool_override_change,
			pool_override_remove => \&_session_pool_override_remove,
936

Nigel Kukard's avatar
Nigel Kukard committed
937
938
939
			pool_add => \&_session_pool_add,
			pool_remove => \&_session_pool_remove,
			pool_change => \&_session_pool_change,
940

Nigel Kukard's avatar
Nigel Kukard committed
941
942
943
			poolmember_add => \&_session_poolmember_add,
			poolmember_remove => \&_session_poolmember_remove,
			poolmember_change => \&_session_poolmember_change,
944

945
946
		}
	);
947
948
949
}


Nigel Kukard's avatar
Nigel Kukard committed
950

Nigel Kukard's avatar
Nigel Kukard committed
951
952
953
# Start the plugin
sub plugin_start
{
954
955
956
957
958
959
960
	# Load config
	if (-f $config->{'statefile'}) {
		_load_statefile();
	} else {
		$logger->log(LOG_WARN,"[CONFIGMANAGER] Statefile '%s' cannot be opened: %s",$config->{'statefile'},$!);
	}

961
962
963
964
	$logger->log(LOG_INFO,"[CONFIGMANAGER] Started with %s pools, %s pool members and %s pool overrides",
			scalar(keys %{$globals->{'Pools'}}),
			scalar(keys %{$globals->{'PoolMembers'}}),
			scalar(keys %{$globals->{'PoolOverrides'}})
Nigel Kukard's avatar
Nigel Kukard committed
965
	);
Nigel Kukard's avatar
Nigel Kukard committed
966
967
968
}


969
970

# Initialize config manager
Nigel Kukard's avatar
Nigel Kukard committed
971
sub _session_start
972
973
{
	my ($kernel,$heap) = @_[KERNEL,HEAP];
974
975
976
977
978
979


	# Set our alias
	$kernel->alias_set("configmanager");

	# Set delay on config updates
Nigel Kukard's avatar
Nigel Kukard committed
980
	$kernel->delay('_tick' => TICK_PERIOD);
981

Nigel Kukard's avatar
Nigel Kukard committed
982
	$kernel->sig('HUP', '_SIGHUP');
983

Nigel Kukard's avatar
Nigel Kukard committed
984
	$logger->log(LOG_DEBUG,"[CONFIGMANAGER] Initialized");
985
986
987
}


Nigel Kukard's avatar
Nigel Kukard committed
988

989
# Stop the session
Nigel Kukard's avatar
Nigel Kukard committed
990
sub _session_stop
991
992
993
994
995
996
{
	my ($kernel,$heap) = @_[KERNEL,HEAP];


	$logger->log(LOG_NOTICE,"[CONFIGMANAGER] Shutting down, saving configuration...");

997
	# We only need to write the sate if something changed?
998
	if ($globals->{'StateChanged'}) {
999
1000
		# The 1 means FULL WRITE of all entries
		_write_statefile(1);