.. _intro:

*****************************
Pypacker general introduction

*****************************

This is a general user-introduction to pypacker and will cover the topics of
creating, analyzing, sending and receiving packets. For a quick dive-into
check out the examples in the respective directory.


Needed imports
==============

In order to do anything with pypacker all relevant layers have to be imported.
This will look like: from pypacker.layerX import PacketCLass. The following
statement will import the ethernet layer::

	from pypacker.layer12 import ethernet

Now the Ethernet class contained in the ethernet packet can be used::

	ether = ethernet.Ethernet()

The following examples assume all relevant layers and classes have been imported.


Creating custom packets
=======================

There are two ways to create custom packets: Using keyword constructor or
giving raw bytes::

	# keyword consturctor
	ether = Ethernet(src_s="aa:bb:cc:dd:ee:ff")
	# init using raw bytes
	ether = Ethernet(b"\xAA\xBB\xCC\xDD\xEE\xFF\xAA\xBB\xCC\xDD\xEE\xFF\x00\x00\xFF\xFF")

Note that only the src address was given using the keyword constructor.
Generally all fields left out will be set to default values defined in the
correspondig class (see API-documentation).
Another handy feature is the auto-conversion seen in the keyword example:
using the format "varname_s" generally all MAC-, IP-address or DNS-names can be set
using normal string representation. Setting values using raw bytes is
supported after all e.g. via 'src=b"\\xAA\\xBB\\xCC\\xDD\\xEE\\xFF'.

After instantiation all header fields can be further changed::

	# change destination like this..
	ether.dst_s="aa:bb:cc:dd:ee:ff"
	# ..this is the same as that
	ether.dst= b"\xAA\xBB\xCC\xDD\xEE\xFF"
	ether.bin()

Packets can contain simple and list style fields (aka TriggerList). Simple ones are like
python byte strings or integers. Byte fields have a fixed length except
some rare cases (see dns.DNS.Query -> name). Those can be identified
via their header format "None". All simple fields can be deactivated by setting
the value to "None" and re-activated by setting any other value.

In contrast to this, list style headers can contain Packets itself (see ip.IP.opts) OR anything
else which represents this header (see http.HTTP.hdr).
The kind of content for this header depends on the class itself
(see header info at module documentation). List style headers can be identified
via format "None" and a TriggerList-like value. As static fields already have
been introduced the following examples will clear up handling dynamic ones::

	# init packet having dynamic fields
	# single value
	ip = IP(src_s="127.0.0.1", opts=[IPOptMulti(type=IP_OPT_TS, len=3, body_bytes=b"\x00\x01")])
	# multiple values
	ip = IP(src_s="127.0.0.1", opts=[IPOptMulti(type=IP_OPT_TS, len=2, body_bytes=b"\x00\x01"), IPOptSingle(type=IP_OPT_EOOL)])
	# change dynamic field after instantiation
	ip.opts=IPOptSingle(type=IP_OPT_EOOL)

Dynamic fields generally expect raw bytes, tuples or Packets itself depending on the implementation.
As dynamic fields are basically python lists itself, all list operations can be applied, too::

	del ip.opts[0]
	ip.opts.insert(1, IPOptMulti(type=IP_OPT_TS, len=2, body_bytes=b"\x00\x01"))
	ip.opts.append(IPOptMulti(type=IP_OPT_TS, len=2, body_bytes=b"\x00\x01"))

Layers can be concatenated using "+"::

	# create a new packet composed of concatenated ethernet, ip and tcp layer
	packet = Ethernet() + IP() + TCP()
	# dump raw bytes of all concatenated layers
	packet.bin()

Note: Changes to a packet will also change the original packet instances used to build "packet" and vise versa::

	ip1 = IP()
	tcp1 = TCP()
	packet = ip1 + tcp1
	# this will also change "p" in ip1
	packet.p = 123
	# this will also change packet.tcp1.sport
	tcp1.sport = 123

Note: Auto-update fields like checksums, lengths etc. (eg "sum" in IP) will not be updated until calling bin().
This can be disabled by calling bin(update_auto_fields=False) to set custom values eg for fuzzing purposes.


Analyzing packets
=================

All header values can be retrieved like in the previous examples. To check if
a packet matches an other e.g. to find an answer packet, using the direction()
method can become in handy. This will return the constants DIR_SAME, DIR_REV or DIR_UNKNOWN
or an "OR"-concatination of some::

	# DIR_SAME, DIR_REV, DIR_UNKNOWN or combination of any of them
	direction = ether1.direction
	# check direction explicitly
	if ether1.is_direction(ether2, DIR_REV):
		print("found answer packet!")

All present layers can be accessed using the index notation::

	tcp = ethernet[TCP]
	# no TCP layer found
	if tcp is None:
		print("no TCP layer found: %s" % tcp)
	# this is equivalent to the previous example (assuming TCP is really present)
	tcp = ethernet.ip.tcp


Read/write packets from/to file
===============================

Pypacker supports reading and writing pcap files (micro and nano second format). The ppcap
module is responsible for this task::

	from pypacker import ppcap
	# open file to be read
	pcap = ppcap.Reader(filename="my_packets.pcap")
	# cycle through all packets
	for ts, buf in pcap:
		eth = ethernet.Ethernet(buf)
		print("time (nanoseconds): %d, packet: %s:" % (ts, eth))
	pcap.close()


Send and receive packets
========================

Using the SocketHndl class one can send and receive packets at OSI layer 2 and 3.
This supports simple receiving/sending, advanced receiving using filters and
send/receiving auto-matching answers. The following examples will clear this up::

	from pypacker import psocket

	# open sockets using the socket handler
	sock_l2 = psocket.SocketHndl(iface_name="eth0", mode=psocket.SocketHndl.MODE_LAYER_2)
	sock_l3 = psocket.SocketHndl(iface_name="eth0", mode=psocket.SocketHndl.MODE_LAYER_3)
	# send raw bytes
	sock_l2.send(ether.bin())
	sock_l3.send(ether.bin(), "127.0.0.1")
	# receive arbitrary bytes
	bts = sock_l2.recv()
	# receive packets: raw bytes will be internally used to create packets
	pkts = socket_l2.recvp(filter=lambda p: p[IP].src=="127.0.0.1", lowest_layer=Ethernet)
	# send packets and auto-match answers: those will be returned
	pkts = socket_l2.sr(Ethernet() + IP() + TCP(), lowest_layer=ethernet.Ethernet)
	print("answer was: %s" % pkts[0])
	# close sockets
	sock_l2.close()
	sock_l3.close()


Implementing new Protocols
==========================	

See HACKING file -> "Adding protocols" in the main directory.
