#!/usr/bin/env python
# Copyright (c) 2006 - 2013 Arista Networks, Inc.  All rights reserved.
# Arista Networks, Inc. Confidential and Proprietary.

import optparse, socket, random, array, fcntl, re, sys
import Ethxmit
import ctypes
import Tac
from eunuchs.if_packet_h import *
from eunuchs.if_arp_h import *
from eunuchs.if_ether_h import *
from eunuchs.in_h import *
from eunuchs.if_vlan_h import *

SIOCGIFHWADDR = 0x8927
SADATA = 18

class Values( optparse.Values):
   ''' 
   This class encapsulates the Values passed to optparse and allows us to track
   if the given option is specified explicitly in the command line or is just
   using the default value. Sometimes the checks are different depending on whether
   the option is explicitly specified in the command-line or not.
   '''
   def __init__(self, defaults):
      # keeps track to distinguish the values that are manipulated
      # programmatically as opposed to set during parsing of cmdline
      self.cmdlineOptionsParseComplete = False
      optparse.Values.__init__( self, defaults )
      self.specifiedInCmdline = set()

   def __setattr__(self, name, value):
      self.__dict__[ name ] = value
      if hasattr( self, "specifiedInCmdline" ) and \
             not self.cmdlineOptionsParseComplete:
         # This means that the option is being explicitly set from command-line
         # and not as a result of initializing with default values.
         self.specifiedInCmdline.add( name )

def intSplit( option, opt, value, parser ):
   ''' This function allows you to pass a comma separated list of 
   intergers and will result in a list of those integers being stored
   '''
   labels = []
   for num in value.split( ',' ):
      try:
         labels.append( int( num ) )
      except ValueError:
         raise ValueError( 
            "%s must be a comma separated list of integers" % option )
   
   setattr( parser.values, option.dest, labels )

parser = optparse.OptionParser()
parser.usage = "%prog [ OPTIONS ] interface"

# Options controlling the content of the packet.
defaultPktSize = 64
parser.add_option( "-s", "--size", type="int", default=defaultPktSize,
                   help="packet size, including CRC "
                   "(default: minimum possible size, typically %default)" )
parser.add_option( "-S", "--smac", 
                   help="source MAC address (default: use interface's MAC address)" )
parser.add_option( "--incSmac", type="int",
                   help=("increment source mac repeating " + 
                         "after N iterations (max 65535)") )
parser.add_option( "-D", "--dmac", default="ff:ff:ff:ff:ff:ff",
                   help="destination MAC address (default: %default)" )
parser.add_option( "--incDmac", type="int",
                   help=("increment dest mac repeating after N iterations" +
                         " (max 65535)") )
parser.add_option( "-e", "--ethertype",
                   help="ethertype (default: 0x1234)" )
parser.add_option( "-V", "--ip-version", dest="ip_version", type="int", default=4,
                   help="IP version either 4 or 6 (default: %default)" )
parser.add_option( "-p", "--progress", action="store_true",
                   help="display progress indication to stdout" )
parser.add_option( "--vlan", type="int", default=None,
                   help="vlan id (default: no tag, or 0 if --vpri is set)" )
parser.add_option( "--vpri", type="int", default=None,
                   help="vlan priority (default: no tag, or 0 if --vlan is set)" )
parser.add_option( "--vlan-tpid", type="int", default=ETH_P_8021Q,
                   help="vlan TPID (default: 0x%x)" % ETH_P_8021Q)

parser.add_option( "--inner-vlan", dest="inner_vlan", type="int", default=None,
                   help="inner vlan id "
                   "(default: no tag, or 0 if --inner-vpri is set)" )
parser.add_option( "--inner-vpri", dest="inner_vpri", type="int", default=None,
                   help="inner vlan priority "
                   "(default: no tag, or 0 if --inner-vlan is set)" )
parser.add_option( "--inner-vlan-tpid", type="int", default=ETH_P_8021Q,
                   help="inner vlan TPID (default: 0x%x)" % ETH_P_8021Q )

parser.add_option( "--ip-src", dest="ip_src", metavar="ADDR",
                   help="source IP address" )
parser.add_option( "--ip-dst", dest="ip_dst", metavar="ADDR",
                   help="destination IP address" )
parser.add_option( "--ip-protocol", dest="ip_protocol", metavar="PROTO",
                   help="IP protocol number or name (default: 63)" )
parser.add_option( "--ttl", type="int",
                   help="IP TTL (Time To Live) value (default: 64)" )
parser.add_option( "--tos", type="int",
                   help="IP TOS (Type Of Service) value (default: 0)" )
parser.add_option( "--flow-label", type="int",
                   help="IPv6 Flow Label value (default: 0)" )
parser.add_option( "--dont-fragment", action="store_true", default=0,
                   help="Set IP Don\'t Fragment flag as 1" )
parser.add_option( "--more-fragments", action="store_true", default=0,
                   help="Set IP More Fragments flag as 1" )
parser.add_option( "--frag-offset", default=0, type="int",
                   help="Fragment offset in units of 64-bits (e.g. 1 = 64 bits)" )
parser.add_option( "--ip-id", default=0, type="int",
                   help="IP header id" )
parser.add_option( "--router-alert", action="store_true",
                   help="add a Router Alert IP option" )
parser.add_option( "--cksum", default=0, type="int",
                   help="IP Checksum" )
parser.add_option( "--ipver", default=0, type="int",
                   help="IPv4 version to generate a version error" )
parser.add_option( "--iphlen", default=0, type="int",
                   help="IPv4 Header Length" )

parser.add_option( "--udp-sport", type="int", dest="udp_sport", metavar="PORT",
                   help="UDP source port number " )
parser.add_option( "--udp-dport", type="int", dest="udp_dport", metavar="PORT",
                   help="UDP destination port number" )
parser.add_option( "--icmp-type", type="int", dest="icmp_type",
                   help="ICMP type" )
parser.add_option( "--icmp-code", type="int", dest="icmp_code",
                   help="ICMP code" )

parser.add_option( "--inner-udp-sport", type="int", dest="inner_udp_sport", 
                   metavar="PORT", 
                   help="inner source UDP port for a tunneled IP/UDP packet" )
parser.add_option( "--inner-udp-dport", type="int", dest="inner_udp_dport",
                   metavar="PORT",
                   help="inner dest UDP port for a tunneled IP/UDP packet" )
parser.add_option( "--inner-tcp-sport", type="int", dest="inner_tcp_sport",
                   metavar="PORT",
                   help="inner source TCP port for a tunneled IP/TCP packet" )
parser.add_option( "--inner-tcp-dport", type="int", dest="inner_tcp_dport",
                   metavar="PORT",
                   help="inner dest TCP port for a tunneled IP/TCP packet" )
parser.add_option( "--inner-ip-protocol", dest="inner_ip_protocol", metavar="PROTO",
                   help="IP protocol number or name (default: 63) for a "
                   "tunneled IP packet" )
parser.add_option( "--inner-ttl", type="int",
                   help="IP TTL (Time To Live) value (default: 64) for a "
                   "tunneled IP packet" )
parser.add_option( "--inner-tos", type="int",
                   help="IP TOS (Type Of Service) value (default: 0) for a "
                   "tunneled IP packet" )
parser.add_option( "--inner-flow-label", type="int",
                   help="IPv6 Flow Label value (default: 0) for a "
                   "tunneled IP packet" )
parser.add_option( "--inner-ip-src", dest="inner_ip_src", metavar="ADDR", 
                   help="inner source IP address for a tunneled IP packet" )
parser.add_option( "--inner-ip-dst", dest="inner_ip_dst", metavar="ADDR",
                   help="inner destination IP address for a tunneled IP packet" )
parser.add_option( "--inner-smac", dest="inner_smac", 
                   default="00:00:aa:aa:aa:aa",
                   help="source MAC address for inner header of a tunneled packet "
                   "(default: %default)")
parser.add_option( "--inner-dmac", dest="inner_dmac", 
                   default="ff:ff:ff:ff:ff:ff",
                   help="destination MAC address for inner header of a "
                   "tunneled packet (default %default)" )
parser.add_option( "--inner-ethertype", dest="inner_ethertype",
                   help="ethertype for inner header of a tunneled packet "
                   "(default: 0x0800 for IP)" )
parser.add_option( "--inner-ip-version", dest="inner_ip_version", type="int",
                   default=4, help="IP version either 4 or 6 (default: %default) "
                   "of a tunneled IP packet" )
parser.add_option( "--inner-dont-fragment", action="store_true", default=0,
                   help="Set IP Don\'t Fragment flag as 1 for a tunneled IP packet" )
parser.add_option( "--inner-more-fragments", action="store_true", default=0,
                   help="Set IP More Fragments flag as 1 for a tunneled IP packet" )
parser.add_option( "--inner-frag-offset", default=0, type="int",
                   help="Fragment offset in units of 64-bits (e.g. 1 = 64 bits) " \
                      "for a tunneled IP packet" )
parser.add_option( "--inner-ip-id", default=0, type="int",
                   help="IP header id for a tunneled IP packet" )
parser.add_option( "--inner-arp", dest="inner_arp",
                   help='ARP: "request", "reply"' )
parser.add_option( "--inner-ndp", dest="inner_ndp",
                   help='--inner-ndp <"nbr-solicit", "nbr-advt", "rtr-solicit">' )

parser.add_option( "--vxlan-vni", dest="vxlan_vni",
                   help="vni for Vxlan packet (adds vxlan header). "
                   "Also: Sets IP_PROTO to udp and dest port to vxlan port, "
                   "unless overridden" )
parser.add_option( "--vxlan-flags", dest="vxlan_flags", type="int", default=0x08,
                   help="Sets VXLAN flags (first 8 bits of VXLAN header)" )
parser.add_option( "--payload-vlan", dest="payload_vlan", type="int", default=None,
                   help="Payload vlan id "
                   "(default: no tag, or 0 if --payload-vpri is set)" )
parser.add_option( "--payload-vpri", dest="payload_vpri", type="int", default=None,
                   help="Payload vlan priority "
                   "(default: no tag, or 0 if --payload-vlan is set)" )
parser.add_option( "--payload-vlan-tpid", type="int", default=ETH_P_8021Q,
                   help="Payload vlan TPID (default: 0x%x)" % ETH_P_8021Q )
parser.add_option( "--payload-inner-vlan", dest="payload_inner_vlan", type="int",
                   default=None,
                   help="Payload inner vlan id "
                   "(default: no tag, or 0 if --payload-inner-vpri is set)" )
parser.add_option( "--payload-inner-vpri", dest="payload_inner_vpri",
                   type="int", default=None,
                   help="Payload inner vlan priority "
                   "(default: no tag, or 0 if --payload-inner-vlan is set)" )
parser.add_option( "--payload-inner-vlan-tpid", dest="payload_inner_vlan_tpid",
                   type="int", default=ETH_P_8021Q,
                   help="Payload inner vlan TPID (default: 0x%x)" % ETH_P_8021Q )
parser.add_option( "--mpls-label", metavar="LABEL", type="string",
                   help="A comma separated list of MPLS (Multiprotocol "
                   "Label Switching) labels to be added to the packet. "
                   "Specified from top to bottem",
                   action='callback', callback=intSplit )
parser.add_option( "--mpls-ttl", type="string",
                   help="comma separated MPLS TTL (Time To Live) value "
                   "(default: 64)",
                   action='callback', callback=intSplit )
parser.add_option( "--mpls-exp", type="string",
                   help="comma separted MPLS EXP (Experimental ) value "
                   "(default: 0)",
                   action='callback', callback=intSplit )
parser.add_option( "--gre", action="store_true", help="enable Generic Routing "
                   "Encapsulation (GRE)" )
parser.add_option( "--gre-over-mpls", dest="gre_mpls", action="store_true",
                   help="enable Generic Routing Encapsulation (GRE) over MPLS" )
parser.add_option( "--eth-over-gre", dest="eth_gre", action="store_true",
                   help="enable Ethernet over Generic Routing Encapsulation (GRE)" )
parser.add_option( "--udp-over-mpls", dest="udp_mpls", action="store_true",
                   help="enable UDP over MPLS" )
parser.add_option( "--eth-over-mpls", dest="eth_mpls", action="store_true",
                   help="enable Ethernet over MPLS" )
parser.add_option( "--mpls-pw-cw-type", dest="mpls_pw_cw_type", type="int",
                   default=0,
                   help="set the MPLS Pseudowire Control Word Type "
                   "(default %default)" )
parser.add_option( "--mpls-pw-cw-flags", dest="mpls_pw_cw_flags", type="int",
                   default=0,
                   help="set the MPLS Pseudowire Control Word Flags "
                   "(default %default)" )
parser.add_option( "--mpls-pw-cw-frg", dest="mpls_pw_cw_frg", type="int",
                   default=0,
                   help="set the MPLS Pseudowire Control Word Fragment "
                  "(default %default)" )
parser.add_option( "--mpls-pw-cw-seqno", dest="mpls_pw_cw_seqno", type="int",
                   default=0,
                   help="set the MPLS Pseudowire Control Word Sequence Number "
                   "(default %default)" )
parser.add_option( "--mpls-pw-cw-raw", dest="mpls_pw_cw_raw", type="string",
                   default=None,
                   help="set the raw MPLS Pseudowire Control Word "
                        "(4-byte hex string)" )
parser.add_option( "--gre-protocol", metavar="PROTO",
                   help="set the protocol type of the GRE payload "
                   "(default: 0x0800 (IP))" )
parser.add_option( "--gre-version", metavar="VER", type="int", default=0,
                   help="set the GRE version number (default %default)" )
parser.add_option( "--gre-checksum", metavar="VALUE", type="int",
                   help="enable Checksum Present bit in GRE header and insert "
                   "checksum with VALUE, not enabled by default" )
parser.add_option( "--gre-key", metavar="VALUE", type="int",
                   help="enable Key Present bit in GRE header and insert "
                   "key with VALUE, not enabled by default" )
parser.add_option( "--gre-sequence", metavar="VALUE", type="int",
                   help="enable Sequence Number Present bit in GRE header and "
                   "insert sequence number with VALUE, not enabled by default" )

parser.add_option( "--data-type", type="choice", dest="dataType",
                   choices=[ 'constant', 'incrementing', 'random', 'raw' ],
                   default='incrementing',
                   metavar="TYPE",
                   help="one of: constant, incrementing, random, raw "
                   "(default: %default)" )
parser.add_option( "--data-value", type="str", dest="dataValue", default="0",
                   metavar="VALUE",
                   help="payload value if data-type is 'constant' or 'raw' "
                   "constant: single byte repeated to fill payload SIZE "
                   "raw: hex string for payload, zero padded if short "
                   "(default: %default)" )
parser.add_option( "--random-seed", type="int", dest="randomSeed",
                   metavar="SEED",
                   help="seed for random number generator" )

parser.add_option( "--vnTagSVif", type="int", default=None,
                   help="source Vif, the vif_id of the port that add the vntag" )
parser.add_option( "--vnTagDVif", type="int", default=None,
                   help="destination Vif, identifies the destination port" )
parser.add_option( "--br", type="int", default=None,
                   help="IEEE 802.1BR E-tag" )

# Options controlling how many packets are transmitted, and when.
parser.add_option( "-n", "--num", type="int", default=1,
                   help="total number of packets to send (default: %default)" )
parser.add_option( "--seq", action="store_true",
                   help="replace last four bytes of user payload with " \
                        "incrementing sequence value" )
parser.add_option( "-b", "--burst", type="int", default=1,
                   help="number of packets in each burst (default: %default)" )
parser.add_option( "--sleep", type="float", default=0.0,
                   help="how long to sleep (in seconds) after each burst "
                   "(default: %default)")
parser.add_option( "-c", "--continuous", action="store_true",
                   help="run continuously" )
parser.add_option( "-B", "--sndbuf", type="int",
                   help="socket send-buffer size to use" )

parser.add_option( "--arp", dest="arp",
                   help='ARP: "request", "reply"' )
parser.add_option( "--rarp", dest="rarp",
                   help='RARP: "request", "reply"' )
parser.add_option( "--arp-sha", dest="arpSha", help="Sender Hardware Address in ARP"
                   "/RARP message (default: smac)" )
parser.add_option( "--ndp", dest="ndp",
                   help='--ndp <"nbr-solicit", "nbr-advt", "rtr-solicit">' )
parser.add_option( "--isl-f1", dest="f1",
                         help="f1 field in ISL header" )
parser.add_option( "--isl-f2", dest="f2",
                         help="f2 field in ISL header" )
parser.add_option( "--isl-sglort", dest="sglort",
                         help="sglort field in ISL header" )
parser.add_option( "--isl-dglort", dest="dglort",
                         help="dglort field in ISL header" )

parser.add_option( "--txraw-fap", dest="txrawFap",
                         help="src FAP in txraw header" )
parser.add_option( "--svtag-signature", type="int",
                   help="signature in SvTag" )
parser.add_option( "--svtag-channel-id", type="int",
                   help="channelId in SvTag" )
parser.add_option( "--itmh-sysDestPort", dest="sysDestPort", type="int",
                         help="sysDestPort in ITMH header" )

parser.add_option( "--stdout", action="store_true",
                         help="write packet to stdout" )
parser.add_option( "--hexDump", action="store_true",
                         help="print the hex value of the packet" )

parser.add_option( "--tcp-flags", help="Set TCP control flags: U,A,P,R,S,F",
                   type="string", dest="tcp_flags" )
parser.add_option( "--tcp-sport", help="TCP source port number", type="int",
                   dest="tcp_sport" )
parser.add_option( "--tcp-dport", help="TCP destination port number", type="int",
                   dest="tcp_dport" )

cmdlineSpecifiedValues = Values( parser.defaults )
( options, args ) = parser.parse_args( values=cmdlineSpecifiedValues )
cmdlineSpecifiedValues.cmdlineOptionsParseComplete = True

if len( args ) == 0:
   if not options.hexDump:
      parser.error( "expected output interface" )
   else:
      interface = "hexDump"
elif len( args ) == 1:
   interface = args[ 0 ]
else:
   parser.error( "unexpected args: %s" % args[ 1: ] )


# Set proctitle to 'ethxmit' so that killall, etc. can work.  Note that the only
# parameter we include in the title is the interface name.  This is because we need
# the interface name to be present in order to determine which old ethxmit processes
# to kill on a MAC/PHY concentrator at the start of a new product test, but
# Tac.setproctitle() truncates the title to 12 characters due to BUG743.  This
# workaround is only good enough for interface names of 4 characters or less (and
# therefore fails for interface names such as et16_1 on a Meritage DUT).  See
# BUG43789.
Tac.setproctitle( 'ethxmit ' + interface )

if options.randomSeed is not None:
   random.seed( options.randomSeed )

def optionSpecifiedInCmdline( optionName ):
   return optionName in cmdlineSpecifiedValues.specifiedInCmdline

def getHwaddr( interface ):
   s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
   buf = array.array( 'c', interface + '\0' * 32 )
   fcntl.ioctl( s.fileno(), SIOCGIFHWADDR, buf )
   return Ethxmit.bytesToMacaddr( buf[ SADATA : SADATA + 6 ] )

def optionToValue( optionString, defaultValue, optionName, namePrefix ):
   if optionString is None:
      return defaultValue

   try:
      return int( optionString, 0 )
   except ValueError:
      pass

   s = optionString.upper()
   m = re.match( '[A-Z0-9_]+$', s )
   if m:
      try:
         return eval( namePrefix + s )
      except NameError:
         pass

   parser.error(
      'option %s: invalid integer value or name: %r' % ( optionName, optionString ) )

def ipProtocolOptionToValue( optionString, defaultValue ):
   return optionToValue( optionString, defaultValue, '--ip-protocol', 'IPPROTO_' )

def greProtocolOptionToValue( optionString, defaultValue ):
   return optionToValue( optionString, defaultValue, '--gre-protocol', 'ETH_P_' )

def ethertypeOptionToValue( optionString, defaultValue ):
   return optionToValue( optionString, defaultValue, '--ethertype', 'ETH_P_' )

def buildInnerPktHeader( options ):
   # options.size is the size of the entire packet on the wire after all
   # encapsulation.
   # Encapsulating packet with inner-vlan is not currently implemented
   innerEthHdr = ''
   innerArp = ''
   innerNdp = ''
   def innerEthHdrRequired():
      if options.vxlan_vni:
         return True
      elif options.gre:
         gre_proto = greProtocolOptionToValue( options.gre_protocol, ETH_P_IP )
         # check if gre and gre protocol is TEB: transparent Ethernet bridging
         if gre_proto == ETH_P_TEB:
            return True
      elif options.eth_mpls:
         return True
      elif options.eth_gre:
         options.gre = True
         return True
      return False

   if innerEthHdrRequired():
      if options.inner_ip_src or options.inner_ip_dst:
         ethertype = ETH_P_IP
         if options.inner_ip_version == 6:
            ethertype = ETH_P_IPV6
      else:
         ethertype = ethertypeOptionToValue( options.inner_ethertype, 
                                             0x1234 )
      if options.inner_arp:
         if options.inner_arp == 'request':
            innerArp = Ethxmit.buildArpPacket( options.inner_smac,
                                               options.inner_ip_dst,
                                               options.inner_dmac,
                                               options.inner_ip_src )
         elif options.inner_arp == 'reply':
            innerArp = Ethxmit.buildArpPacket( options.inner_smac,
                                               options.inner_ip_dst,
                                               options.inner_dmac,
                                               options.inner_ip_src,
                                               requestOrReply='reply' )
         ethertype = ethertypeOptionToValue( options.inner_ethertype, ETH_P_ARP )
      if options.inner_ndp:
         options.inner_ip_version = 6
         ethertype = ETH_P_IPV6
         innerNdp = Ethxmit.buildNdpPacket( options.inner_ndp,
                                            options.inner_smac,
                                            options.inner_dmac,
                                            options.inner_ip_dst,
                                            options.inner_ip_src )
         if options.inner_ndp == 'nbr-solicit':
            # If dmac is broadcast, modify it as Multicast mac, which is derived from
            # the dest-IP. Otherwise use the (possibly) unicast mac from the command
            # line.
            if options.inner_dmac == 'ff:ff:ff:ff:ff:ff':
               options.inner_dmac = Ethxmit.getNsMcastMac( socket.inet_pton(
                              socket.AF_INET6, options.inner_ip_dst ) )

      innerEthHdr = Ethxmit.buildEthernetHeader( options.inner_dmac,
                                                 options.inner_smac,
                                                 ethertype, options.payload_vlan,
                                                 options.payload_vpri,
                                                 options.payload_inner_vlan,
                                                 options.payload_inner_vpri,
                                                 options.payload_vlan_tpid,
                                                 options.payload_inner_vlan_tpid,
                                                 options.svtag_signature,
                                                 options.svtag_channel_id )

   innerIpProtocol = ipProtocolOptionToValue( options.inner_ip_protocol, 63 )
   innerTtl = options.inner_ttl if ( options.inner_ttl is not None ) else 64
   innerTos = options.inner_tos if ( options.inner_tos is not None ) else 0
   innerFlowLabel = options.inner_flow_label if ( options.inner_flow_label
                                                  is not None ) else 0
   innerHdr = ''
   pktSize = options.size
   if options.inner_udp_sport is not None or options.inner_udp_dport is not None:
      innerIpProtocol = IPPROTO_UDP
   if options.inner_tcp_sport is not None or options.inner_tcp_dport is not None:
      innerIpProtocol = IPPROTO_TCP
   innerPktHdr = innerEthHdr
   if options.inner_arp:
      innerPktHdr += innerArp
   if options.inner_ndp:
      innerPktHdr += innerNdp
   elif options.inner_ip_src or options.inner_ip_dst:
      ipHdrSize = Ethxmit.IP6_HDR_LEN if options.inner_ip_version == 6 \
                  else Ethxmit.IP_HDR_LEN
      minIpPktSize = Ethxmit.ETH_HLEN + ipHdrSize + Ethxmit.ETH_FCS_LEN
      pktSize = min( pktSize, minIpPktSize )
      if innerIpProtocol == IPPROTO_UDP:
         # Minimum IP/UDP Ethernet packet size is 46
         # 14 (Eth) + 20/40 (IPv4/IPv6) + 8 (UDP) + 4 (FCS)
         minIpUdpPktSize = Ethxmit.ETH_HLEN + ipHdrSize + \
             Ethxmit.UDP_HDR_LEN + Ethxmit.ETH_FCS_LEN
         pktSize = max( pktSize, minIpUdpPktSize )
         if options.inner_udp_dport is None:
            options.inner_udp_dport = Ethxmit.UDP_DPORT_DEFAULT
         if options.inner_udp_sport is None:
            options.inner_udp_sport = Ethxmit.UDP_SPORT_DEFAULT
         innerHdr = Ethxmit.buildUdpPacket(
            pktSize, options.inner_udp_sport, options.inner_udp_dport,
            options.inner_ip_src, options.inner_ip_dst,
            ipProtocol=innerIpProtocol, ttl=innerTtl, tos=innerTos,
            version=options.inner_ip_version,
            dontFragFlag=options.inner_dont_fragment,
            moreFragsFlag=options.inner_more_fragments,
            fragOffset=options.inner_frag_offset,
            ipId=options.inner_ip_id,
            flowLabel=innerFlowLabel )
      elif innerIpProtocol == IPPROTO_TCP:
         # Minimum IP/TCP Ethernet packet size is 58
         # 14 (Eth) + 20/40 (IPv4/IPv6) + 20 (TCP) + 4(FCS)
         minIpTcpPktSize = Ethxmit.ETH_HLEN + ipHdrSize + \
            Ethxmit.TCP_HDR_LEN + Ethxmit.ETH_FCS_LEN
         pktSize = max( pktSize, minIpTcpPktSize )
         if options.inner_tcp_dport is None:
            options.inner_tcp_dport = Ethxmit.TCP_DPORT_DEFAULT
         if options.inner_tcp_sport is None:
            options.inner_tcp_sport = Ethxmit.TCP_SPORT_DEFAULT
         innerHdr = Ethxmit.buildTcpPacket(
            size=pktSize,
            ipSrc=options.inner_ip_src,
            ipDst=options.inner_ip_dst,
            ipVersion=options.inner_ip_version,
            tos=innerTos,
            ipId=options.inner_ip_id,
            dontFragFlag=options.inner_dont_fragment,
            moreFragsFlag=options.inner_more_fragments,
            fragOffset=options.inner_frag_offset,
            ttl=innerTtl,
            tcpSPort=options.inner_tcp_sport,
            tcpDPort=options.inner_tcp_dport )
      else:
         innerHdr = Ethxmit.buildIpPacket( 
            pktSize, options.inner_ip_src, options.inner_ip_dst,
            ipProtocol=innerIpProtocol, ttl=innerTtl, tos=innerTos,
            version=options.inner_ip_version,
            dontFragFlag=options.inner_dont_fragment,
            moreFragsFlag=options.inner_more_fragments,
            fragOffset=options.inner_frag_offset,
            ipId=options.inner_ip_id,
            flowLabel=innerFlowLabel )

      innerPktHdr += innerHdr
   return innerPktHdr

def validatedMinPktSize( options, minPktSize ):
   if optionSpecifiedInCmdline( 'size' ) and options.size < minPktSize:
      raise ValueError( "size too small: must be at least %d" % minPktSize )
   return max( options.size, minPktSize )

def buildVxlanPacket( options, ipOptionData, innerPktHdr ):
   # add Vxlan header 
   vxlanHeader = Ethxmit.buildVxlanHeader( flags=options.vxlan_flags, 
                                           vni=int( options.vxlan_vni ) )
   # Minimum VXLAN packet size has to be 68
   # 14 (Eth) + 20/40 (IPv4/IPv6) + IP-Options (optional) +
   # 8 (UDP) + 8 (VXLAN) + 14 (Eth) + 4 (FCS)
   minVxlanPktSize = Ethxmit.ETH_HLEN + ipHdrSize( options ) + \
       len( ipOptionData ) + Ethxmit.UDP_HDR_LEN + Ethxmit.VXLAN_HDR_LEN + \
       len( innerPktHdr ) +  Ethxmit.ETH_FCS_LEN
   size = validatedMinPktSize( options, minVxlanPktSize )

   # Construct the entire packet, using the inner packet (including 
   # vxlan header ) as data value
   # totalsize must include Outer IP header, Outer UDP header, Vxlan Header
   # and inner headers + data.
   vxlanPktHdr = vxlanHeader + innerPktHdr

   if options.udp_dport is None:
      options.udp_dport = Ethxmit.UDP_DPORT_VXLAN
   if options.udp_sport is None:
      options.udp_sport = Ethxmit.UDP_SPORT_DEFAULT

   data = Ethxmit.buildUdpPacket(
      size, options.udp_sport, options.udp_dport,
      options.ip_src, options.ip_dst,
      ipProtocol=IPPROTO_UDP, ttl=options.ttl, tos=options.tos, 
      ipOptionData=ipOptionData, initialData=vxlanPktHdr, 
      version=options.ip_version, dontFragFlag=options.dont_fragment,
      moreFragsFlag=options.more_fragments,
      fragOffset=options.frag_offset,
      ipId=options.ip_id,
      flowLabel=options.flow_label )

   return data

def buildMplsOverUdpPacket( options, ipProtocol, ipOptionData, innerPktHdr ):

   mplsHeader = ''

   # Minimum UDP over mpsl packet size is 70 bytes
   # ETH(14) + Ipv4(20) + UDP(8) + MPLS(4) + IP/IPv6(20/40) + IP options + FCS(4)
   minUdpPktSize = Ethxmit.ETH_HLEN + ipHdrSize( options ) + Ethxmit.UDP_HDR_LEN + \
         4 + len( innerPktHdr ) + Ethxmit.ETH_FCS_LEN
   size = validatedMinPktSize( options, minUdpPktSize )
   udpHeader = Ethxmit.buildUdpHeader( size, options.udp_sport, options.udp_dport )

   mplsHeader = ""
   for l in range( 0, len( options.mpls_label ) - 1 ):
      mplsHeader += Ethxmit.buildMplsHeader( options.mpls_label[ l ],
                                             options.mpls_ttl[ l ],
                                             options.mpls_exp[ l ],
                                             mpls_bos=False )
   mplsHeader += Ethxmit.buildMplsHeader( options.mpls_label[ -1 ],
                                          options.mpls_ttl[ -1 ],
                                          options.mpls_exp[ -1 ] )

   # Construct the entire packet, using the inner packet as data value
   # totalsize must include Outer IP header, Outer UDP header, Vxlan Header
   # and inner headers + data.
   udpPktHdr = udpHeader + mplsHeader + innerPktHdr

   data = Ethxmit.buildIpPacket( size, options.ip_src, options.ip_dst,
      ipProtocol=ipProtocol, ttl=options.ttl, tos=options.tos, 
      ipOptionData=ipOptionData, dataType=options.dataType,
      dataValue=options.dataValue, initialData=udpPktHdr, version=options.ip_version,
      dontFragFlag=options.dont_fragment, extraHeaderSize=0,
      flowLabel=options.flow_label )

   return data

def buildGrePacket( options, ipProtocol, ipOptionData, innerPktHdr ):
   # add GRE header 
   greHeader = Ethxmit.buildGreHeader( greVersion=options.gre_version,
                                       greProtocol=options.gre_protocol,
                                       greChecksum=options.gre_checksum,
                                       greKey=options.gre_key,
                                       greSequence=options.gre_sequence )
   mplsHeader = ''
   # Minimum GRE packet size is 62 bytes
   # 14 (Eth) + 20/40 (IPv4/IPv6) + IP-Options (optional) +
   # 4 (GRE) + 20/40 (IPv4/IPv6) + 4 (FCS)
   minGrePktSize = Ethxmit.ETH_HLEN + ipHdrSize( options ) + len( ipOptionData ) + \
                   4 + len( innerPktHdr ) + Ethxmit.ETH_FCS_LEN

   if options.gre_checksum:
      minGrePktSize += 4
   if options.gre_key:
      minGrePktSize += 4
   if options.gre_sequence:
      minGrePktSize += 4
   if options.mpls_label and not options.gre_mpls:
      minGrePktSize += 4
   size = validatedMinPktSize( options, minGrePktSize )

   extraHeaderSize = 4 if options.mpls_label and options.gre_mpls else 0

   if options.mpls_label and not options.gre_mpls:
      mplsHeader = ""
      for l in range( 0, len( options.mpls_label ) - 1 ):
         mplsHeader += Ethxmit.buildMplsHeader( options.mpls_label[ l ],
                                                options.mpls_ttl[ l ],
                                                options.mpls_exp[ l ],
                                                mpls_bos=False )
      mplsHeader += Ethxmit.buildMplsHeader( options.mpls_label[ -1 ],
                                             options.mpls_ttl[ -1 ],
                                             options.mpls_exp[ -1 ] )

   # Construct the entire packet, using the inner packet as data value
   # totalsize must include Outer IP header, Outer UDP header, Vxlan Header
   # and inner headers + data.
   grePktHdr = greHeader + mplsHeader + innerPktHdr

   data = Ethxmit.buildIpPacket( size, options.ip_src, options.ip_dst,
      ipProtocol=ipProtocol, ttl=options.ttl, tos=options.tos, 
      ipOptionData=ipOptionData, dataType=options.dataType,
      dataValue=options.dataValue, initialData=grePktHdr, version=options.ip_version,
      dontFragFlag=options.dont_fragment, extraHeaderSize=extraHeaderSize,
      flowLabel=options.flow_label )

   return data

def ipHdrSize( options ):
   # Return IP Header length based on option if choosen
   return Ethxmit.IP6_HDR_LEN if options.ip_version == 6 else Ethxmit.IP_HDR_LEN

def doValidateCmdlineOptions( options ):
   # 8021q is considered layer 2.5 and isn't counted
   # as part of size calculations of ethernet header
   # Adjust size settings to account for this to match
   # user expected behavior that packet size includes vlan
   if ( options.vlan is not None ) or ( options.vpri is not None ):
      options.size -= VLAN_HLEN
   if ( options.inner_vlan is not None ) or ( options.inner_vpri is not None ):
      options.size -= VLAN_HLEN

   if ( options.vlan_tpid != ETH_P_8021Q ) and ( not options.vlan ):
      raise ValueError( "must enable --vlan to use --vlan-tpid" )

   if ( options.inner_vlan_tpid != ETH_P_8021Q ) and ( not options.inner_vlan ):
      raise ValueError( "must enable --inner-vlan to use --inner-vlan-tpid" )

   if ( options.payload_vlan_tpid != ETH_P_8021Q ) and not ( options.payload_vlan ):
      raise ValueError( "must enable --payload-vlan to use --payload-vlan-tpid" )

   if ( options.payload_vlan ) and not ( options.vxlan_vni or options.eth_mpls ):
      raise ValueError( "Payload vlan applicable only for Vxlan or "
                        "Ethernet over MPLS packets" )

   if ( options.payload_inner_vlan ) and not ( options.eth_mpls ):
      raise ValueError( "Payload inner vlan applicable only for "
                        "Ethernet over MPLS packets" )

   if ( options.payload_inner_vlan_tpid != ETH_P_8021Q ) and not \
      ( options.payload_inner_vlan ):
      raise ValueError( "must enable --payload-inner-vlan to use "
                         "--payload-inner-vlan-tpid" )

   if options.ip_version not in [ 4, 6 ]:
      raise ValueError( "ip version must be either 4 or 6" )

   if options.vxlan_vni and int( options.vxlan_vni ) not in range( 0, 16777216 ):
      raise ValueError( "vxlan vni values supported from 0 to 16777215" )

   if options.vxlan_flags & ~0xff:
      raise ValueError( "must set vxlan flags to an 8-bit value" )

   if options.ip_src or options.ip_dst:
      if not options.ip_src or not options.ip_dst:
         raise ValueError( "must specify both --ip-src and --ip-dst, or neither" )

   if options.inner_arp:
      if ( not options.inner_ip_src or not options.inner_ip_dst or
           not options.inner_smac ):
         raise ValueError(
            "must specify --inner-ip-src, --inner-ip-dst, and --inner-smac"
            " for inner arp" )

   if options.inner_ip_src or options.inner_ip_dst:
      if not options.inner_ip_src or not options.inner_ip_dst:
         raise ValueError(
            "must specify both --inner-ip-src and --inner-ip-dst, or neither" )

   if options.inner_udp_sport is not None or options.inner_udp_dport is not None:
      if options.inner_ip_src is None or options.inner_ip_dst is None:
         raise ValueError(
            "cannot specify UDP ports for non-IP inner packet" )

   if options.inner_udp_sport is not None or options.inner_udp_dport is not None:
      if options.inner_udp_sport is None or not options.inner_udp_dport is None:
         raise ValueError(
            "must specify both --inner-udp-sport and --inner-udp-dport, or neither" )

   if options.inner_tcp_sport is not None or options.inner_tcp_dport is not None:
      if options.inner_ip_src is None or options.inner_ip_dst is None:
         raise ValueError(
            "cannot specify TCP ports for non-IP inner packet" )

   if options.inner_tcp_sport is not None or options.inner_tcp_dport is not None:
      if options.inner_tcp_sport is None or options.inner_tcp_dport is None:
         raise ValueError(
            "must specify both --inner-tcp-sport and --inner-tcp-dport, or neither" )

   if options.udp_sport is not None or options.udp_dport is not None:
      if options.udp_sport is None or options.udp_dport is None:
         raise ValueError(
            "must specify both --udp-sport and --udp-dport, or neither" )

   if options.inner_flow_label is not None and options.inner_ip_version != 6:
      raise ValueError(
         "cannot specify --inner-flow-label when --inner-ip-version != 6" )

   if options.flow_label is not None and options.ip_version != 6:
      raise ValueError( "cannot specify --flow-label when --ip-version != 6" )

   if options.mpls_ttl and not options.mpls_label:
      raise ValueError( "must specify --mpls-label with --mpls-ttl option" )

   if options.mpls_ttl and len( options.mpls_ttl ) != len( options.mpls_label ):
      raise ValueError( "must specify a ttl for every label or no ttl" )

   if options.mpls_exp and not options.mpls_label:
      raise ValueError( "must specify --mpls-label with --mpls-exp option" )

   if options.mpls_exp and len( options.mpls_exp ) != len( options.mpls_label ):
      raise ValueError( "must specify an exp for every label or no exp" )

   if options.gre_mpls and not options.mpls_label:
      raise ValueError( "must specify --mpls-label with --gre-over-mpls option" )

   if options.udp_mpls and not options.mpls_label:
      raise ValueError( "must specify --mpls-label with --udp-over-mpls option" )

   if options.eth_mpls and not options.mpls_label:
      raise ValueError( "must specify --mpls-label with --eth-over-mpls option" )

   if options.gre_mpls and options.eth_mpls:
      raise ValueError( "cannot specify both --gre-over-mpls and "
                        "--eth-over-mpls option" )

   if options.eth_mpls and ( options.ip_src or options.ip_dst ):
      raise ValueError( "cannot specify --eth-over-mpls with either "
                        "--ip-dst or --ip-src option" )

   if not options.eth_mpls and \
      ( optionSpecifiedInCmdline( 'mpls_pw_cw_type' ) or \
        optionSpecifiedInCmdline( 'mpls_pw_cw_flags' ) or \
        optionSpecifiedInCmdline( 'mpls_pw_cw_frg' ) or \
        optionSpecifiedInCmdline( 'mpls_pw_cw_seqno' ) or \
        options.mpls_pw_cw_raw ):
      raise ValueError( "must specify --eth-over-mpls to use --mpls-pw-... options" )

   if options.mpls_pw_cw_raw and \
      ( optionSpecifiedInCmdline( 'mpls_pw_cw_type' ) or \
        optionSpecifiedInCmdline( 'mpls_pw_cw_flags' ) or \
        optionSpecifiedInCmdline( 'mpls_pw_cw_frg' ) or \
        optionSpecifiedInCmdline( 'mpls_pw_cw_seqno' ) ):
      raise ValueError( "cannot specify both --mpls-pw-cw-raw ( hex string format ) "
                        "and --mpls-pw-cw-... options" )

   if options.gre_checksum or options.gre_key or options.gre_sequence:
      if not options.gre and not options.gre_mpls:
         raise ValueError(
            "must specify --gre or --gre-over-mpls to use --gre-... options" )

   if options.ip_protocol and (options.gre or options.gre_mpls):
      raise ValueError( "cannot specify IP protocol for GRE as it must be 47" )

   if options.gre_protocol and options.gre and options.mpls_label:
      raise ValueError( "GRE protocol must be MPLS unicast (0x8847) for MPLSoGRE" )

   if options.gre and options.gre_mpls:
      raise ValueError( "please specify one of --gre or --gre-over-mpls, not both" )

   if options.txrawFap or options.sysDestPort:
      if not options.txrawFap:
         raise ValueError( "must specify --txrawFap when using --sysDestPort" )
      if not options.sysDestPort:
         raise ValueError( "must specify --sysDestPort when using --txrawFap" )
   if options.svtag_signature:
      if not options.svtag_channel_id:
         raise ValueError( "must specify " + \
                           "--svtag-channel-id when using --svtag-signature" )

try:
   doValidateCmdlineOptions( options )
   if options.smac:
      smac = options.smac
   elif options.hexDump:
      # if hexDump is enabled, we don't have an interface to get the smac from
      # so we hardcode it to a value
      smac = "0.1.2"
   else:
      smac = getHwaddr( interface )

   innerPktHdr = buildInnerPktHeader( options )

   numLabels = len( options.mpls_label ) if options.mpls_label else 0
   options.mpls_ttl = options.mpls_ttl if ( options.mpls_ttl is not None ) \
       else [ 64 ] * numLabels
   options.mpls_exp = options.mpls_exp if ( options.mpls_exp is not None ) \
       else [ 0 ] * numLabels

   if options.ip_src or options.ip_dst:
      if options.gre or options.gre_mpls or options.eth_gre:
         ipProtocol = IPPROTO_GRE
      elif options.udp_mpls:
         ipProtocol = IPPROTO_UDP
      else:
         ipProtocol = ipProtocolOptionToValue( options.ip_protocol, 63 )
      if options.mpls_label and options.gre:
         options.gre_protocol = ETH_P_MPLS_UC
      elif options.eth_gre:
         options.gre_protocol = ETH_P_TEB
      else:
         options.gre_protocol = greProtocolOptionToValue( options.gre_protocol,
                                                          ETH_P_IP )
      options.ttl = options.ttl if ( options.ttl is not None ) else 64
      options.tos = options.tos if ( options.tos is not None ) else 0
      options.flow_label = options.flow_label if ( options.flow_label is not None ) \
            else 0
      ipOptionData = '\x94\x04\x00\x00' if options.router_alert else ''
      if options.vxlan_vni:
         data = buildVxlanPacket( options, ipOptionData, innerPktHdr )
      elif options.gre or options.gre_mpls:
         data = buildGrePacket( options, ipProtocol, ipOptionData, innerPktHdr )
      elif options.udp_mpls:
         data = buildMplsOverUdpPacket( options, ipProtocol, ipOptionData,
                                        innerPktHdr )
      elif options.inner_ip_src or options.inner_ip_dst:
         minIpInIpPktSize = Ethxmit.ETH_HLEN + ipHdrSize( options ) + \
             len( ipOptionData ) + len( innerPktHdr ) + Ethxmit.ETH_FCS_LEN
         if options.mpls_label:
            minIpInIpPktSize += 4
         pktSize = validatedMinPktSize( options, minIpInIpPktSize )
         # protocol is IPPROTO_IPIP( 4 ) when inner packet is Ipv4 packet
         # protocol is IPPROTO_IPV6( 41 ) when inner packet is Ipv6 packet
         ipProtocol = IPPROTO_IPIP if options.inner_ip_version == 4 \
                      else IPPROTO_IPV6
         data = Ethxmit.buildIpPacket(
            pktSize, options.ip_src, options.ip_dst,
            ipProtocol=ipProtocol, ttl=options.ttl, tos=options.tos,
            ipOptionData=ipOptionData, dataType=options.dataType,
            dataValue=options.dataValue, initialData=innerPktHdr,
            dontFragFlag=options.dont_fragment, version=options.ip_version,
            moreFragsFlag=options.more_fragments,
            fragOffset=options.frag_offset,
            ipId=options.ip_id, flowLabel=options.flow_label )
      elif options.udp_sport or options.udp_dport:
         ipProtocol = IPPROTO_UDP
         # 14 (Eth) + 20/40 (IPv4/IPv6) + IP-Options (optional) +
         # 8 (UDP) + 4 (FCS)
         minUdpPktSize = Ethxmit.ETH_HLEN + ipHdrSize( options ) + \
                         len( ipOptionData ) + Ethxmit.UDP_HDR_LEN + \
                         len( innerPktHdr ) + Ethxmit.ETH_FCS_LEN
         pktSize = validatedMinPktSize( options, minUdpPktSize )
         data = Ethxmit.buildUdpPacket(
            pktSize, options.udp_sport, options.udp_dport,
            options.ip_src, options.ip_dst,
            ipProtocol=ipProtocol, ttl=options.ttl, tos=options.tos,
            ipOptionData=ipOptionData, dataType=options.dataType,
            dataValue=options.dataValue, version=options.ip_version,
            dontFragFlag=options.dont_fragment,
            moreFragsFlag=options.more_fragments,
            fragOffset=options.frag_offset,
            ipId=options.ip_id,
            flowLabel=options.flow_label )
      elif options.tcp_flags or options.tcp_sport or options.tcp_dport:
         tcp_flags = options.tcp_flags.upper() if ( options.tcp_flags is not None ) \
                     else ''
         tcp_sport = options.tcp_sport if ( options.tcp_sport is not None ) \
                     else Ethxmit.TCP_SPORT_DEFAULT
         tcp_dport = options.tcp_dport if ( options.tcp_dport is not None ) \
                     else Ethxmit.TCP_DPORT_DEFAULT

         pktSize = Ethxmit.ETH_HLEN + ipHdrSize( options ) + \
                   len( ipOptionData ) + Ethxmit.TCP_HDR_LEN + \
                   len( innerPktHdr ) + Ethxmit.ETH_FCS_LEN
         data = Ethxmit.buildTcpPacket( size=pktSize,
                                        ipSrc=options.ip_src,
                                        ipDst=options.ip_dst,
                                        ipVersion=options.ip_version,
                                        tos=options.tos,
                                        ipId=options.ip_id,
                                        dontFragFlag=options.dont_fragment,
                                        moreFragsFlag=options.more_fragments,
                                        fragOffset=options.frag_offset,
                                        ttl=options.ttl,
                                        ipOptionData=ipOptionData,
                                        tcpSPort=tcp_sport,
                                        tcpDPort=tcp_dport,
                                        tcpFlags=tcp_flags,
                                        initialData=innerPktHdr )
      elif options.icmp_type is not None or options.icmp_code is not None:
         # Note that for a valid ICMP reply packet, both icmp_type and icmp_code have
         # value 0. Thus, to craft an ICMP reply packet, we only need to check if any
         # of these options is set or not.
         if options.size and options.size != defaultPktSize:
            raise ValueError( "Only default packet size (%d) is supported "
                              "for ICMP packets" % defaultPktSize )
         data = Ethxmit.buildIcmpPacket(
            options.ip_src, options.ip_dst, ipProtocol=IPPROTO_ICMP,
            icmpType=options.icmp_type, icmpCode=options.icmp_code,
            ttl=options.ttl, tos=options.tos, ipOptionData=ipOptionData,
            version=options.ip_version, dontFragFlag=options.dont_fragment,
            moreFragsFlag=options.more_fragments,
            fragOffset=options.frag_offset, ipId=options.ip_id,
            flowLabel=options.flow_label )
      elif options.arp is not None:
         # ARP Packet
         arpSha = options.arpSha or smac
         if options.arp == 'request':
            data = Ethxmit.buildArpPacket( arpSha, options.ip_dst, options.dmac,
                  options.ip_src )
         elif options.arp == 'reply':
            data = Ethxmit.buildArpPacket( arpSha, options.ip_dst, options.dmac,
                  options.ip_src, requestOrReply='reply' )
      elif options.ndp is not None:
         # NDP is encapsulated in IPv6 
         options.ip_version = 6
         data = Ethxmit.buildNdpPacket( options.ndp, smac, options.dmac,
               options.ip_dst, options.ip_src )

         if options.ndp == 'nbr-solicit':
            # If dmac is broadcast, modify it as Multicast mac, which is derived from
            # the dest-IP. Otherwise use the (possibly) unicast mac from the command
            # line.
            if options.dmac == 'ff:ff:ff:ff:ff:ff':
               options.dmac = Ethxmit.getNsMcastMac( socket.inet_pton(
                              socket.AF_INET6, options.ip_dst ) )
      elif options.rarp is not None:
         # RARP Packet
         arpSha = options.arpSha or smac
         if options.rarp == 'request':
            data = Ethxmit.buildArpPacket( arpSha, options.ip_dst, options.dmac,
                  options.ip_src )
         elif options.rarp == 'reply':
            data = Ethxmit.buildArpPacket( arpSha, options.ip_dst, options.dmac,
                  options.ip_src, requestOrReply='reply' )
      else:
         minIpPktSize = Ethxmit.ETH_HLEN + ipHdrSize( options ) + \
                        len( ipOptionData ) + len( innerPktHdr ) + \
                        Ethxmit.ETH_FCS_LEN
         pktSize = validatedMinPktSize( options, minIpPktSize )
         extraHeaderSize = 4 if options.mpls_label else 0
         data = Ethxmit.buildIpPacket(
            pktSize, options.ip_src, options.ip_dst,
            ipProtocol=ipProtocol, ttl=options.ttl, tos=options.tos, 
            ipOptionData=ipOptionData, dataType=options.dataType, 
            dataValue=options.dataValue, version=options.ip_version, 
            dontFragFlag=options.dont_fragment,
            moreFragsFlag=options.more_fragments,
            fragOffset=options.frag_offset,
            ipId=options.ip_id, extraHeaderSize=extraHeaderSize,
            ipCkSum=options.cksum,
            ipVer=options.ipver, ipHl=options.iphlen, flowLabel=options.flow_label )

      ethertype = None
      if options.arp is not None:
         ethertype = ethertypeOptionToValue( options.ethertype, ETH_P_ARP )
      elif options.rarp is not None:
         ethertype = ethertypeOptionToValue( options.ethertype, ETH_P_RARP )
      elif options.mpls_label and not ( options.gre or options.udp_mpls ):
         ethertype = ethertypeOptionToValue( options.ethertype, ETH_P_MPLS_UC )
      elif options.ip_version == 6:
         ethertype = ethertypeOptionToValue( options.ethertype, ETH_P_IPV6 )
      else:
         ethertype = ethertypeOptionToValue( options.ethertype, ETH_P_IP )
   else:
      if options.vxlan_vni is not None:
         raise ValueError( "must define ip src and dst addresses for vxlan packet" )
      if options.mpls_label and not options.eth_mpls:
         raise ValueError( "must specify either IP src and dst, or "
                           "Ethernet over MPLS option for an MPLS packet" )
      if options.gre or options.gre_mpls:
         raise ValueError( "must specify IP src and dst for a GRE tunneled packet" )
      if options.ip_protocol is not None:
         raise ValueError( "cannot specify IP protocol number for a non-IP packet" )
      if options.ttl is not None:
         raise ValueError( "cannot specify TTL for a non-IP packet" )
      if options.tos is not None:
         raise ValueError( "cannot specify TOS for a non-IP packet" )
      if options.router_alert:
         raise ValueError( "cannot add Router Alert option to a non-IP packet" )
      if options.udp_sport or options.udp_dport:
         raise ValueError(
            "cannot specify UDP source or destination port for a non-IP packet" )
      if options.tcp_sport or options.tcp_dport:
         raise ValueError(
            "cannot specify TCP source or destination port for a non-IP packet" )

      data = Ethxmit.buildEthernetPacket( 
         options.size, initialData=innerPktHdr, dataType=options.dataType,
         dataValue=options.dataValue )
      if options.eth_mpls:
         ethertype = ethertypeOptionToValue( options.ethertype, ETH_P_MPLS_UC )
      elif options.inner_ip_src or options.inner_ip_dst:
         ethertype = ETH_P_IP
      else:
         ethertype = ethertypeOptionToValue( options.ethertype, 0x1234 )

   header = Ethxmit.buildEthernetHeader( options.dmac, smac, ethertype,
                                         options.vlan, options.vpri,
                                         options.inner_vlan, options.inner_vpri,
                                         options.vlan_tpid,
                                         options.inner_vlan_tpid,
                                         options.vnTagSVif, options.vnTagDVif,
                                         options.br, options.svtag_signature,
                                         options.svtag_channel_id )

   if options.mpls_label and not (options.gre or options.udp_mpls):
      for l in range( 0, len( options.mpls_label ) - 1 ):
         header += Ethxmit.buildMplsHeader( options.mpls_label[ l ],
                                            options.mpls_ttl[ l ],
                                            options.mpls_exp[ l ],
                                            mpls_bos=False )
      header += Ethxmit.buildMplsHeader( options.mpls_label[ -1 ],
                                         options.mpls_ttl[ -1 ],
                                         options.mpls_exp[ -1 ] )

   if options.eth_mpls:
      if options.mpls_pw_cw_raw:
         # hex-string format
         header += Ethxmit.buildControlWord( options.mpls_pw_cw_raw, True )
      elif ( optionSpecifiedInCmdline( 'mpls_pw_cw_type' ) or
             optionSpecifiedInCmdline( 'mpls_pw_cw_flags' ) or
             optionSpecifiedInCmdline( 'mpls_pw_cw_frg' ) or
             optionSpecifiedInCmdline( 'mpls_pw_cw_seqno' ) ):
         # options format
         opt = ( options.mpls_pw_cw_type, options.mpls_pw_cw_flags,
                  options.mpls_pw_cw_frg, options.mpls_pw_cw_seqno )
         header += Ethxmit.buildControlWord( opt )

   # ALTA F64 ISL
   if( options.f1 or options.f2 or options.sglort or options.dglort ):
      header = Ethxmit.insertIsl( header, options.f1, options.f2, options.sglort,
                                  options.dglort )

   # Sand PTCH + ITMH Header
   if options.txrawFap or options.sysDestPort:
      header = Ethxmit.insertPtchItmh( header, options.txrawFap,
                                       options.sysDestPort )

   pkt = header + data

   if options.hexDump:
      hexStr = "0x"
      for char in pkt:
         hexStr += "%02x" % ord( char )
      print hexStr
      sys.exit()

   sock = socket.socket( socket.PF_PACKET, socket.SOCK_RAW )
   sock.bind( ( interface, ethertype ) )

   if options.sndbuf:
      sock.setsockopt( socket.SOL_SOCKET, socket.SO_SNDBUF, options.sndbuf )

except ValueError, e:
   parser.error( e )

if options.continuous:
   if options.num != 1:
      parser.error( "cannot specify both iterations and continuous operation" )
   stopat = -1
else:
   stopat = options.num

# C version of the following send loop for speed
#
# burst = 0
# i = 0
# while True:
#    sock.send( pkt )
#    i += 1
#    burst += 1
#    if burst >= options.burst:
#       burst = 0
#       if options.sleep:
#          time.sleep( options.sleep )
#    if i == stopat: break

fd = sock
if options.stdout:
   fd = sys.stdout

if options.incSmac > 0xffff:
   print 'incSmac=%d too large, capping at %d' % ( options.incSmac, 0xffff )
   options.incSmac = 0xffff
if options.incDmac > 0xffff:
   print 'incDmac=%d too large, capping at %d' % ( options.incDmac, 0xffff )
   options.incDmac = 0xffff

et = ctypes.cdll.LoadLibrary( "libethxmit.so" )
status = et.socksend( fd.fileno(), pkt, len( pkt ), stopat, options.burst,
                      ctypes.c_float( options.sleep ), options.seq,
                      options.progress, options.incSmac, options.incDmac )
sys.exit(status) 
   
