diff options
Diffstat (limited to 'lib/Vyatta')
-rw-r--r-- | lib/Vyatta/Qos/HFSCClass.pm | 194 | ||||
-rw-r--r-- | lib/Vyatta/Qos/HFSCShaper.pm | 183 | ||||
-rw-r--r-- | lib/Vyatta/Qos/Shared.pm | 1 |
3 files changed, 378 insertions, 0 deletions
diff --git a/lib/Vyatta/Qos/HFSCClass.pm b/lib/Vyatta/Qos/HFSCClass.pm new file mode 100644 index 0000000..08a6c58 --- /dev/null +++ b/lib/Vyatta/Qos/HFSCClass.pm @@ -0,0 +1,194 @@ +# Traffic shaper sub-class + +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2008 Vyatta, Inc. +# All Rights Reserved. +# **** End License **** + +package Vyatta::Qos::HFSCClass; +use strict; +use warnings; + +require Vyatta::Config; +use Vyatta::Qos::Match; +use Vyatta::Qos::Util qw/getDsfield getTime getRate/; + +sub new { + my ( $that, $config, $id ) = @_; + my $class = ref($that) || $that; + my $self = {}; + + $self->{id} = $id; + + bless $self, $class; + + if ($config) { + my $level = $config->setLevel(); + + $self->{level} = $level; + my @matches = _getMatch("$level match"); + + # HFSC service curves + $self->{_linkshare} = (); + $self->{_realtime} = (); + $self->{_upperlimit} = (); + + # Populate services curves using configuration + foreach my $sc (qw(linkshare realtime upperlimit)) { + foreach my $type (qw(m1 m2)) { + $self->{'_' . $sc}{$type} = $config->returnValue($sc . ' ' . $type); + } + my $delay = $config->returnValue($sc . ' d'); + $self->{'_' . $sc}{d} = getTime($delay) if ($delay); + } + + $self->{_match} = \@matches; + } + + + return $self; +} + +sub _getMatch { + my $level = shift; + my @matches; + my $config = new Vyatta::Config; + + foreach my $match ( $config->listNodes($level) ) { + $config->setLevel("$level $match"); + my $match = new Vyatta::Qos::Match($config); + if (defined($match)) { + push @matches, $match; + } + } + return @matches; +} + +sub matchRules { + my ($self) = @_; + my $matches = $self->{_match}; + return @$matches; +} + +sub _getPercentRate { + my ( $rate, $speed ) = @_; + return unless defined $rate; + + # Rate might be a percentage of speed + if ( $rate =~ /%$/ ) { + my $percent = substr( $rate, 0, length($rate) - 1 ); + if ( $percent < 0 || $percent > 100 ) { + die "Invalid percentage bandwidth: $percent\n"; + } + + return ( $percent * $speed ) / 100.; + } + + return getRate($rate); +} + +# Check rate configuration (%age or absolute values) +sub rateCheck { + my ( $self, $ifspeed, $level ) = @_; + + # We need at least one M2 to be set + if(!(defined $self->{_linkshare}{m2} || defined $self->{_realtime}{m2} || defined $self->{_upperlimit}{m2})) { + print STDERR "Configuration error in: $level\n"; + print STDERR "At least one m2 value needs to be set"; + exit 1; + } + + # Linkshare (or servicecurve) must be defined to use upperlimit + if(defined $self->{_upperlimit}{m2} && !defined $self->{_linkshare}{m2}) { + print STDERR "Configuration error in: $level\n"; + print STDERR "Linkshare m2 needs to be defined to use upperlimit m2"; + exit 1; + } + + # Check that each m2 rate is below interface rate + foreach my $sc (qw(linkshare realtime upperlimit)) { + my $rate = _getPercentRate($self->{'_' . $sc}{m2}, $ifspeed); + if(defined $rate && $rate > $ifspeed) { + print STDERR "Configuration error in: $level\n"; + printf STDERR "$sc m2 value (%dKbps) must be less than the bw for the policy (%dKbps)", $rate / 1000, $ifspeed / 1000; + exit 1; + } + } + # Same with m1 values, check that we have a matching m2 value and a valid d value + foreach my $sc (qw(linkshare realtime upperlimit)) { + if(defined $self->{'_' . $sc}{m1}) { + # m1 is set, we need a matching m2 value + if(!defined $self->{'_' . $sc}{m2}) { + print STDERR "Configuration error in :$level\n"; + print STDERR "$sc m1 value is set, but no m2 was found !"; + exit 1; + } + # m1 is set, we need a matching d value + if(!defined $self->{'_' . $sc}{d}) { + print STDERR "Configuration error in :$level\n"; + print STDERR "$sc m1 value is set, but no d was found !"; + exit 1; + } + } + my $rate = _getPercentRate($self->{'_' . $sc}{m1}, $ifspeed); + if(defined $rate && $rate > $ifspeed) { + print STDERR "Configuration error in: $level\n"; + printf STDERR "$sc m1 value (%dKbps) must be less than the bw for the policy (%dKbps)", $rate / 1000, $ifspeed / 1000; + exit 1; + } + } + +} + + +sub get_rate { + my ( $self, $speed ) = @_; + + return _getPercentRate( $self->{_rate}, $speed ); +} + +sub gen_class { + my ( $self, $dev, $qdisc, $parent, $speed ) = @_; + + printf "class add dev %s parent %x:1 classid %x:%x hfsc", + $dev, $parent, $parent, $self->{id}; + + my $ret = ''; + # format : 'ul m1 Xbit d Yms m2 Xbit rt m2 Xbit' + foreach my $sc (qw(linkshare upperlimit realtime)) { + # Translate long service curves names to short ones + my %sc_short = ( + 'linkshare' => 'ls', + 'upperlimit' => 'ul', + 'realtime' => 'rt'); + if(defined $self->{'_' . $sc}{m2}) { # We have an m2 value, add curve to hfsc class + $ret .= ' ' . $sc_short{$sc} . ' '; + if(defined $self->{'_' . $sc}{m1} && defined $self->{'_' . $sc}{d}) { # We have m1 and d value, define curve + $ret .= 'm1 ' . _getPercentRate($self->{'_' . $sc}{m1}, $speed) . ' d ' . $self->{'_' . $sc}{d} . ' '; + } + $ret .= 'm2 ' . _getPercentRate($self->{'_' . $sc}{m2}, $speed); + } + } + + print $ret; + print "\n"; + + # Add SFQ qdisc + + printf "qdisc add dev %s parent %x:%x handle f%x: sfq perturb 10", + $dev, $parent, $self->{id}, $self->{id}; + print "\n"; +} + + +1; diff --git a/lib/Vyatta/Qos/HFSCShaper.pm b/lib/Vyatta/Qos/HFSCShaper.pm new file mode 100644 index 0000000..389cd8c --- /dev/null +++ b/lib/Vyatta/Qos/HFSCShaper.pm @@ -0,0 +1,183 @@ +# Traffic shaper +# This is a extended form of Hierarchal Token Bucket with +# more admin friendly features. Similar in spirt to other shaper scripts +# such as wondershaper. +# +# **** License **** +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# 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. +# +# This code was originally developed by Vyatta, Inc. +# Portions created by Vyatta are Copyright (C) 2008 Vyatta, Inc. +# All Rights Reserved. +# **** End License **** + +package Vyatta::Qos::HFSCShaper; +use strict; +use warnings; + +require Vyatta::Config; +require Vyatta::Qos::HFSCClass; +use Vyatta::Qos::Util qw/getRate getAutoRate/; +use POSIX; + +# Create a new instance based on config information +sub new { + my ( $that, $config, $name ) = @_; + my $rate = $config->returnValue("bandwidth"); + my $level = $config->setLevel(); + my @classes = _getClasses($level); + + _checkClasses( $level, $rate, @classes ); + + my $self = {}; + my $class = ref($that) || $that; + bless $self, $class; + + $self->{_rate} = $rate; + $self->{_level} = $level; + $self->{_classes} = \@classes; + return $self; +} + +sub _getClasses { + my $level = shift; + my @classes; + my $config = new Vyatta::Config; + + $config->setLevel($level); + $config->exists("default") + or die "$level configuration not complete: missing default class\n"; + + $config->setLevel("$level default"); + push @classes, new Vyatta::Qos::HFSCClass($config); + $config->setLevel($level); + + foreach my $id ( $config->listNodes("class") ) { + $config->setLevel("$level class $id"); + push @classes, new Vyatta::Qos::HFSCClass( $config, $id ); + } + + return @classes; +} + +# Check constraints on class bandwidth values +sub _checkClasses { + my $level = shift; + my $rate = shift; + + # if auto, can't check for constraints until later + $rate = ( $rate eq "auto" ) ? undef : getRate($rate); + + # handle default class + my $default = shift; + $default->rateCheck( $rate, "$level default" ) if $rate; + + foreach my $class (@_) { + # Check rates for each class + $class->rateCheck( $rate, "$level class $class->{id}" ) if $rate; + } +} + +sub _minRate { + my ($speed, $classes) = @_; + my $min = $speed / 8; + + foreach my $class (@$classes) { + my $bps = $class->get_rate($speed) / 8; # bytes per second + + $min = $bps if $bps < $min; + } + + return $min; +} + +sub commands { + my ( $self, $dev ) = @_; + my $rate = getAutoRate( $self->{_rate}, $dev ); + my $classes = $self->{_classes}; + my %dsmark = (); + my $default = shift @$classes; + my $maxid = 1; + + $default->rateCheck( $rate, "$self->{_level} default" ); + + foreach my $class (@$classes) { + my $level = "$self->{_level} class $class->{id}"; + $class->rateCheck( $rate, $level ); + + # find largest class id + if ( defined $class->{id} && $class->{id} > $maxid ) { + $maxid = $class->{id}; + } + } + + # fill in id of default + $default->{id} = ++$maxid; + unshift @$classes, $default; + + # Check if we need dsmrk + my $usedsmark; + foreach my $class (@$classes) { + if ( defined $class->{dsmark} ) { + $usedsmark = 1; + last; + } + } + + my $parent = 1; + my $root = "root"; + +=pod + # if we need to change dsfield values, then put dsmark in front + if ($usedsmark) { + + # dsmark max index must be power of 2 + my $indices = $maxid + 1; + while ( ( $indices & ( $indices - 1 ) ) != 0 ) { + ++$indices; + } + + print "qdisc add dev $dev handle 1:0 root dsmark" + . " indices $indices default_index $default->{id} set_tc_index\n"; + + foreach my $class (@$classes) { + $class->dsmarkClass( 1, $dev ); + foreach my $match ( $class->matchRules() ) { + $match->filter( $dev, $parent, $class->{id}, 1 ); + } + } + + $parent = $indices + 1; + $root = "parent 1:1"; + } +=cut + # Add root qdisc + printf "qdisc add dev %s %s handle %x: hfsc default %x\n", + $dev, $root, $parent, $default->{id}; + # Add root class + # Note : sc = realtime + linkshare + printf "class add dev %s parent %x: classid %x:1 hfsc sc rate %s ul rate %s\n", + $dev, $parent, $parent, $rate, $rate; + + printf "qdisc add dev %s parent %x:1 handle f1: sfq perturb 10\n", + $dev, $parent; + + my $prio = 1; + foreach my $class (@$classes) { + $class->gen_class( $dev, 'hfsc', $parent, $rate ); + + foreach my $match ( $class->matchRules() ) { + $match->filter( $dev, $parent, $class->{id}, $prio++, + $class->{dsmark} ); + } + } +} + +1; diff --git a/lib/Vyatta/Qos/Shared.pm b/lib/Vyatta/Qos/Shared.pm index d7628b4..2a0fc3d 100644 --- a/lib/Vyatta/Qos/Shared.pm +++ b/lib/Vyatta/Qos/Shared.pm @@ -30,6 +30,7 @@ use base qw(Exporter); our %policies = ( 'out' => { + 'shaper-hfsc' => 'HFSCShaper', 'shaper' => 'TrafficShaper', 'fair-queue' => 'FairQueue', 'rate-control' => 'RateLimiter', |