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

# pylint: disable=consider-using-f-string
# pylint: disable=superfluous-parens
# pylint: disable=redefined-outer-name

import fcntl
import optparse # pylint: disable=deprecated-module
import random
import re
import socket
import sys

import Ethxmit
import Tac
import struct
from SysConstants.if_ether_h import (
      ETH_P_8021Q,
      ETH_P_ARP,
      ETH_P_IP,
      ETH_P_IPV6,
      ETH_P_MPLS_UC,
      ETH_P_RARP,
      ETH_P_SLOW,
      ETH_P_TEB,
)
from SysConstants.in_h import (
      IPPROTO_UDP,
      IPPROTO_TCP,
      IPPROTO_GRE,
      IPPROTO_IPIP,
      IPPROTO_IPV6,
      IPPROTO_ICMP )
from SysConstants.if_vlan_h import VLAN_HLEN

SIOCGIFHWADDR = 0x8927
SADATA = 18
ETHADDR_SLOW_MC = '01:80:c2:00:00:02'

# CFM specific fields
CFM_MDL_MIN = 0
CFM_MDL_MAX = ( 1 << 3 ) - 1
CFM_MEP_ID_MIN = 1
CFM_MEP_ID_MAX = 8192
SLM_TEST_ID_MIN = 0
SLM_TEST_ID_MAX = ( 1 << 32 ) - 1
DM_TIMESTAMP_MIN = 0
DM_TIMESTAMP_MAX = ( 1 << 64 ) - 1
LM_COUNTER_MIN = 0
LM_COUNTER_MAX = ( 1 << 32 ) - 1

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(  # pylint: disable=raise-missing-from
            "%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( "--dei", action="store_const", const=1, default=0,
                   help="dei (default: no tag, or 0 if --vpri 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-dei", dest="inner_dei",
                   action="store_const", const=1, default=0,
                   help="inner vlan dei ( default: no tag, "
                   "or 0 if --inner-vpri or --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( "--ip-src-inc", dest="ip_src_inc", type="int",
                   help="increment source IP address, repeating after N iterations" )
parser.add_option( "--ip-dst-inc", dest="ip_dst_inc", type="int",
                   help="increment destination IP address, repeating after N "
                        "iterations" )
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( "--gue-sport", type="int", dest="gue_sport", metavar="PORT", 
                   help="source GUE port for a tunneled IP/UDP/MPLS packet" )
parser.add_option( "--gue-dport", type="int", dest="gue_dport", metavar="PORT", 
                   help="destination GUE port for a tunneled IP/UDP/MPLS packet" )

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-tcp-flags", help="Set TCP control flags: U,A,P,R,S,F",
                   type="string", dest="inner_tcp_flags" )
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-dei", dest="payload_dei",
                   action="store_const", const=1, default=0,
                   help="payload vlan dei ( default: no tag, "
                   "or 0 if --payload-vlan or --payload-vpri 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-dei", dest="payload_inner_dei",
                   action="store_const", const=1, default=0,
                   help="payload inner vlan dei ( default: no tag, or 0 "
                   "if --payload-inner-vpri or --payload-inner-vlan 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( "--gtpv1", action="store_true",
                   help="enable GTPv1 encapsulation ")
parser.add_option( "--gtpv1-msg-type", type="int", dest="gtpv1_msg_type", 
                   help="GTPv1 message type " )
parser.add_option( "--gtpv1-teid", type="int", dest="gtpv1_teid",
                   help="GTPv1 tunnel endpoint identifier " )
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" )

parser.add_option( "--l2-ts-ver", type="int", default=None,
                   help="Arista L2 timestamp tag version" )
parser.add_option( "--l2-ts-sec", type="int", default=None,
                   help="Arista L2 timestamp tag seconds" )
parser.add_option( "--l2-ts-nsec", type="int", default=None,
                   help="Arista L2 timestamp tag nanoseconds" )
parser.add_option( "--l2-ts-after-vlan", action="store_true",
                   help="enable Arista L2 timestamp placed after VLAN tags" )

parser.add_option( "--dzgre", action="store_true", help="enable DzGRE header" )
parser.add_option( "--dzgre-version", metavar="VER", type="int", default=0,
                   help="set the DzGRE version number (default %default)" )
parser.add_option( "--dzgre-entropy", metavar="VALUE", type="int",
                   help="Arista DzGRE entropy" )
parser.add_option( "--dzgre-switch-id", type="int", default=None,
                   help="Arista DzGRE switch Id" )
parser.add_option( "--dzgre-port-id", type="int", default=None,
                   help="Arista DzGRE port Id" )
parser.add_option( "--dzgre-policy-id", type="int", default=None,
                   help="Arista DzGRE policy Id" )
parser.add_option( "--dzgre-ts-sec", type="int", default=None,
                   help="Arista DzGRE timestamp seconds" )
parser.add_option( "--dzgre-ts-nanosec", type="int", default=None,
                   help="Arista DzGRE timestamp nanoseconds" )

# Options for creating rocev2 packets
parser.add_option( "--rocev2", action="store_true", help="create a rocev2 packet" )
parser.add_option( "--bth-opcode", type="int", default=None,
                   help="RoCEv2 BTH header opcode" ) # 8
parser.add_option( "--bth-flags", type="int", default=None,
                   help="RoCEv2 BTH header flags" ) # 8
parser.add_option( "--bth-pkey", type="int", default=None,
                   help="RoCEv2 BTH header partition key" ) # 16
parser.add_option( "--bth-queue-pair", type="int", default=None,
                   help="RoCEv2 BTH header queue pair" ) # 24
parser.add_option( "--bth-queue-pair-inc", type="int",
                   help=( "increment RoCEv2 BTH queue pair, repeating after N "
                          "iterations (max 65535)" ) )
parser.add_option( "--bth-ack-req", type="int", default=None,
                   help="RoCEv2 BTH header acknowledge request" ) # 8 (1 )
parser.add_option( "--bth-psn", type="int", default=None,
                   help="RoCEv2 BTH header packet sequenece number" ) # 24
parser.add_option( "--bth-reserved-8b", type="int", default=None,
                   help="RoCEv2 BTH header 8 bit reserved field" ) # 8
parser.add_option( "--bth-reserved-7b", type="int", default=None,
                   help="RoCEv2 BTH header 7 bit reserved field" ) # 7
parser.add_option( "--aeth-syndrome", type="int", default=None,
                   help="RoCEV2 AETH header syndrome" ) # 8 bits
parser.add_option( "--aeth-msn", type="int", default=None,
                   help="RoCEV2 AETH header message sequence number" ) # 24 bits
parser.add_option( "--reth-write-length", type="int", default=None,
                   help="RoCEV2 RETH write length" ) # 32 bits
parser.add_option( "--reth-remote-key", type="int", default=None,
                   help="RoCEV2 RETH remote key" ) # 32 bits
parser.add_option( "--reth-virtual-address", type="int", default=None,
                   help="RoCEV2 RETH virtual address" ) # 64 bits

# 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", type="int",
                   help="4 byte svtag" )
parser.add_option( "--ptch-sysSrcPort", dest="sysSrcPort", type="int",
                         help="src Port to inject packet from" )
parser.add_option( "--itmh-sysDestPort", dest="sysDestPort", type="int",
                         help="sysDestPort in ITMH header" )
parser.add_option( "--cpu-channel", dest="cpuChannel", type="int",
                         help="cpu channel (0-255)" )

parser.add_option( "--raw", dest="raw",
                   help="Hexidecimal byte stream (e.g. 0xaaaa010234....) of a "
                   "pre-formed packet to use.  All other packet building "
                   "options are ignored." )

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" )
parser.add_option( "--tcp-mss", help="TCP maximum segment size", type="int",
                   dest="tcp_mss" )
parser.add_option( "--esmc-ssm", help="ESMC SSM value (0-15)", type="int" )
parser.add_option( "--esmc-type",
                   help="ESMC type: informational (I) or event (E) (default: I)",
                   type="choice", choices=[ 'informational', 'I', 'event', 'E' ],
                   default="I" )
parser.add_option( "--src-port-inc", type="int",
                   help=("increment source port, repeating after N iterations"
                         " (max 65535)") )
parser.add_option( "--dst-port-inc", type="int",
                   help=("increment destination port, repeating after N iterations"
                         " (max 65535)" ) )
parser.add_option( "--tcp-seq-num", type="int", default=1,
                   help="TCP header sequence number (default: 1)" )
parser.add_option( "--tcp-ack-num", type="int", default=0,
                   help="TCP header acknowledge number (default: 0)" )
parser.add_option( "--adj-hdr-csums", action="store_true",
                   help=("when either --src-port-inc or --dst-port-inc or"
                   " --ip-src-inc or --ip-dst-inc is set it forces TCP and IP"
                   " header checksum update" ) )
# CFM specific options
supportedCfmPdu = [ 'ccm', 'dmm', 'dmr', 'lmm', 'lmr', 'slm', 'slr' ]
# Common options to all CFM PDUs
parser.add_option( "--cfm-opcode",
                   help="CFM Opcode (%s)" % ' | '.join( supportedCfmPdu ),
                   dest="cfm_opcode",
                   type="choice", choices=supportedCfmPdu )
mdlRangeHelpStr = '%d to %d' % ( CFM_MDL_MIN, CFM_MDL_MAX )
parser.add_option( "--cfm-mdl",
                   help="CFM MD Level (%s)" % mdlRangeHelpStr,
                   dest="cfm_mdl",
                   type="int" )
# CCM specific options
mepRangeHelpStr = '%d to %d' % ( CFM_MEP_ID_MIN, CFM_MEP_ID_MAX )
parser.add_option( "--cfm-mep-id",
                   help="CFM Local MEP ID (%s)" % mepRangeHelpStr,
                   dest="cfm_mep",
                   type="int" )
parser.add_option( "--cfm-ma",
                   help="CFM MA", dest="cfm_ma", type="int" )
# DM specific options
cfmTimestampRangeHelpStr = '%d to %x' % ( DM_TIMESTAMP_MIN, DM_TIMESTAMP_MAX )
parser.add_option(
   "--dm-txF",
   help="Timestamp when DMM is transmitted (%s)" % cfmTimestampRangeHelpStr,
   dest="dm_txF", type="int" )
parser.add_option(
   "--dm-rxF",
   help="Timestamp when DMM is received (%s)" % cfmTimestampRangeHelpStr,
   dest="dm_rxF", type="int" )
parser.add_option(
   "--dm-txB",
   help="Timestamp when DMR is transmitted (%s)" % cfmTimestampRangeHelpStr,
   dest="dm_txB", type="int" )
# LM|SLM specific options
cfmCounterRangeHelpStr = '%d to %x' % ( LM_COUNTER_MIN, LM_COUNTER_MAX )
parser.add_option(
   "--lm-txFcf",
   help="Counter when LMM|SLM is transmitted (%s)" % cfmCounterRangeHelpStr,
   dest="lm_txFcf", type="int" )
parser.add_option(
   "--lm-rxFcf",
   help="Counter when LMR|SLR is transmitted (%s)" % cfmCounterRangeHelpStr,
   dest="lm_rxFcf", type="int" )
parser.add_option(
   "--lm-txFcb",
   help="Counter when LMR|SLR is transmitted (%s)" % cfmCounterRangeHelpStr,
   dest="lm_txFcb", type="int" )
# SLM specific options
parser.add_option(
   "--src-mep-id",
   help="MEP ID of SLM transmitter (%s)" % mepRangeHelpStr,
   dest="src_mep", type="int" )
parser.add_option(
   "--rsp-mep-id",
   help="MEP ID of SLM responder (%s)" % mepRangeHelpStr,
   dest="rsp_mep", type="int" )
slmTestIdRangeHelpStr = '%d to %x' % ( SLM_TEST_ID_MIN, SLM_TEST_ID_MAX )
parser.add_option(
   "--slm-test-id",
   help="Test ID of SLM session (%s)" % slmTestIdRangeHelpStr,
   dest="slm_test_id", type="int" )

def isCfmTimestampValid( timestamp ):
   return DM_TIMESTAMP_MIN <= timestamp <= DM_TIMESTAMP_MAX

def isCfmCounterValid( counter ):
   return LM_COUNTER_MIN <= counter <= LM_COUNTER_MAX

def isCfmMdlValid( mdl ):
   return CFM_MDL_MIN <= mdl <= CFM_MDL_MAX

def isCfmMepIdValid( mepId ):
   return CFM_MEP_ID_MIN <= mepId <= CFM_MEP_ID_MAX

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 = bytearray( interface.encode() + b'\0' * 32 )
   fcntl.ioctl( s.fileno(), SIOCGIFHWADDR, buf )
   return Ethxmit.bytesToMacaddr( buf[ SADATA : SADATA + 6 ] )

# pylint: disable-next=inconsistent-return-statements
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 ) # pylint: disable=eval-used
      except NameError:
         pass

   parser.error(
      f'option {optionName}: invalid integer value or name: {optionString!r}' )

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 = b''
   innerArp = b''
   innerNdp = b''
   def innerEthHdrRequired():
      if options.vxlan_vni:
         return True
      elif options.gre or options.gre_mpls:
         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_dei,
                                                 options.payload_inner_vlan,
                                                 options.payload_inner_vpri,
                                                 options.payload_inner_dei,
                                                 options.payload_vlan_tpid,
                                                 options.payload_inner_vlan_tpid,
                                                 options.svtag )

   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 = b''
   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 or \
      options.inner_tcp_flags is not None:
      innerIpProtocol = IPPROTO_TCP
   innerPktHdr = innerEthHdr
   if options.cfm_opcode is not None:
      innerPktHdr += Ethxmit.buildCfmHeader( options )
   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 options.rocev2:
         # hack - guess size of outer payload
         # Roce will assume the presence of additional headers depending on the
         # opcode. Packet analyzers will require those headers be present in order
         # to parse a ROCE packet. Therefore, we must make sure the UDP length
         # is long enough to cover the expected header sizes. By doing this,
         # we allos options.size to control what gets set in the inner UDP
         # payload length, which ensures we can create valid packets.
         outerPkt = 16 + 40 + 8 + 8
         pktSize = max( pktSize, options.size - outerPkt )
         innerHdr = Ethxmit.buildRoceV2Packet(
            pktSize, options.inner_udp_sport, options.inner_udp_dport,
            options.inner_ip_src, options.inner_ip_dst,
            options.bth_opcode, options.bth_queue_pair,
            bthFlags=options.bth_flags, bthPKey=options.bth_pkey,
            bthAckReq=options.bth_ack_req, bthPsn=options.bth_psn,
            bthReserved8b=options.bth_reserved_8b,
            bthReserved7b=options.bth_reserved_7b,
            aethSyndrome=options.aeth_syndrome, aethMsn=options.aeth_msn,
            rethWriteLength=options.reth_write_length,
            rethRKey=options.reth_remote_key,
            rethVirtualAddr=options.reth_virtual_address,
            ttl=innerTtl, tos=innerTos,
            dataValue=options.dataValue,
            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=options.inner_flow_label )

      elif 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_flags is None:
            options.inner_tcp_flags = ''
         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,
            tcpFlags=options.inner_tcp_flags)
      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, 
                            udpSport, udpDport ):

   mplsHeader = b''

   # 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, udpSport, udpDport )

   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 )
   if options.dzgre:
      greHeader = Ethxmit.buildDzGreHeader( greVersion=options.gre_version,
                                            greKey=options.dzgre_entropy,
                                            dzGreVersion=options.dzgre_version,
                                            switchId=options.dzgre_switch_id,
                                            portId=options.dzgre_port_id,
                                            policyId=options.dzgre_policy_id,
                                            tsSeconds=options.dzgre_ts_sec,
                                            tsNanoseconds=options.dzgre_ts_nanosec )

   mplsHeader = b''
   # 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 = b""
      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 buildGtpv1Hdr( options, payloadLen ):
   version = 1
   pt = 1
   if options.gtpv1_msg_type is None:
      msgType = 255
   else:
      msgType = options.gtpv1_msg_type
   if options.gtpv1_teid is None:
      teid = 0x11223344
   else:
      teid = options.gtpv1_teid

   byte1 = version<< 5 | ( pt<< 4 )
   gtpv1Hdr = struct.pack( '>BBHI', byte1, msgType, payloadLen, teid )
   # Minimum GTP packet size has to be 74
   # 14 (Eth) + 20/40 (IPv4/IPv6) +  8 (UDP) + 8 (GTPv1Hdr)
   # + 20/40 (IPv4/IPv6)) + 4 (FCS)
   minPktSize = Ethxmit.ETH_HLEN + ipHdrSize( options ) + \
       + Ethxmit.UDP_HDR_LEN + len( gtpv1Hdr ) + \
       len( innerPktHdr ) + Ethxmit.ETH_FCS_LEN
   size = validatedMinPktSize( options, minPktSize )
   #print( minPktSize, size )
   return gtpv1Hdr, size

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.dei:
      if not( options.vlan or options.vpri ):
         raise ValueError( "must enable --vlan or --vpri to use --dei" )

   if options.inner_dei:
      if not( options.inner_vlan or options.inner_vpri ):
         raise ValueError(
               "must enable --inner-vlan or --inner-vpri to use --inner-dei" )

   if options.payload_dei:
      if not( options.payload_vlan or options.payload_vpri ):
         raise ValueError(
               "must enable --payload-vlan or --payload-vpri to use --payload-dei" )

   if options.payload_inner_dei:
      if not( options.payload_inner_vlan or options.payload_inner_vpri ):
         raise ValueError( "must enable --payload-inner-vlan or "
                           "--payload-inner-vpri to use --payload-inner-dei" )

   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 not 0 <= int( options.vxlan_vni ) <= 16777215:
      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 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.gue_sport is not None or options.gue_dport is not None:
      if options.gue_sport is None or options.gue_dport is None:
         raise ValueError(
            "must specify both --gue-sport and --gue-dport, or neither" )
      if options.udp_sport is not None or options.udp_dport is not None:
         raise ValueError(
            "must specify either --gue-sport/--gue-dport or --udp-sport/--udp-dport"
            )

   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.src_port_inc and
        ( options.udp_sport is None and options.tcp_sport is None ) ):
      raise ValueError(
         "must specify either --udp-sport or --tcp-sport when using --src-port-inc" )

   if ( options.dst_port_inc and
        ( options.udp_dport is None and options.tcp_dport is None ) ):
      raise ValueError(
         "must specify either --udp-dport or --tcp-dport when using --dst-port-inc" )

   if ( options.adj_hdr_csums and
        ( options.tcp_sport is None and options.tcp_dport is None and
          options.tcp_flags is None ) ):
      raise ValueError(
         "must specify either --tcp-sport, --tcp-dport or --tcp-flags when using"
         " --adj-hdr-csums" )
   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 is not None
       or options.sysSrcPort is not None ):
      if not options.txrawFap:
         raise ValueError( "must specify --txrawFap when using --sysDestPort "
                           "or --sysSrcPort" )
      if options.sysDestPort is None and options.sysSrcPort is None:
         raise ValueError( "must specify --sysDestPort or --sysSrcPort "
                           "when using --txrawFap" )

   if( options.cpuChannel and options.sysSrcPort is None ):
      raise ValueError( "must specify --sysSrcPort when using --cpu-channel ")

   if options.gtpv1:
      if ( not options.inner_ip_src or not options.inner_ip_dst or
           not options.ip_src or not options.ip_dst ):
         raise ValueError(
            "must specify --ip-src, --ip-dst,--inner-ip-src, --inner-ip-dst"
            " for gtpv1 " )
      if not options.udp_sport:
         options.udp_sport = 2152
      if not options.udp_dport:
         options.udp_dport = 2152

   bthOption = ( options.bth_opcode or options.bth_flags or options.bth_pkey or
                 options.bth_queue_pair or options.bth_ack_req or options.bth_psn or
                 options.bth_reserved_8b or options.bth_reserved_7b or
                 options.bth_queue_pair_inc )
   aethOption = ( options.aeth_syndrome or options.aeth_msn )
   rethOption = ( options.reth_write_length or options.reth_remote_key or
                  options.reth_virtual_address )

   if bthOption or aethOption or rethOption:
      if not options.rocev2:
         raise ValueError( "must specify rocev2 with any bth or aeth options" )
      if aethOption and rethOption:
         raise ValueError( "Cannot specify both reth and aeth options" )

   if options.esmc_ssm is not None:
      if optionSpecifiedInCmdline( 'dmac' ):
         raise ValueError( "--dmac and --esmc-ssm are incompatible" )
      if options.esmc_ssm not in range( 16 ):
         raise ValueError( "invalid --esmc-ssm value, must be 0 to 15" )
   elif optionSpecifiedInCmdline( 'esmc_type' ):
      raise ValueError( "must specify --esmc-ssm with --esmc-type for ESMC" )

   if not options.dzgre and \
      ( optionSpecifiedInCmdline( 'dzgre_version' ) or \
        optionSpecifiedInCmdline( 'dzgre_entropy' ) or \
        optionSpecifiedInCmdline( 'dzgre_switch_id' ) or \
        optionSpecifiedInCmdline( 'dzgre_port_id' ) or \
        optionSpecifiedInCmdline( 'dzgre_policy_id' ) or \
        optionSpecifiedInCmdline( 'dzgre_ts_sec' ) or \
        optionSpecifiedInCmdline( 'dzgre_ts_nanosec' ) ):
      raise ValueError( "must specify --dzgre to use --dzgre-... options" )

   if options.dzgre:
      if not options.dzgre_entropy:
         raise ValueError( "must specify --dzgre-entropy to use --dzgre option" )
      if not options.gre and not options.eth_gre:
         raise ValueError(
            "must specify --gre or --eth-over-gre to use --dzgre option" )

   # if --cfm-opcode is passed then check for all the manadatory arguments
   if options.cfm_opcode is not None:
      # Validate the fields in CFM header which is common to all types of CFM PDU
      if options.cfm_mdl is None:
         raise ValueError( "must specify --cfm-mdl" )

      if not isCfmMdlValid( options.cfm_mdl ):
         raise ValueError( "invalid --cfm-mdl value, must be %s" %
                           mdlRangeHelpStr )

      # Validate CFM PDU specific fields
      if options.cfm_opcode == 'ccm':
         if options.cfm_mep is None:
            raise ValueError( "must specify --cfm-mep-id" )
         if not isCfmMepIdValid( options.cfm_mep ):
            raise ValueError( "invalid --cfm-mep-id value, must be %s" %
                              mepRangeHelpStr )
         if options.cfm_ma is None:
            raise ValueError( "must specify --cfm-ma" )
      elif options.cfm_opcode in [ 'dmm', 'dmr' ]:
         if options.dm_txF is None:
            raise ValueError( "must specify --dm-txF" )
         if not isCfmTimestampValid( options.dm_txF ):
            raise ValueError( "invalid --dm-txF value, must be %s" %
                              cfmTimestampRangeHelpStr )
         if options.cfm_opcode == 'dmr':
            if options.dm_rxF is None:
               raise ValueError( "must specify --dm-rxF" )
            if not isCfmTimestampValid( options.dm_rxF ):
               raise ValueError( "invalid --dm-rxF value, must be %s" %
                                 cfmTimestampRangeHelpStr )
            if options.dm_txB is None:
               raise ValueError( "must specify --dm-txB" )
            if not isCfmTimestampValid( options.dm_txB ):
               raise ValueError( "invalid --dm-txB value, must be %s" %
                                 cfmTimestampRangeHelpStr )
      elif options.cfm_opcode in [ 'lmm', 'lmr' ]:
         if options.lm_txFcf is None:
            raise ValueError( "must specify --lm-txFcf" )
         if not isCfmCounterValid( options.lm_txFcf ):
            raise ValueError( "invalid --lm-txFxf value, must be %s" %
                              cfmCounterRangeHelpStr )
         if options.cfm_opcode == 'lmr':
            if options.lm_rxFcf is None:
               raise ValueError( "must specify --lm-rxFcf" )
            if not isCfmCounterValid( options.lm_rxFcf ):
               raise ValueError( "invalid --lm-rxFcf value, must be %s" %
                                 cfmCounterRangeHelpStr )

            if options.lm_txFcb is None:
               raise ValueError( "must specify --lm-txFcb" )
            if not isCfmCounterValid( options.lmr_tx ):
               raise ValueError( "invalid --lm-txFcb value, must be %s" %
                                 cfmCounterRangeHelpStr )
      elif options.cfm_opcode in [ 'slm', 'slr' ]:
         if options.src_mep is None:
            raise ValueError( "must specify --src-mep" )
         if not isCfmMepIdValid( options.src_mep ):
            raise ValueError( "invalid --src-mep value, must be %s" %
                              mepRangeHelpStr )

         if options.lm_txFcf is None:
            raise ValueError( "must specify --lm-txFcf" )
         if not isCfmCounterValid( options.lm_txFcf ):
            raise ValueError( "invalid --lm-txFcf value, must be %s" %
                              cfmCounterRangeHelpStr )

         if options.slm_test_id is None:
            raise ValueError( "must specify --slm-test-id" )
         if ( options.slm_test_id < SLM_TEST_ID_MIN or
              options.slm_test_id > SLM_TEST_ID_MAX ):
            raise ValueError(
               "invalid --slm-test-id value, must be %s" % slmTestIdRangeHelpStr )
         if options.cfm_opcode == 'slr':
            if options.rsp_mep is None:
               raise ValueError( "must specify --rsp-mep" )
            if not isCfmMepIdValid( options.rsp_mep ):
               raise ValueError( "invalid --rsp-mep value, must be %s" %
                                 mepRangeHelpStr )
            if options.lm_txFcb is None:
               raise ValueError( "must specify --lm-txFcb" )
            if not isCfmCounterValid( options.lm_txFcb ):
               raise ValueError( "invalid --lm-txFcb value, must be %s" %
                                 cfmCounterRangeHelpStr )
      else:
         raise ValueError( "invalid --cfm-opcode %s" % options.cfm_opcode )

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
      elif options.gtpv1:
         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 = b'\x94\x04\x00\x00' if options.router_alert else b''
      if options.vxlan_vni:
         data = buildVxlanPacket( options, ipOptionData, innerPktHdr )
      elif options.gre or options.gre_mpls or options.dzgre:
         data = buildGrePacket( options, ipProtocol, ipOptionData, innerPktHdr )
      elif options.udp_mpls:
         data = buildMplsOverUdpPacket( options, ipProtocol, ipOptionData,
                                        innerPktHdr, options.udp_sport,
                                        options.udp_dport)
      elif options.gtpv1:
         gtpHdr, pktSize = buildGtpv1Hdr( options, len( innerPktHdr ) )
         udpPayload = gtpHdr + innerPktHdr
         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, initialData=udpPayload,
            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.gue_dport:
         ipProtocol = IPPROTO_UDP
         if numLabels == 0: # number of mpls labels
            # 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.gue_sport, options.gue_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,
               initialData=innerPktHdr,
               dontFragFlag=options.dont_fragment,
               moreFragsFlag=options.more_fragments,
               fragOffset=options.frag_offset,
               ipId=options.ip_id,
               flowLabel=options.flow_label )
         else:
            data = buildMplsOverUdpPacket( options, ipProtocol, ipOptionData,
                                           innerPktHdr, options.gue_sport,
                                           options.gue_dport )

      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.rocev2:
         minUdpPktSize = Ethxmit.ETH_HLEN + ipHdrSize( options ) + \
            len( ipOptionData ) + Ethxmit.UDP_HDR_LEN + \
            Ethxmit.ETH_FCS_LEN
         pktSize = validatedMinPktSize( options, minUdpPktSize )

         if len( innerPktHdr ) > 0:
            raise ValueError( "rocev2 does not support an inner packet" )
         data = Ethxmit.buildRoceV2Packet(
            pktSize, options.udp_sport, options.udp_dport,
            options.ip_src, options.ip_dst,
            options.bth_opcode, options.bth_queue_pair,
            bthFlags=options.bth_flags, bthPKey=options.bth_pkey,
            bthAckReq=options.bth_ack_req, bthPsn=options.bth_psn,
            bthReserved8b=options.bth_reserved_8b,
            bthReserved7b=options.bth_reserved_7b,
            aethSyndrome=options.aeth_syndrome, aethMsn=options.aeth_msn,
            rethWriteLength=options.reth_write_length,
            rethRKey=options.reth_remote_key,
            rethVirtualAddr=options.reth_virtual_address,
            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.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
         tcp_mss = options.tcp_mss if ( options.tcp_mss is not None ) else 0

         minTcpPktSize = Ethxmit.ETH_HLEN + ipHdrSize( options ) + \
            len( ipOptionData ) + Ethxmit.TCP_HDR_LEN + \
            len( innerPktHdr ) + Ethxmit.ETH_FCS_LEN

         if tcp_mss:
            minTcpPktSize = minTcpPktSize + Ethxmit.TCP_MSS_OPTION_LEN

         payloadSize = options.size - minTcpPktSize
         payload = Ethxmit.buildPayload( payloadSize,
                                         options.dataType, options.dataValue )
         if len( payload ) > 0:
            innerPktHdr = innerPktHdr + payload
            minTcpPktSize = minTcpPktSize + len( payload )

         pktSize = validatedMinPktSize( options, minTcpPktSize )
         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,
                                        tcpMss=tcp_mss,
                                        initialData=innerPktHdr,
                                        seqNum=options.tcp_seq_num,
                                        ackNum=options.tcp_ack_num)
      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 or
                                        options.gue_dport ):
         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.gue_sport or options.gue_dport:
         raise ValueError(
            "cannot specify GUE 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" )
      if options.gre and options.dzgre:
         raise ValueError(
            "GRE flag and DzGRE flag are mutually exclusive. Use only one of them" )

      if options.esmc_ssm is not None:
         # Overwrite the destination mac and specify the ethertype for
         # Synchronous Ethernet before constructing the ethernet header
         assert not optionSpecifiedInCmdline( 'dmac' )
         options.dmac = ETHADDR_SLOW_MC
         ethertype = ETH_P_SLOW
      else:
         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 )

   l2TimestampInfo = [ options.l2_ts_ver, options.l2_ts_sec, options.l2_ts_nsec,
                       options.l2_ts_after_vlan ]
   header = Ethxmit.buildEthernetHeader( options.dmac, smac, ethertype,
                                         options.vlan, options.vpri, options.dei,
                                         options.inner_vlan, options.inner_vpri,
                                         options.inner_dei, options.vlan_tpid,
                                         options.inner_vlan_tpid,
                                         options.vnTagSVif, options.vnTagDVif,
                                         options.br, options.svtag,
                                         l2TimestampInfo )

   if options.mpls_label and not ( options.gre or options.udp_mpls or 
                                   options.gue_dport ):
      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:
      header, data = Ethxmit.insertPtchItmh( header, data, options.txrawFap,
                                             options.sysDestPort,
                                             options.sysSrcPort,
                                             cpuChannel=options.cpuChannel )

   # Synchronous Ethernet ESMC
   if options.esmc_ssm is not None:
      # Construct the Synchronous Ethernet ESMC data packet after the header
      # is finalized to determine if padding is necessary
      data = Ethxmit.buildEsmcPacket(
         len( header ), options.esmc_type, options.esmc_ssm )

   pkt = header + data

   if options.raw:
      pkt = b''
      if options.raw.startswith( '0x' ):
         raw = options.raw[ 2 : ]
      else:
         raw = options.raw

      if len( raw ) % 2:
         raise ValueError( '--raw hexidecimal stream has odd number of characters' )
      for i in range( 0, len( raw ), 2 ):
         hexByte = raw[ i : i + 2 ]
         try:
            pkt += struct.pack( ">B", int( hexByte, 16 ) )
         except ValueError:
            # pylint: disable-next=raise-missing-from
            raise ValueError( '--raw contains non-hex byte "%s"' % hexByte )

   if options.hexDump:
      hexStr = "0x"
      for b in iter( pkt ):
         hexStr += f"{b:02x}"
      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 as 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 and options.incSmac > 0xffff:
   print( 'incSmac=%d too large, capping at %d' % ( options.incSmac, 0xffff ) )
   options.incSmac = 0xffff
if options.incDmac and options.incDmac > 0xffff:
   print( 'incDmac=%d too large, capping at %d' % ( options.incDmac, 0xffff ) )
   options.incDmac = 0xffff

if options.src_port_inc and options.src_port_inc > 0xffff:
   print( 'src_port_inc=%d too large, capping at %d' % ( options.src_port_inc,
                                                         0xffff ) )
   options.src_port_inc = 0xffff
if options.dst_port_inc and options.dst_port_inc > 0xffff:
   print( 'dst_port_inc=%d too large, capping at %d' % ( options.dst_port_inc,
                                                         0xffff ) )
   options.dst_port_inc = 0xffff

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

status = Ethxmit.socksend( fd, pkt, stopat=stopat, burst=options.burst,
                           sleep=options.sleep, seq=options.seq,
                           progress=options.progress, incSmac=options.incSmac,
                           incDmac=options.incDmac, incSrcIp=options.ip_src_inc,
                           incDstIp=options.ip_dst_inc,
                           incSrcPort=options.src_port_inc,
                           incDstPort=options.dst_port_inc,
                           adjHdrCSums=options.adj_hdr_csums,
                           incBthQp=options.bth_queue_pair_inc )
sys.exit( status )

