VMPS::Packet - Handle VMPS request/response packets.


VMPS-Server documentation Contained in the VMPS-Server distribution.

Index


Code Index:

NAME

Top

VMPS::Packet - Handle VMPS request/response packets.

FUNCTIONS

Top

client_ip()

Return the IP address encoded in the request. NB: this may be different from the source IP in the UDP header.

port()

The switch port name in the request.

vlan()

Return the VLAN name from the request. With arg, sets the VLAN name in replies:

    $reply->vlan('your_vlan');

domain()

Returns the VTP domain from the request.

packet()

Returns the "first packet" that may be encoded in the request.

mac_addr()

Returns a Net::MAC object with the MAC address from the request. This searches the dynamic mac address (0xc06), then the static mac address (0xc08). Returns undef if none can be found.

GENERATING REPLIES

Top

reply()

Create a request object with the specified error code and vlan. Vlan may be omitted for error codes other than VLAN_ERROR_NONE.

    my $reply = $request->reply($err_code, $vlan);

GENERATING REPLIES MADE EASY

Top

reject

Create a response object that rejects the request.

    my $reply = $request->reject();

accept

Accept the request, assign the user to the specified VLAN name.

    my $reply = $request->accept($vlan);

shutdown

Reject the request; instruct the switch to shutdown the port.

    my $reply = $request->shutdown();

wrong_domain

Reject the request because it is from the wrong domain.

    my $reply = $request->wrong_domain();


VMPS-Server documentation Contained in the VMPS-Server distribution.
package VMPS::Packet;
use strict;
use warnings;
use base qw[ Exporter ];

our $VERSION = '0.04';

use Net::MAC;
use Storable qw[ dclone ];

our %CONSTANTS;
BEGIN {
    %CONSTANTS = (
        VMPS_REQ_JOIN => 0x1,
        VMPS_RESP_JOIN => 0x2,
        VMPS_REQ_RECONFIRM => 0x3,
        VMPS_RESP_RECONFIRM => 0x4,

        VMPS_ERROR_NONE => 0x0,
        VMPS_ERROR_ACCESS_DENIED => 0x3,
        VMPS_ERROR_SHUTDOWN => 0x4,
        VMPS_ERROR_WRONG_DOMAIN => 0x5,

        VMPS_DATA_CLIENT_IP   => 0xc01,
        VMPS_DATA_PORT_NAME   => 0xc02,
        VMPS_DATA_VLAN_NAME   => 0xc03,
        VMPS_DATA_VTP_DOMAIN  => 0xc04,
        VMPS_DATA_PACKET      => 0xc05,
        VMPS_DATA_MAC_DYNAMIC => 0xc06,
        VMPS_DATA_UNK7        => 0xc07,
        VMPS_DATA_MAC_STATIC  => 0xc08,
    );
}

use constant \%CONSTANTS;
use constant VMPS_HEADER_SIZE => 8;
use constant VMPS_TLV_HLEN => 6;

our @EXPORT_OK = keys %CONSTANTS;
our %EXPORT_TAGS = (
    'constants' => [keys %CONSTANTS],
);

#################################################################

sub _decode {
    my ($this, $dgram) = @_;

    die "Short packet!" if length($dgram) < VMPS_HEADER_SIZE;

    ## parse the header
    my ($one, $type, $err, $num_rec, $seq)
        = unpack('C4 N', substr($dgram, 0, VMPS_HEADER_SIZE, ''));

    ## sanity check
    die "Packet must start with 0x01" unless ($one == 0x1);
    die "Unknown request type: $type"
        unless ($type == VMPS_REQ_JOIN or $type == VMPS_REQ_RECONFIRM);

    my %pkt_data;

    ## decode TLVs
    while ($num_rec > 0)
    {
        $dgram or die "Short packet (expecting $num_rec more records)";
        $num_rec--;
        my ($typ, $len, $val);

        ($typ, $len) = unpack('Nn', substr($dgram, 0, VMPS_TLV_HLEN, ''));

        $val = substr($dgram, 0, $len, '');

        $pkt_data{$typ} = $val;
    }

    $dgram and die "Extra data at end of packet!";

    bless { TYPE => $type,
            ERR  => $err,
            SEQ  => $seq,
            DATA => \%pkt_data }, $this;
}

###################################

sub _encode {
    my ($this) = @_;

    ###################################
    ## encode packet header

    ## leading byte always 1
    my $pkt = pack('C', 1);

    die "Unknown response type ($this->{TYPE})!"
        unless (   $this->{TYPE} == VMPS_RESP_JOIN
                or $this->{TYPE} == VMPS_RESP_RECONFIRM);

    my $num_rec = $this->{ERR} == VMPS_ERROR_NONE ? 2 : 0;

    $pkt .= pack('C3 N', $this->{TYPE}, $this->{ERR}, $num_rec, $this->{SEQ});

    ###################################
    ## encode contents

    if ($this->{ERR} == VMPS_ERROR_NONE)
    {
        $pkt .= $this->_encode_data(VMPS_DATA_VLAN_NAME);
        $pkt .= $this->_encode_data(VMPS_DATA_MAC_STATIC, $this->_first_mac());
    }

    return $pkt;
}

###################################

sub _encode_data {
    my $this = shift;
    my $type = shift;

    my $val = @_ ? $_[0] : $this->{DATA}{$type};
    my $len = length($val);

    return pack ('N n a*', $type, $len, $val);
}

#################################################################
## accessor functions

sub client_ip { join '.', unpack('C4', shift->{DATA}{+VMPS_DATA_CLIENT_IP}) }

sub port { shift->{DATA}{+VMPS_DATA_PORT_NAME} }

sub vlan {
    my $this = shift;

    if (@_)
    {
        $this->{DATA}{+VMPS_DATA_VLAN_NAME} = $_[0];
    }

    return $this->{DATA}{+VMPS_DATA_VLAN_NAME};
}

sub domain { shift->{DATA}{+VMPS_DATA_VTP_DOMAIN} }

sub packet { shift->{DATA}{+VMPS_DATA_PACKET} }

sub mac_addr {
    my ($this) = @_;

    my $mac = $this->_first_mac() || return undef;
    my $str = join ':', map { sprintf("%02x", $_) } unpack ('C6', $mac);

    return Net::MAC->new(mac => $str);
}

sub _first_mac {
    my $this = shift;

    for my $req (VMPS_DATA_MAC_DYNAMIC, VMPS_DATA_MAC_STATIC)
    {
        return $this->{DATA}{$req} if exists $this->{DATA}{$req};
    }

    ## pick the MAC out of the first packet!
    if (defined $this->{DATA}{+VMPS_DATA_PACKET})
    {
        return substr($this->{DATA}{+VMPS_DATA_PACKET}, 6, 6);
    }

    return undef;
}

#################################################################
## craft a response packet

sub reply {
    my ($req, $err, $vlan) = @_;
    my $rep = dclone($req);

    $req->{TYPE}++;  # reply packet types are req type + 1
    $req->{ERR} = $err || 0;
    $req->{DATA}{+VMPS_DATA_VLAN_NAME} = $vlan || '';

    return $req;
}

###################################

sub reject { shift->reply(VMPS_ERROR_ACCESS_DENIED) }

###################################

sub accept { shift->reply(VMPS_ERROR_NONE, shift) }

###################################

sub shutdown { shift->reply(VMPS_ERROR_SHUTDOWN) }

###################################

sub wrong_domain { shift->reply(VMPS_ERROR_WRONG_DOMAIN) }

#################################################################

1;