Create Custom Protocol - lemontree55/packetgen GitHub Wiki
PacketGen allows you adding your own header classes.
Quick start
To add a new/custom header, you first have to define the new header class. For example:
module MyModule
class MyHeader < PacketGen::Header::Base
define_attr :field1, BinStruct::Int32
define_attr :field2, BinStruct::Int32
# Mandatory to be detected as MyModule::MyHeader,
# else it will be detected as MyHeader.
def self.protocol_name; "MyModule::MyHeader"; end
end
end
Then, class must be declared to PacketGen:
PacketGen::Header.add_class MyModule::MyHeader
Finally, bindings must be declared:
# bind MyHeader as IP protocol number 254 (needed by Packet#parse and Packet#add)
PacketGen::Header::IP.bind MyModule::MyHeader, protocol: 254
And use it:
pkt = Packet.gen('IP').add('MyHeader', field1: 0x12345678)
pkt.myheader.field2.from_human(0x01)
pkt.myheader.to_s # => "\x12\x34\x56\x78\x00\x00\x00\x01"
Add a new header type, in more detail
Define a header class
A new header class should inherit from PacketGen::Header::Base
class (or from PacketGen::Header::ASN1Base
but this is off topic). This base class implements minimal API (see below) to parse a header from binary
string, or to generate binary string from a header class.
PacketGen::Header::Base
inherits from BinStruct::Struct
, which is a class to define headers or anything else with a binary format containing multiple fields.
Here, magical method is define_attr
. This method defines a BinStruct::Struct
attribute and its name. You may set as attributes as you need. A type may be a predefined type from BinStruct
, or any other BinStruct::Sruct
subclass:
class CustomAttribute < BinStruct::Strut
# add a 16-bit integer attribute
define_attr :one, BinStruct::Int16
# add another one
define_attr :two, BinStruct::Int16
end
class ExampleHeader < PacketGen::Header::Base
# define a first 32-bit integer attribute
define_attr :first, BinStruct::Int32
# add a custom attribute
define_attr :custom, CustomAttribute
# Define an integer attribute, composed of bit attributes:
# * ack and error are boolean flags, as no size is specified (default to 1)
# * rsv is a 4-bit field
# * type is a 2-bit field
define_bit_attr :int8, ack: 1, error: 1, :rsv, 4, :type, 2
end
Here are some example to access these fields:
example = ExampleHeader.new
example.first #=> Integer
example.first = 0x12345678
example.custom #=> CustomField
example.custom.one #=> Integer
example.ack? #=> Boolean
example.ack #=> Integer
# May be set as a boolean or as an integer
example.ack = true
example.ack = 1
example.type #=> Integer
example.type = 1
BinStruct Types
To create new Struct or Header classes, BinStruct provides some base types:
- unsigned integers:
Int8
,- big-endian integers:
Int16
orInt16be
,Int32
orInt32be
andInt64
orInt64be
, - little-endian integers:
Int16le
,Int32le
andInt64le
,
- signed integers:
SInt8
,- big-endian integers:
SInt16
orSInt16be
,SInt32
orSInt32be
andSInt64
orSInt64be
, - little-endian integers:
SInt16le
,SInt32le
andSInt64le
,
- enumerated integers:
Int8Enum
,Int16Enum
,Int16beEnum
,Int16leEnum
,Int32Enum
,Int32beEnum
andInt32leEnum
, - strings:
String
,IntString
(a string with a length field at the beginning) andCString
(null-terminated string), - arrays:
Array
to define set of attributes, AbstractTLV
to define fields as set of type-length-value,OUI
(Organizationally Unique Identifier).
All these types are defined under BinStruct
namespace.
Add some methods to a header class
By default, all attributes will have accessors with the good type. By example, a BinStruct::Int32
field may be accessed as an Integer, a BinStruct::String
may be accessed as a String, etc.
But, for some reason, you may need to add another accessor, or a method to compute some protocol data.
To do that, you have to understand Struct model. An attribute may be accessed through its accessor, as already seen. But it may also be accessed through Struct's hash.
Struct class defines #[]
method to access to attribute object by its name. For example, with our previously defined ExampleHeader:
example.first #=> Integer
example[:first] #=> BinStruct::Int32
Sometimes, you will need to access real attribute objects. All attribute objects have common methods:
#read
reads a binary string to set object,#to_s
gives binary string from object,#sz
gives binary size,#from_human
to set object from a human readable object (often a String or an Integer).
As an example of method to a header class, we will define one to calculate first
attribute, which value should be size of custom attribute:
class ExampleHeader
# compute first attribute
def calc_first
self[:first].from_human(self[:custom].sz)
end
end
Add a new ASN.1 header
Some header may be defined using ASN.1 notation, like SNMP.
To define such a header, use PacketGen::Header::ASN1Base
as base class:
class SNMP < PacketGen::Header::ASN1Base
sequence :message,
content: [enumerated(:version, value: 'v2c',
enum: { 'v1' => 0, 'v2c' => 1, 'v2' => 2, 'v3' =>}),
octet_string(:community, value: 'public'),
model(:data, PDUClass)]
define_attributes :version, :community
end
This definition uses lots of stuffs from rasn1
gem:
sequence
defines a ASN.1 Sequence namedmessage
. This sequence contains:- an Enumerated named
version
, - an Octet String named
community
, - and a PDUClass (subclass of
RASN1::Model
) nameddata
and not defined here.
- an Enumerated named
define_attributes
is a helper method to declare attributes from some ASN.1 fields. This helps to mimic standard header behaviour.
Packet magics
PacketGen::Packet
defines some magics using specific header methods.
If your header class has a checksum attribute and/or a length attribute, Packet
provides magic for them. You have to define a #calc_checksum
and/or a calc_length
which appropriatly set checksum and/or length attributes respectively.
Then Packet#calc_checksum
and Packet#calc_length
will calculate all checksum and length attributes in all headers, including yours.
If your header class is not an application layer one, you should define a body
field of type BinStruct::String
. This will allow Packet#parse
to automagically parse headers embedded in yours. Same magic will happen for Packet#to_s
, Packet#encapsulate
, Packet#decapsulate
and Packet#add
.
If, to respond to a message, some attributes may be inverted in your header, you should define #reply!
to help Packet#reply
and Packet#reply!
do the thing.
Header minimal API
PacketGen::Header::Base
and PacketGen::Header::ASN1Base
are provided to simplify writing of new headers. But they may not be so useful for some protocol types.
So, here is minimal API needed by PacketGen to handle a header class.
A header MUST have accessors:
#packet
: get/set packet to which header belongs.
A header MUST respond to:
.protocol_name
: get protocol name, usually class name as a String, without module path,#method_name
: get method name, usually same as protocol name but downcase. This name is used as accessor from packet to access header object,#read
: method to parse binary string and decode header,#parse?
: returntrue
if decoded header is correct. Used when guessing if header may be decoded from binary string. An example of use is checking first 4-bit attribute for IP version in a IP/IPv6 header.