I'm working on a network sniffer program, whose structure uses 3 main classes:
- Frame Class - One per monitored frame, holds reference to all the other sub frames
- Parse Class - Holds the hex value of the entire frame
- Protocol Classes - Individual classes for each network protocol
When a new frame is captured its sent to the Frame()
class and put inside a Parse()
object for unpacking at each layer:
Inside the frame object, a check is made to see what protocol comes next, that's then registered up at the top of the frame class (e.g., IPv4 ipv4 = new IPv4(this);
), and the Frame instance is passed to that object. When the processing is done in the protocol class, a call is made to one of the switch
methods (back in Frame()) to determine which protocol comes next.
Frame Class:
package frames;
import java.util.ArrayList;
import frames.Specification.PROTO;
import utils.Parser;
import utils.Utils;
public class Frame
{
public static int pakCount = 0;
public static int parseErrCount = 0;
public static boolean print = true;
public int dlt = -1;
public Parser parse = null;
public IEEE_802_3_ethernet_header ieee_802_3_ethernet_header = null;
public IEEE_802_11_radiotap_header ieee_802_11_radiotap_header = null;
public IEEE_802_11_mgmt_beacon ieee_802_11_mgmt_beacon = null;
public IPv4 ipv4 = null;
public UDP udp = null;
public TCP tcp = null;
public ICMP icmp = null;
public ICMPv6 icmpv6 = null;
public IPv6 ipv6 = null;
private ArrayList<PROTO> protocols = new ArrayList<PROTO>();
public static Specification spec = new Specification();
public Frame(String dlt, String hex)
{
Frame.pakCount++;
parse = new Parser(hex);
if (Frame.print)
{
System.out.print("-" + Frame.pakCount);
System.out.println(Utils.repeat(400, "-"));
parse.printHex();
}
dltSwitch(Integer.valueOf(dlt));
if (parse.parseErr == true)
{
parseErrCount++;
}
}
// See [1] for DLT numbers
public void dltSwitch(int dlt)
{
if (Frame.print)
System.out.println("[+] DLT: " + dlt);
switch(dlt)
{
case 1:
ieee_802_3_ethernet_header = new IEEE_802_3_ethernet_header(this);
break;
case 127:
ieee_802_11_radiotap_header = new IEEE_802_11_radiotap_header(this);
ieee_802_11_mgmt_beacon = new IEEE_802_11_mgmt_beacon(this);
break;
}
}
public void etherTypeSwitch(int etherType)
{
switch (etherType)
{
case 2048:
ipv4 = new IPv4(this);
break;
case 34525:
ipv6 = new IPv6(this);
break;
}
}
public void ipProtoSwitch(int proto)
{
switch (proto)
{
case 1:
icmp = new ICMP(this);
break;
case 6:
tcp = new TCP(this);
break;
case 17:
udp = new UDP(this);
break;
case 58:
icmpv6 = new ICMPv6(this);
break;
}
}
public static void spec()
{
spec.setLinks(1, "https://github.com/the-tcpdump-group/libpcap/blob/master/pcap/dlt.h");
}
public void setProto(PROTO p)
{
protocols.add(p);
}
}
IpV4 Class: (all the other protocol classes are structurally similar)
package frames;
import frames.Specification.ENDIANNESS;
import frames.Specification.PROTO;
import utils.Types;
public class IPv4
{
public String version = "";
public String internetHeaderLength = "";
public String typeOfService = "";
public int totalLengthBits = -1;
public String identification = "";
public String unassigned = "";
public String dontFrag = "";
public String moreFrag = "";
public String fragOffset = "";
public String ttl = "";
public int protocol = -1;
public String checksum = "";
public String sourceIP = "";
public String destIP = "";
public static Specification spec = new Specification();
public IPv4(Frame frame)
{
this.version = frame.parse.nextBits(4);
this.internetHeaderLength = frame.parse.nextBits(4);
this.typeOfService = frame.parse.nextBytes(1);
this.totalLengthBits = (frame.parse.nextBytesNum(2) * 8);
this.identification = frame.parse.nextBytes(2);
this.unassigned = frame.parse.nextBits(1);
this.dontFrag = frame.parse.nextBits(1);
this.moreFrag = frame.parse.nextBits(1);
this.fragOffset = frame.parse.nextBits(13);
this.ttl = frame.parse.nextBytes(1);
this.protocol = Types.hexToNum(frame.parse.nextBytes(1));
this.checksum = frame.parse.nextBytes(2);
this.sourceIP = frame.parse.nextBytes(4);
this.destIP = frame.parse.nextBytes(4);
print();
frame.ipProtoSwitch(protocol);
}
public void print()
{
if (Frame.print)
{
System.out.printf("IPv4 Header \n");
System.out.printf(" | [Version: %s][IHL: %s][TOS: %s][LEN: %s]\n", version, internetHeaderLength, typeOfService, totalLengthBits);
System.out.printf(" | [ID: %s][Unassigned: %s][DF: %s][MF: %s][FragOffset: %s]\n", identification, unassigned, dontFrag, moreFrag, fragOffset);
System.out.printf(" | [TTL: %s][PROTO: %s][Checsum: %s]\n", ttl, protocol, checksum);
System.out.printf(" | [Source: %s][Dest: %s]\n", Types.hexToIP(sourceIP), Types.hexToIP(destIP));
}
}
public void spec()
{
spec.setEndianess(ENDIANNESS.big);
spec.setName(PROTO.ipv4);
spec.setLinks(1, "https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers");
}
}
Parser class for breaking up the frame bits:
package utils;
import java.util.zip.CRC32;
public class Parser
{
public String hex = null;
public String binary = null;
public boolean parseErr = false;
public int offset = 0;
public int hexLen = 0;
public int binLen = 0;
private int crcIndex = 0;
public Parser(String hex)
{
this.hex = hex;
this.binary = Types.hexToBin(hex);
hexLen = hex.length();
binLen = binary.length();
}
public void printHex()
{
System.out.println(hex);
System.out.println("Raw: " + Utils.repeat((offset / 4) + 5, " ") + "^");
}
public void printBin()
{
System.out.println(binary);
}
public void initialiseCRC()
{
crcIndex = offset;
}
/** Performs the Cyclic redundancy check.
* Prerequisite: must call crcInit() at the start of the frame
* Input: The CRC from the frame
* Process: Substrings the binary string using the CRC index - the 32 bit CRC value
* Returns: True if matches, else false */
public boolean checkCRC32(String crc)
{
CRC32 crc32 = new CRC32();
String substr = "";
try {
substr = binary.substring(crcIndex, binLen - 32);
} catch (StringIndexOutOfBoundsException e) {
this.parseErr = true;
System.out.println(e);
return false;
}
crc32.update(Types.hexToBytes(Types.binToHex(substr)));
String calculatedCRC = String.format("%X", crc32.getValue());
if (crc.equals(calculatedCRC))
{
return true;
}
else
{
this.parseErr = true;
return false;
}
}
public String lastBits(int n)
{
String substr = "";
try {
substr = binary.substring((binLen - n), binLen);
} catch (StringIndexOutOfBoundsException e) {
this.parseErr = true;
System.out.println(e);
return "ERROR";
}
return Types.binToHex(substr);
}
public String lastBytes(int n)
{
return lastBits(n * 8);
}
/** Returns the next # of bytes as hex */
public String nextBits(int n)
{
int numBits = n;
int newOffset = offset + numBits;
String substr = "";
try {
substr = binary.substring(offset, newOffset);
} catch (StringIndexOutOfBoundsException e) {
this.parseErr = true;
return "0000";
}
offset = newOffset;
return Types.binToHex(substr);
}
/** Returns the next 8 bits as hex */
public String nextBytes(int n)
{
return nextBits(n * 8);
}
public int nextBytesNum(int n)
{
return Types.hexToNum(nextBits(n * 8));
}
public String nextBytesIP(int n)
{
return Types.hexToIP(nextBits(n * 8));
}
public String nextBytesMAC(int n)
{
return Types.hexToMac((nextBits(n * 8)));
}
/** Returns next 8 bits has hex with bytes flipped */
public String nextBytesOrder(int n)
{
String bits = nextBits(n * 8);
return Utils.flipBytes(bits);
}
}
I'm trying to find a better class organisation to represent these nested classes. The requirements are that the current protocol class being processed needs to be able to access the fields of the protocol class above it. I was thinking of some sort of inheritance but with so many protocol class types I can't think of a way to make it work.