Skip to content

Commit 78d40ea

Browse files
authored
Add Parse Ethernet frame Operation, allow Parse IPv4 Header to cascade (#1722)
1 parent 2b370b9 commit 78d40ea

File tree

6 files changed

+186
-7
lines changed

6 files changed

+186
-7
lines changed

src/core/config/Categories.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@
249249
"DNS over HTTPS",
250250
"Strip HTTP headers",
251251
"Dechunk HTTP response",
252+
"Parse Ethernet frame",
252253
"Parse User Agent",
253254
"Parse IP range",
254255
"Parse IPv6 address",
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* @author tedk [tedk@ted.do]
3+
* @copyright Crown Copyright 2024
4+
* @license Apache-2.0
5+
*/
6+
7+
import Operation from "../Operation.mjs";
8+
import OperationError from "../errors/OperationError.mjs";
9+
import Utils from "../Utils.mjs";
10+
import {fromHex, toHex} from "../lib/Hex.mjs";
11+
12+
/**
13+
* Parse Ethernet frame operation
14+
*/
15+
class ParseEthernetFrame extends Operation {
16+
17+
/**
18+
* ParseEthernetFrame constructor
19+
*/
20+
constructor() {
21+
super();
22+
23+
this.name = "Parse Ethernet frame";
24+
this.module = "Default";
25+
this.description = "Parses an Ethernet frame and either shows the deduced values (Source and destination MAC, VLANs) or returns the packet data.<br /><br />Good for use in conjunction with the Parse IPv4, and Parse TCP/UDP recipes.";
26+
this.infoURL = "https://en.wikipedia.org/wiki/Ethernet_frame#Frame_%E2%80%93_data_link_layer";
27+
this.inputType = "string";
28+
this.outputType = "html";
29+
this.args = [
30+
{
31+
name: "Input type",
32+
type: "option",
33+
value: [
34+
"Raw", "Hex"
35+
],
36+
defaultIndex: 0,
37+
},
38+
{
39+
name: "Return type",
40+
type: "option",
41+
value: [
42+
"Text output", "Packet data", "Packet data (hex)",
43+
],
44+
defaultIndex: 0,
45+
}
46+
];
47+
}
48+
49+
50+
/**
51+
* @param {string} input
52+
* @param {Object[]} args
53+
* @returns {html}
54+
*/
55+
run(input, args) {
56+
const format = args[0];
57+
const outputFormat = args[1];
58+
59+
if (format === "Hex") {
60+
input = fromHex(input);
61+
} else if (format === "Raw") {
62+
input = new Uint8Array(Utils.strToArrayBuffer(input));
63+
} else {
64+
throw new OperationError("Invalid input format selected.");
65+
}
66+
67+
const destinationMac = input.slice(0, 6);
68+
const sourceMac = input.slice(6, 12);
69+
70+
let offset = 12;
71+
const vlans = [];
72+
73+
while (offset < input.length) {
74+
const ethType = Utils.byteArrayToChars(input.slice(offset, offset+2));
75+
offset += 2;
76+
77+
78+
if (ethType === "\x08\x00") {
79+
break;
80+
} else if (ethType === "\x81\x00" || ethType === "\x88\xA8") {
81+
// Parse the VLAN tag:
82+
// [0000] 0000 0000 0000
83+
// ^^^ PRIO - Ignored
84+
// ^ DEI - Ignored
85+
// ^^^^ ^^^^ ^^^^ VLAN ID
86+
const vlanTag = input.slice(offset+2, offset+4);
87+
vlans.push((vlanTag[0] & 0b00001111) << 4 | vlanTag[1]);
88+
89+
offset += 2;
90+
} else {
91+
break;
92+
}
93+
}
94+
95+
const packetData = input.slice(offset);
96+
97+
if (outputFormat === "Packet data") {
98+
return Utils.byteArrayToChars(packetData);
99+
} else if (outputFormat === "Packet data (hex)") {
100+
return toHex(packetData);
101+
} else if (outputFormat === "Text output") {
102+
let retval = `Source MAC: ${toHex(sourceMac, ":")}\nDestination MAC: ${toHex(destinationMac, ":")}\n`;
103+
if (vlans.length > 0) {
104+
retval += `VLAN: ${vlans.join(", ")}\n`;
105+
}
106+
retval += `Data:\n${toHex(packetData)}`;
107+
return retval;
108+
}
109+
110+
}
111+
112+
113+
}
114+
115+
export default ParseEthernetFrame;

src/core/operations/ParseIPv4Header.mjs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ class ParseIPv4Header extends Operation {
3333
"name": "Input format",
3434
"type": "option",
3535
"value": ["Hex", "Raw"]
36+
},
37+
{
38+
"name": "Output format",
39+
"type": "option",
40+
"value": ["Table", "Data (hex)", "Data (raw)"],
41+
defaultIndex: 0,
3642
}
3743
];
3844
}
@@ -44,6 +50,8 @@ class ParseIPv4Header extends Operation {
4450
*/
4551
run(input, args) {
4652
const format = args[0];
53+
const outputFormat = args[1];
54+
4755
let output;
4856

4957
if (format === "Hex") {
@@ -98,7 +106,10 @@ class ParseIPv4Header extends Operation {
98106
checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")";
99107
}
100108

101-
output = `<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr>
109+
const data = input.slice(ihl * 4);
110+
111+
if (outputFormat === "Table") {
112+
output = `<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr>
102113
<tr><td>Version</td><td>${version}</td></tr>
103114
<tr><td>Internet Header Length (IHL)</td><td>${ihl} (${ihl * 4} bytes)</td></tr>
104115
<tr><td>Differentiated Services Code Point (DSCP)</td><td>${dscp}</td></tr>
@@ -116,13 +127,19 @@ class ParseIPv4Header extends Operation {
116127
<tr><td>Protocol</td><td>${protocol}, ${protocolInfo.protocol} (${protocolInfo.keyword})</td></tr>
117128
<tr><td>Header checksum</td><td>${checksumResult}</td></tr>
118129
<tr><td>Source IP address</td><td>${ipv4ToStr(srcIP)}</td></tr>
119-
<tr><td>Destination IP address</td><td>${ipv4ToStr(dstIP)}</td></tr>`;
130+
<tr><td>Destination IP address</td><td>${ipv4ToStr(dstIP)}</td></tr>
131+
<tr><td>Data (hex)</td><td>${toHex(data)}</td></tr>`;
120132

121-
if (ihl > 5) {
122-
output += `<tr><td>Options</td><td>${toHex(options)}</td></tr>`;
123-
}
133+
if (ihl > 5) {
134+
output += `<tr><td>Options</td><td>${toHex(options)}</td></tr>`;
135+
}
124136

125-
return output + "</table>";
137+
return output + "</table>";
138+
} else if (outputFormat === "Data (hex)") {
139+
return toHex(data);
140+
} else if (outputFormat === "Data (raw)") {
141+
return Utils.byteArrayToChars(data);
142+
}
126143
}
127144

128145
}

tests/browser/02_ops.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ module.exports = {
268268
testOpHtml(browser, "Parse colour code", "#000", ".colorpicker-preview", "rgb(0, 0, 0)");
269269
testOpHtml(browser, "Parse DateTime", "01/12/2000 13:00:00", "", /Date: Friday 1st December 2000/);
270270
// testOp(browser, "Parse IP range", "test input", "test_output");
271-
testOpHtml(browser, "Parse IPv4 header", "45 c0 00 c4 02 89 00 00 ff 11 1e 8c c0 a8 0c 01 c0 a8 0c 02", "tr:last-child td:last-child", "192.168.12.2");
271+
testOpHtml(browser, "Parse IPv4 header", "45 c0 00 c4 02 89 00 00 ff 11 1e 8c c0 a8 0c 01 c0 a8 0c 02", "tr:nth-last-child(2) td:last-child", "192.168.12.2");
272272
// testOp(browser, "Parse IPv6 address", "test input", "test_output");
273273
// testOp(browser, "Parse ObjectID timestamp", "test input", "test_output");
274274
// testOp(browser, "Parse QR Code", "test input", "test_output");

tests/operations/index.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ import "./tests/NetBIOS.mjs";
122122
import "./tests/NormaliseUnicode.mjs";
123123
import "./tests/NTLM.mjs";
124124
import "./tests/OTP.mjs";
125+
import "./tests/ParseEthernetFrame.mjs";
125126
import "./tests/ParseIPRange.mjs";
126127
import "./tests/ParseObjectIDTimestamp.mjs";
127128
import "./tests/ParseQRCode.mjs";
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Parse Ethernet frame tests.
3+
*
4+
* @author tedk [tedk@ted.do]
5+
* @copyright Crown Copyright 2017
6+
* @license Apache-2.0
7+
*/
8+
import TestRegister from "../../lib/TestRegister.mjs";
9+
10+
TestRegister.addTests([
11+
{
12+
name: "Parse plain Ethernet frame",
13+
input: "000000000000ffffffffffff08004500",
14+
expectedOutput: "Source MAC: ff:ff:ff:ff:ff:ff\nDestination MAC: 00:00:00:00:00:00\nData:\n45 00",
15+
recipeConfig: [
16+
{
17+
"op": "Parse Ethernet frame",
18+
"args": ["Hex", "Text output"]
19+
}
20+
]
21+
},
22+
// Example PCAP data from: https://packetlife.net/captures/protocol/vlan/
23+
{
24+
name: "Parse Ethernet frame with one VLAN tag (802.1q)",
25+
input: "01000ccdcdd00013c3dfae188100a0760165aaaa",
26+
expectedOutput: "Source MAC: 00:13:c3:df:ae:18\nDestination MAC: 01:00:0c:cd:cd:d0\nVLAN: 117\nData:\naa aa",
27+
recipeConfig: [
28+
{
29+
"op": "Parse Ethernet frame",
30+
"args": ["Hex", "Text output"]
31+
}
32+
]
33+
},
34+
{
35+
name: "Parse Ethernet frame with two VLAN tags (802.1ad)",
36+
input: "0019aa7de688002155c8f13c810000d18100001408004500",
37+
expectedOutput: "Source MAC: 00:21:55:c8:f1:3c\nDestination MAC: 00:19:aa:7d:e6:88\nVLAN: 16, 128\nData:\n45 00",
38+
recipeConfig: [
39+
{
40+
"op": "Parse Ethernet frame",
41+
"args": ["Hex", "Text output"]
42+
}
43+
]
44+
}
45+
]);

0 commit comments

Comments
 (0)