From cd5570d34b82d25d90d2c92b8de54da665abcab66bab09488de8d1061e462a62 Mon Sep 17 00:00:00 2001 From: brent saner Date: Sun, 31 Aug 2025 00:29:20 -0400 Subject: [PATCH] include IPv4 Option padding and type packing/unpacking --- .githooks/pre-commit/02-gofmt | 15 ++ README.adoc | 75 +++++++- README.html | 320 ++++++++++++++++++++++++++++++++-- examples/de.go | 4 +- examples/frag.go | 4 +- examples/v4optspad.go | 73 ++++++++ examples/v4optstype.go | 111 ++++++++++++ examples/vihl.go | 4 +- examples/vtf.go | 4 +- 9 files changed, 586 insertions(+), 24 deletions(-) create mode 100755 .githooks/pre-commit/02-gofmt create mode 100644 examples/v4optspad.go create mode 100644 examples/v4optstype.go diff --git a/.githooks/pre-commit/02-gofmt b/.githooks/pre-commit/02-gofmt new file mode 100755 index 0000000..6486ef5 --- /dev/null +++ b/.githooks/pre-commit/02-gofmt @@ -0,0 +1,15 @@ +#!/bin/bash + +srcdir="${PWD}/examples" + +if ! command -v gofmt &> /dev/null; +then + exit 0 +fi + +for f in $(find ${srcdir} -type f -iname "*.go"); +do + gofmt -w "${f}" + git add "${f}" +done +echo "Reformatted examples" diff --git a/README.adoc b/README.adoc index a352d2b..56fc9e8 100644 --- a/README.adoc +++ b/README.adoc @@ -270,7 +270,7 @@ A flag's presence can be checked via a bit-wise `AND` against _flag_ being equal [%collapsible] .Example in Go ===== -.`hasflag.go` +.`examples/hasflag.go` [source,go] ---- include::examples/hasflag.go[] @@ -390,7 +390,7 @@ IP:P:: <> _(1 Byte)_ IP:C:: Header Checksum (RFC https://datatracker.ietf.org/doc/html/rfc1071[1071^], https://datatracker.ietf.org/doc/html/rfc1071[1141^], https://datatracker.ietf.org/doc/html/rfc1624[1624^]) _(2 Bytes)_ IP:S:: Source IPv4 Address _(32 Bits/4 Bytes)_ IP:D:: Destination IPv4 Address _(32 Bits/4 Bytes)_ -IP:O:: Options (Optional) (See the https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml[IANA Registered IP Parameters^] for details) _(Variable Length)_ +IP:O:: Options + Null Padding (Optional) _(Variable Length, see <>)_ [id="spec_bcast_v6"] ==== IPv6 @@ -541,6 +541,7 @@ To *retrieve* the Version and IHL, the value is bit-shifted to the *right* by 4 [%collapsible] .Example in Go ==== +.`examples/vihl.go` [source,go] ---- include::examples/vihl.go[] @@ -571,6 +572,7 @@ Notable ECN mentions are: [%collapsible] .Example in Go ==== +.`examples/de.go` [source,go] ---- include::examples/de.go[] @@ -586,12 +588,31 @@ The Fragmentation Flags and Offset are defined in https://datatracker.ietf.org/d [%collapsible] .Example in Go ==== +.`examples/frag.go` [source,go] ---- include::examples/frag.go[] ---- ==== +[id="xtra_bitpacked_ip4opt_t"] +==== IPv4 Option Type +The *Type* field of an <> is a single byte that consists of: + +* A "Copy" flag _(1 Bit)_ +* A "Class" flag _(2 Bits)_ +* An Option "Number" _(5 Bits)_ + +[%collapsible] +.Example in Go +==== +.`examples/v4optstype.go` +[source,go] +---- +include::examples/v4optstype.go[] +---- +==== + [id="xtra_bitpacked_vtf"] ==== IP Version, Traffic Class, Flow Label (IPv6) IPv6 thankfully only has one bitpacking in the header. Unfortunately, it's a triple-whammy. @@ -607,8 +628,58 @@ The IP Version takes up 4 bits (just as in IPv4, except it will always be `6` th [%collapsible] .Example in Go ==== +.`examples/vtf.go` [source,go] ---- include::examples/vtf.go[] ---- ==== + + +[id="xtra_pad"] +=== Padded Fields + +[id="xtra_pad_v4opts"] +==== IPv4 Options +The IPv4 options, if specified, *must* align to a 32-bit/4-byte multiple size. +(In other words, its total length of bytes *must* be cleanly divisible by 4; +or said another way its total length of bits must be cleanly divisible by 32). + +It uses null-byte (`0x00`) padding to achieve this. + +[%collapsible] +.Example in Go +==== +.`examples/v4optspad.go` +[source,go] +---- +include::examples/v4optspad.go[] +---- +==== + +For more extensive details, see https://datatracker.ietf.org/doc/html/rfc791#section-3.1[RFC 791 § 3.1^] +and the https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml[IANA Registered IP Paramaeters^] +(the *Value* column is what should be used as the *Type*, see <>. + +Each option (with two exceptions) is a TLV (<> / <> / <>) structured field. + +The two exceptions for the above are `EOOL` (`0x00`) and `NOP` (`0x01`), both of which occupy only a single byte -- they are *unvalued*, so they have no length or value field. + +[id="xtra_pad_v4opts_t"] +===== Type +See <> for details. + +[id="xtra_pad_v4opts_l"] +===== Length +The *Length* is the length of this _entire_ option as an 8-bit unsigned integer. + +In other words it includes the length of the <> field, the <> field (this field), +*and* the <> field together. + +This is not present for `EOOL` (`0x00`) and `NOP` (`0x01`) (or any other "non-valued" options). + +[id="xtra_pad_v4opts_v"] +===== Value +Value is the option's value. Its <> is variable depending on the <>. + +This is not present for `EOOL` (`0x00`) and `NOP` (`0x01`) (or any other "non-valued" options). diff --git a/README.html b/README.html index e33db01..abd723c 100644 --- a/README.html +++ b/README.html @@ -559,7 +559,7 @@ pre.rouge .gs {
Brent Saner

-Last rendered 2025-08-30 17:05:09 -0400 +Last rendered 2025-08-31 00:29:24 -0400
Table of Contents
@@ -625,7 +625,19 @@ pre.rouge .gs {
  • 8.1.1. IP Version, IHL (IPv4)
  • 8.1.2. DSCP, ECN (IPv4)
  • 8.1.3. Flags, Fragmentation Offset (IPv4)
  • -
  • 8.1.4. IP Version, Traffic Class, Flow Label (IPv6)
  • +
  • 8.1.4. IPv4 Option Type
  • +
  • 8.1.5. IP Version, Traffic Class, Flow Label (IPv6)
  • + + +
  • 8.2. Padded Fields +
  • @@ -1700,7 +1712,7 @@ If using the online version, Part I-2 is summarized/starts Example in Go
    -
    hasflag.go
    +
    examples/hasflag.go
    @@ -2439,12 +2451,13 @@ IPv6 does not require a header checksum.

    Example in Go
    +
    examples/vihl.go
    package main
     
     import (
    -	`fmt`
    -	`log`
    +	"fmt"
    +	"log"
     	"strconv"
     )
     
    @@ -2573,12 +2586,13 @@ IPv6 does not require a header checksum.

    Example in Go
    +
    examples/de.go
    package main
     
     import (
    -	`fmt`
    -	`log`
    +	"fmt"
    +	"log"
     	"strconv"
     )
     
    @@ -2665,12 +2679,13 @@ IPv6 does not require a header checksum.

    Example in Go
    +
    examples/frag.go
    package main
     
     import (
    -	`fmt`
    -	`log`
    +	"fmt"
    +	"log"
     	"strconv"
     )
     
    @@ -2747,7 +2762,147 @@ IPv6 does not require a header checksum.

    -

    8.1.4. IP Version, Traffic Class, Flow Label (IPv6)

    +

    8.1.4. IPv4 Option Type

    +
    +

    The Type field of an IPv4 option is a single byte that consists of:

    +
    +
    +
      +
    • +

      A "Copy" flag (1 Bit)

      +
    • +
    • +

      A "Class" flag (2 Bits)

      +
    • +
    • +

      An Option "Number" (5 Bits)

      +
    • +
    +
    +
    +Example in Go +
    +
    +
    examples/v4optstype.go
    +
    +
    package main
    +
    +import (
    +	"fmt"
    +	"log"
    +	"strconv"
    +)
    +
    +const (
    +	ClassControl uint8 = iota
    +	ClassReserved
    +	ClassDebug
    +	ClassReserved2
    +)
    +
    +var (
    +	classStr map[uint8]string = map[uint8]string{
    +		ClassControl:   "Control",
    +		ClassReserved:  "Reserved (1)",
    +		ClassDebug:     "Debugging/Measurement",
    +		ClassReserved2: "Reserved (2)",
    +	}
    +)
    +
    +const (
    +	/// This is the same type from examples/v4optspad.go.
    +	ip4OptTypBits string = "10010100" // [1001 0100], or 148 (0x94)
    +)
    +
    +var (
    +	v4TypCpyOffset uint8 = 0x80             // 128
    +	v4TypCpyMask   uint8 = 0x01             // Mask to 1 bit
    +	v4TypCpyPos    uint8 = 8 - v4TypCpyMask // 7 or 0x07 (8 (bits) - mask = Shifted to 7th bit)
    +
    +	v4TypClsOffset uint8 = 0x60 // 96
    +	v4TypClsMask   uint8 = 0x05 // mask to 5 bits
    +	v4TypClsPos    uint8 = 8 - v4TypClsMask
    +
    +	v4TypNumOffset uint8 = 0x1f // 31
    +)
    +
    +func ToV4OptTyp(copied, class, num uint8) (typ uint8) {
    +
    +	typ = ((copied & v4TypCpyMask) << v4TypCpyPos) | ((class & v4TypClsMask) << v4TypClsPos) | (num & v4TypNumOffset)
    +
    +	return
    +}
    +
    +func FromV4OptTyp(typ uint8) (copied, class, num uint8) {
    +
    +	copied = (typ & v4TypCpyOffset) >> v4TypCpyPos
    +	class = (typ & v4TypClsOffset) >> v4TypClsPos
    +	num = (typ & v4TypNumOffset)
    +
    +	return
    +}
    +
    +func main() {
    +
    +	var err error
    +	var u64 uint64
    +	var typ uint8
    +	var cpd uint8
    +	var cls uint8
    +	var num uint8
    +
    +	// Given a type of ip4OptTypBits (see const at top)...
    +	if u64, err = strconv.ParseUint(ip4OptTypBits, 2, 8); err != nil {
    +		log.Panicln(err)
    +	}
    +	typ = uint8(u64)
    +	// Prints:
    +	/*
    +		Type is:	148 (0x0094)
    +	*/
    +	fmt.Printf("Type is:\t%d (%#04x)\n", typ, typ)
    +
    +	cpd, cls, num = FromV4OptTyp(typ)
    +	// Prints:
    +	/*
    +		Copied:		1 (0x0001)
    +		Class:		0 (0x0000)
    +		Number:		20 (0x0014)
    +	*/
    +	fmt.Printf(
    +		"Copied:\t\t%d %#04x)\n"+
    +			"Class:\t\t%d (%#04x)\n"+
    +			"Number:\t\t%d (%#04x)\n",
    +		cpd, cpd,
    +		cls, cls,
    +		num, num,
    +	)
    +
    +	typ = ToV4OptTyp(cpd, cls, num)
    +	// Prints:
    +	/*
    +		Confirmed Type:	148 (0x94)
    +	*/
    +	fmt.Printf("Confirmed Type:\t%d (%#02x)\n\n", typ, typ)
    +
    +	fmt.Println("Class Name:")
    +	// Prints:
    +	/*
    +		Control
    +	*/
    +	for c, cNm := range classStr {
    +		if c == cls {
    +			fmt.Println(cNm)
    +		}
    +	}
    +}
    +
    +
    +
    +
    +
    +
    +

    8.1.5. IP Version, Traffic Class, Flow Label (IPv6)

    IPv6 thankfully only has one bitpacking in the header. Unfortunately, it’s a triple-whammy.

    @@ -2774,12 +2929,13 @@ IPv6 does not require a header checksum.

    Example in Go
    +
    examples/vtf.go
    package main
     
     import (
    -	`fmt`
    -	`log`
    +	"fmt"
    +	"log"
     	"strconv"
     )
     
    @@ -2866,12 +3022,148 @@ IPv6 does not require a header checksum.

    +
    +

    8.2. Padded Fields

    +
    +

    8.2.1. IPv4 Options

    +
    +

    The IPv4 options, if specified, must align to a 32-bit/4-byte multiple size. +(In other words, its total length of bytes must be cleanly divisible by 4; +or said another way its total length of bits must be cleanly divisible by 32).

    +
    +
    +

    It uses null-byte (0x00) padding to achieve this.

    +
    +
    +Example in Go +
    +
    +
    examples/v4optspad.go
    +
    +
    package main
    +
    +import (
    +	"bytes"
    +	"fmt"
    +)
    +
    +const (
    +	padVal   uint8 = 0x00 // Padded with NUL/null-bytes
    +	alignLen int   = 4    // 4-Byte alignment
    +)
    +
    +var (
    +	// See the examples/v4opts.go for how these bytes are constructed.
    +	opts []byte = []byte{
    +		// This is an example.
    +		// Option 1
    +		0x94,       // Type 148, RTRALT (Router Alert) (RFC 2113)
    +		0x04,       // Length (4 bytes)
    +		0x00, 0x00, // "Router shall examine packet"
    +		// EOOL
    +		0x00,
    +		// Padding will go here.
    +	}
    +)
    +
    +func main() {
    +
    +	var optLen int
    +	var padLen int
    +	var pad []byte
    +
    +	optLen = len(opts)
    +
    +	fmt.Println("Before padding:")
    +	// Prints:
    +	/*
    +		0x9404000000
    +		(Length: 5)
    +	*/
    +	fmt.Printf("%#02x\n(Length: %d)\n\n", opts, optLen)
    +
    +	/*
    +		The remainder of the current length divided by
    +		alignLen (4) (modulo) is subtracted from the alignLen
    +		to determine how much must be added to reach the next
    +		"boundary". It's then modulo'd *again* to rule out
    +		currently being on an alignment bounary.
    +	*/
    +	padLen = (alignLen - (optLen % alignLen)) % alignLen
    +	// Prints:
    +	/*
    +		Pad length needed:      3
    +	*/
    +	fmt.Printf("Pad length needed:\t%d\n\n", padLen)
    +	pad = bytes.Repeat([]byte{padVal}, padLen)
    +
    +	opts = append(opts, pad...)
    +
    +	// Alternatively, this can be implemented with a loop, though it's likely less efficient:
    +	/*
    +		for len(opts) % alignLen != 0 {}
    +			opts = append(opts, padVal)
    +		}
    +	*/
    +
    +	// Prints:
    +	/*
    +		Padded:
    +		0x9404000000000000
    +	*/
    +	fmt.Printf("Padded:\n%#x\n", opts)
    +}
    +
    +
    +
    +
    +
    +

    For more extensive details, see RFC 791 § 3.1 +and the IANA Registered IP Paramaeters +(the Value column is what should be used as the Type, see IPv4 Option Type.

    +
    +
    +

    Each option (with two exceptions) is a TLV (type / length / value) structured field.

    +
    +
    +

    The two exceptions for the above are EOOL (0x00) and NOP (0x01), both of which occupy only a single byte — they are unvalued, so they have no length or value field.

    +
    +
    +
    8.2.1.1. Type
    +
    +

    See IPv4 Option Type for details.

    +
    +
    +
    +
    8.2.1.2. Length
    +
    +

    The Length is the length of this entire option as an 8-bit unsigned integer.

    +
    +
    +

    In other words it includes the length of the Type field, the Length field (this field), +and the Value field together.

    +
    +
    +

    This is not present for EOOL (0x00) and NOP (0x01) (or any other "non-valued" options).

    +
    +
    +
    +
    8.2.1.3. Value
    +
    +

    Value is the option’s value. Its Length is variable depending on the Type.

    +
    +
    +

    This is not present for EOOL (0x00) and NOP (0x01) (or any other "non-valued" options).

    +
    +
    +
    +
    diff --git a/examples/de.go b/examples/de.go index c291505..b865d9e 100644 --- a/examples/de.go +++ b/examples/de.go @@ -1,8 +1,8 @@ package main import ( - `fmt` - `log` + "fmt" + "log" "strconv" ) diff --git a/examples/frag.go b/examples/frag.go index 94505af..01aa66f 100644 --- a/examples/frag.go +++ b/examples/frag.go @@ -1,8 +1,8 @@ package main import ( - `fmt` - `log` + "fmt" + "log" "strconv" ) diff --git a/examples/v4optspad.go b/examples/v4optspad.go new file mode 100644 index 0000000..e3f0801 --- /dev/null +++ b/examples/v4optspad.go @@ -0,0 +1,73 @@ +package main + +import ( + "bytes" + "fmt" +) + +const ( + padVal uint8 = 0x00 // Padded with NUL/null-bytes + alignLen int = 4 // 4-Byte alignment +) + +var ( + // See the examples/v4opts.go for how these bytes are constructed. + opts []byte = []byte{ + // This is an example. + // Option 1 + 0x94, // Type 148, RTRALT (Router Alert) (RFC 2113) + 0x04, // Length (4 bytes) + 0x00, 0x00, // "Router shall examine packet" + // EOOL + 0x00, + // Padding will go here. + } +) + +func main() { + + var optLen int + var padLen int + var pad []byte + + optLen = len(opts) + + fmt.Println("Before padding:") + // Prints: + /* + 0x9404000000 + (Length: 5) + */ + fmt.Printf("%#02x\n(Length: %d)\n\n", opts, optLen) + + /* + The remainder of the current length divided by + alignLen (4) (modulo) is subtracted from the alignLen + to determine how much must be added to reach the next + "boundary". It's then modulo'd *again* to rule out + currently being on an alignment bounary. + */ + padLen = (alignLen - (optLen % alignLen)) % alignLen + // Prints: + /* + Pad length needed: 3 + */ + fmt.Printf("Pad length needed:\t%d\n\n", padLen) + pad = bytes.Repeat([]byte{padVal}, padLen) + + opts = append(opts, pad...) + + // Alternatively, this can be implemented with a loop, though it's likely less efficient: + /* + for len(opts) % alignLen != 0 {} + opts = append(opts, padVal) + } + */ + + // Prints: + /* + Padded: + 0x9404000000000000 + */ + fmt.Printf("Padded:\n%#x\n", opts) +} diff --git a/examples/v4optstype.go b/examples/v4optstype.go new file mode 100644 index 0000000..76604bd --- /dev/null +++ b/examples/v4optstype.go @@ -0,0 +1,111 @@ +package main + +import ( + "fmt" + "log" + "strconv" +) + +const ( + ClassControl uint8 = iota + ClassReserved + ClassDebug + ClassReserved2 +) + +var ( + classStr map[uint8]string = map[uint8]string{ + ClassControl: "Control", + ClassReserved: "Reserved (1)", + ClassDebug: "Debugging/Measurement", + ClassReserved2: "Reserved (2)", + } +) + +const ( + /// This is the same type from examples/v4optspad.go. + ip4OptTypBits string = "10010100" // [1001 0100], or 148 (0x94) +) + +var ( + v4TypCpyOffset uint8 = 0x80 // 128 + v4TypCpyMask uint8 = 0x01 // Mask to 1 bit + v4TypCpyPos uint8 = 8 - v4TypCpyMask // 7 or 0x07 (8 (bits) - mask = Shifted to 7th bit) + + v4TypClsOffset uint8 = 0x60 // 96 + v4TypClsMask uint8 = 0x05 // mask to 5 bits + v4TypClsPos uint8 = 8 - v4TypClsMask + + v4TypNumOffset uint8 = 0x1f // 31 +) + +func ToV4OptTyp(copied, class, num uint8) (typ uint8) { + + typ = ((copied & v4TypCpyMask) << v4TypCpyPos) | ((class & v4TypClsMask) << v4TypClsPos) | (num & v4TypNumOffset) + + return +} + +func FromV4OptTyp(typ uint8) (copied, class, num uint8) { + + copied = (typ & v4TypCpyOffset) >> v4TypCpyPos + class = (typ & v4TypClsOffset) >> v4TypClsPos + num = (typ & v4TypNumOffset) + + return +} + +func main() { + + var err error + var u64 uint64 + var typ uint8 + var cpd uint8 + var cls uint8 + var num uint8 + + // Given a type of ip4OptTypBits (see const at top)... + if u64, err = strconv.ParseUint(ip4OptTypBits, 2, 8); err != nil { + log.Panicln(err) + } + typ = uint8(u64) + // Prints: + /* + Type is: 148 (0x0094) + */ + fmt.Printf("Type is:\t%d (%#04x)\n", typ, typ) + + cpd, cls, num = FromV4OptTyp(typ) + // Prints: + /* + Copied: 1 (0x0001) + Class: 0 (0x0000) + Number: 20 (0x0014) + */ + fmt.Printf( + "Copied:\t\t%d %#04x)\n"+ + "Class:\t\t%d (%#04x)\n"+ + "Number:\t\t%d (%#04x)\n", + cpd, cpd, + cls, cls, + num, num, + ) + + typ = ToV4OptTyp(cpd, cls, num) + // Prints: + /* + Confirmed Type: 148 (0x94) + */ + fmt.Printf("Confirmed Type:\t%d (%#02x)\n\n", typ, typ) + + fmt.Println("Class Name:") + // Prints: + /* + Control + */ + for c, cNm := range classStr { + if c == cls { + fmt.Println(cNm) + } + } +} diff --git a/examples/vihl.go b/examples/vihl.go index 338e07e..bc4ad83 100644 --- a/examples/vihl.go +++ b/examples/vihl.go @@ -1,8 +1,8 @@ package main import ( - `fmt` - `log` + "fmt" + "log" "strconv" ) diff --git a/examples/vtf.go b/examples/vtf.go index bdde1e4..19d89d5 100644 --- a/examples/vtf.go +++ b/examples/vtf.go @@ -1,8 +1,8 @@ package main import ( - `fmt` - `log` + "fmt" + "log" "strconv" )