#! /usr/bin/perl

# Copyright (c) 2009, 2010, 2011, 2012, 2015 Nicira, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

use strict;
use warnings;

open(FLOWS, ">&=3");# or die "failed to open fd 3 for writing: $!\n";
open(PACKETS, ">&=4");# or die "failed to open fd 4 for writing: $!\n";

# Print pcap file header.
print PACKETS pack('NnnNNNN',
                   0xa1b2c3d4,  # magic number
                   2,           # major version
                   4,           # minor version
                   0,           # time zone offset
                   0,           # time stamp accuracy
                   1518,        # snaplen
                   1);          # Ethernet

output(DL_HEADER => '802.2');

for my $dl_header (qw(802.2+SNAP Ethernet)) {
    my %a = (DL_HEADER => $dl_header);
    for my $dl_vlan (qw(none zero nonzero)) {
        my %b = (%a, DL_VLAN => $dl_vlan);

        # Non-IP case.
        output(%b, DL_TYPE => 'non-ip');

        for my $ip_options (qw(no yes)) {
            my %c = (%b, DL_TYPE => 'ip', IP_OPTIONS => $ip_options);
            for my $ip_fragment (qw(no first middle last)) {
                my %d = (%c, IP_FRAGMENT => $ip_fragment);
                for my $tp_proto (qw(TCP TCP+options UDP ICMP other)) {
                    output(%d, TP_PROTO => $tp_proto);
                }
            }
        }
    }
}

sub output {
    my (%attrs) = @_;

    # Compose flow.
    my (%flow);
    $flow{DL_SRC} = "00:02:e3:0f:80:a4";
    $flow{DL_DST} = "00:1a:92:40:ac:05";
    $flow{NW_PROTO} = 0;
    $flow{NW_TOS} = 0;
    $flow{NW_SRC} = '0.0.0.0';
    $flow{NW_DST} = '0.0.0.0';
    $flow{TP_SRC} = 0;
    $flow{TP_DST} = 0;
    if (defined($attrs{DL_VLAN})) {
        my (%vlan_map) = ('none' => 0xffff,
                          'zero' => 0,
                          'nonzero' => 0x0123);
        $flow{DL_VLAN} = $vlan_map{$attrs{DL_VLAN}};
    } else {
        $flow{DL_VLAN} = 0xffff; # OFP_VLAN_NONE
    }
    if ($attrs{DL_HEADER} eq '802.2') {
        $flow{DL_TYPE} = 0x5ff; # OFP_DL_TYPE_NOT_ETH_TYPE
    } elsif ($attrs{DL_TYPE} eq 'ip') {
        $flow{DL_TYPE} = 0x0800; # ETH_TYPE_IP
        $flow{NW_SRC} = '10.0.2.15';
        $flow{NW_DST} = '192.168.1.20';
        $flow{NW_TOS} = 44;
        if ($attrs{TP_PROTO} eq 'other') {
            $flow{NW_PROTO} = 42;
        } elsif ($attrs{TP_PROTO} eq 'TCP' ||
                 $attrs{TP_PROTO} eq 'TCP+options') {
            $flow{NW_PROTO} = 6; # IPPROTO_TCP
            $flow{TP_SRC} = 6667;
            $flow{TP_DST} = 9998;
        } elsif ($attrs{TP_PROTO} eq 'UDP') {
            $flow{NW_PROTO} = 17; # IPPROTO_UDP
            $flow{TP_SRC} = 1112;
            $flow{TP_DST} = 2223;
        } elsif ($attrs{TP_PROTO} eq 'ICMP') {
            $flow{NW_PROTO} = 1; # IPPROTO_ICMP
            $flow{TP_SRC} = 8;   # echo request
            $flow{TP_DST} = 0;   # code
        } else {
            die;
        }
        if ($attrs{IP_FRAGMENT} ne 'no' && $attrs{IP_FRAGMENT} ne 'first') {
            $flow{TP_SRC} = $flow{TP_DST} = 0;
        }
    } elsif ($attrs{DL_TYPE} eq 'non-ip') {
        $flow{DL_TYPE} = 0x5678;
    } else {
        die;
    }

    # Compose packet.
    my $packet = '';
    my $wildcards = 1 << 5 | 1 << 6 | 1 << 7 | 32 << 8 | 32 << 14 | 1 << 21;

    $packet .= pack_ethaddr($flow{DL_DST});
    $packet .= pack_ethaddr($flow{DL_SRC});
    if ($flow{DL_VLAN} != 0xffff) {
        $packet .= pack('nn', 0x8100, $flow{DL_VLAN});
    }
    my $len_ofs = length($packet);
    $packet .= pack('n', 0) if $attrs{DL_HEADER} =~ /^802.2/;
    if ($attrs{DL_HEADER} eq '802.2') {
        $packet .= pack('CCC', 0x42, 0x42, 0x03); # LLC for 802.1D STP.
    } else {
        if ($attrs{DL_HEADER} eq '802.2+SNAP') {
            $packet .= pack('CCC', 0xaa, 0xaa, 0x03); # LLC for SNAP.
            $packet .= pack('CCC', 0, 0, 0);          # SNAP OUI.
        }
        $packet .= pack('n', $flow{DL_TYPE});
        if ($attrs{DL_TYPE} eq 'ip') {
            my $ip = pack('CCnnnCCnNN',
                          (4 << 4) | 5,    # version, hdrlen
                          $flow{NW_TOS},   # type of service
                          0,               # total length (filled in later)
                          65432,           # id
                          0,               # frag offset
                          64,              # ttl
                          $flow{NW_PROTO}, # protocol
                          0,               # checksum
                          0x0a00020f,      # source
                          0xc0a80114);     # dest
            $wildcards &= ~( 1 << 5 | 63 << 8 | 63 << 14 | 1 << 21);
            if ($attrs{IP_OPTIONS} eq 'yes') {
                substr($ip, 0, 1) = pack('C', (4 << 4) | 8);
                $ip .= pack('CCnnnCCCx',
                            130,       # type
                            11,        # length
                            0x6bc5,    # top secret
                            0xabcd,
                            0x1234,
                            1,
                            2,
                            3);
            }

            if ($attrs{IP_FRAGMENT} ne 'no') {
                my (%frag_map) = ('first' => 0x2000, # more frags, ofs 0
                                  'middle' => 0x2111, # more frags, ofs 0x888
                                  'last' => 0x0222); # last frag, ofs 0x1110
                substr($ip, 6, 2)
                  = pack('n', $frag_map{$attrs{IP_FRAGMENT}});
            }
            if ($attrs{IP_FRAGMENT} eq 'no' || $attrs{IP_FRAGMENT} eq 'first') {
                if ($attrs{TP_PROTO} =~ '^TCP') {
                    my $tcp = pack('nnNNnnnn',
                                   $flow{TP_SRC},     # source port
                                   $flow{TP_DST},     # dest port
                                   87123455,          # seqno
                                   712378912,         # ackno
                                   (5 << 12) | 0x02 | 0x10, # hdrlen, SYN, ACK
                                   5823,                    # window size
                                   18923,                   # checksum
                                   12893); # urgent pointer
                    if ($attrs{TP_PROTO} eq 'TCP+options') {
                        substr($tcp, 12, 2) = pack('n', (6 << 12) | 0x02 | 0x10);
                        $tcp .= pack('CCn', 2, 4, 1975); # MSS option
                    }
                    $tcp .= 'payload';
                    $ip .= $tcp;
                    $wildcards &= ~(1 << 6 | 1 << 7);
                } elsif ($attrs{TP_PROTO} eq 'UDP') {
                    my $len = 15;
                    my $udp = pack('nnnn', $flow{TP_SRC}, $flow{TP_DST}, $len, 0);
                    $udp .= chr($len) while length($udp) < $len;
                    $ip .= $udp;
                    $wildcards &= ~(1 << 6 | 1 << 7);
                } elsif ($attrs{TP_PROTO} eq 'ICMP') {
                    $ip .= pack('CCnnn',
                                8,        # echo request
                                0,        # code
                                0,        # checksum
                                736,      # identifier
                                931);     # sequence number
                    $wildcards &= ~(1 << 6 | 1 << 7);
                } elsif ($attrs{TP_PROTO} eq 'other') {
                    $ip .= 'other header';
                } else {
                    die;
                }
            }
            substr($ip, 2, 2) = pack('n', length($ip));
            $packet .= $ip;
        }
    }
    if ($attrs{DL_HEADER} =~ /^802.2/) {
        my $len = length ($packet);
        $len -= 4 if $flow{DL_VLAN} != 0xffff;
        substr($packet, $len_ofs, 2) = pack('n', $len);
    }

    print join(' ', map("$_=$attrs{$_}", keys(%attrs))), "\n";
    print join(' ', map("$_=$flow{$_}", keys(%flow))), "\n";
    print "\n";

    print FLOWS pack('Nn',
                     $wildcards, # wildcards
                     1);         # in_port
    print FLOWS pack_ethaddr($flow{DL_SRC});
    print FLOWS pack_ethaddr($flow{DL_DST});
    print FLOWS pack('nCxnCCxxNNnn',
                     $flow{DL_VLAN},
                     0,          # DL_VLAN_PCP
                     $flow{DL_TYPE},
                     $flow{NW_TOS},
                     $flow{NW_PROTO},
                     inet_aton($flow{NW_SRC}),
                     inet_aton($flow{NW_DST}),
                     $flow{TP_SRC},
                     $flow{TP_DST});

    print PACKETS pack('NNNN',
                       0,                # timestamp seconds
                       0,                # timestamp microseconds
                       length($packet),  # bytes saved
                       length($packet)), # total length
                  $packet;
}

sub pack_ethaddr {
    local ($_) = @_;
    my $xx = '([0-9a-fA-F][0-9a-fA-F])';
    my (@octets) = /$xx:$xx:$xx:$xx:$xx:$xx/;
    @octets == 6 or die $_;
    my ($out) = '';
    $out .= pack('C', hex($_)) foreach @octets;
    return $out;
}

sub inet_aton {
    local ($_) = @_;
    my ($a, $b, $c, $d) = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
    defined $d or die $_;
    return ($a << 24) | ($b << 16) | ($c << 8) | $d;
}
