Skip to content

Instantly share code, notes, and snippets.

@Thraetaona
Last active July 20, 2024 04:52
Show Gist options
  • Save Thraetaona/22be71280d781ea9629597244f53af86 to your computer and use it in GitHub Desktop.
Save Thraetaona/22be71280d781ea9629597244f53af86 to your computer and use it in GitHub Desktop.
C11 TCP/IP header structures
// ---------------------------------------------------------------------
// SPDX-License-Identifier: LGPL-3.0-or-later
// ---------------------------------------------------------------------
_Pragma ("once")
#ifndef NETINET_H
#define NETINET_H
// NOTE: Unfortunately, <netinet/ip.h> is not a part of the POSIX
// standard, while <netinet/in.h> and <netinet/tcp.h> somehow are.
// However, even the latter are rather useless, because most of their
// definitions are hidden behind non-standard BSD or System V-specific
// feature flags. In any case, the headers defined there are far
// too outdated, still referring to many fields as "reserved."
// For that reason, and for future compatibility with Win32,
// it is better to define our own protocol header structures.
// NOTE: These included header files should not actually make this
// file dependent upon libc (at runtime), because they are all
// "compile-time" dependencies (except assert() that can be disabled).
#include <stdint.h>
#include <stdbool.h>
// Uncomment (or define via compiler) to disable runtime assert()
//#define NDEBUG
#include <assert.h> // Defines _Static_assert() to be static_assert()
// NOTE: Binary integer literals (e.g., 0b10101010) are a GNU extension;
// unfortunately, they are not part of the C11 standard. (C23 N2549)
// NOTE: Use the standard C99 _Pragma statement rather than #pragma.
// NOTE: You cannot use enums directly inside macro expansions!
// NOTE: C does not let you put "flexible array members" inside
// of unions (or nested structs in unions), even if they both
// have the exact same type...
// NOTE: You can not define anonymous members of unions inside structs,
// so you will have to copy-paste the same union definition for each.
// To make it easier, I had to define some union definitions as pre-
// processor macros. This is especially imperative if your union
// contains non-byte bitfields (which are expected to get packed
// in the exterior struct), because the compiler does not seem
// to honor the "internal" bitfields inside of nested unions/structs.
// NOTE: Do NOT pack the "main" structs (e.g., ip_hdr or tcp_hdr);
// they will be casted to pointers, and we don't want half-words there.
// NOTE: You cannot have namespaces (i.e., enums inside named structs)
// in C, unlike C++. For that reason, make sure to use prefixes to
// properly denote enums/defines, such as TCP_FLAGS_SYN instead of
// just SYN or TCP_SYN.
// NOTE: The C Standardization Committee was sometimes so out-of-
// touch with their decisions; namely, at least before C23, you are
// unable to specify the underlying "type" of an enum. This means that
// an enum with values that are all between 0-255 "may or may not"
// result in a uint8_t, depending on the compiler's discretion.
// Yes, C was supposed to be a "portable assembler," but it is also
// widely used as a systems programming language, where bit-level and
// endianness control is crucial. (C23 N3030)
// Check for C23 support
#if __STDC_VERSION__ >= 202300L // C23 or later
# define ENUM_UNDERLYING(type) : type
#else // Fallback for older standards (e.g., C11)
# define ENUM_UNDERLYING(type) __attribute__((packed))
#endif
typedef enum endianness {
little_endian,
big_endian,
} endianness_t;
// Check endianness at compile-time for the target machine
#if defined(__BYTE_ORDER__) && !(defined(__LITTLE_ENDIAN__) \
|| defined(__BIG_ENDIAN__))
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define __LITTLE_ENDIAN__
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define __BIG_ENDIAN__
# else
# error "Define endianness: __LITTLE_ENDIAN__ or __BIG_ENDIAN__."
# endif
#endif
// Double-check endianness at runtime on the target machine.
inline endianness_t check_endianness() {
union {
int word;
char byte[sizeof (int)];
} same_memory;
same_memory.word = 1;
// Check if the least-significant stored byte is 1 (little endian)
endianness_t runtime_endianness = (same_memory.byte[0] == 1) ?
little_endian : big_endian;
return runtime_endianness;
}
/* START OF THE
Internet Protocol 4 (IPv4)
Header Structure
*/
// IP Address type(s)
typedef union ip_addr_view {
uint32_t address; // IP address in uint32_t form
uint8_t octets[4]; // Four octets (bytes) notation
} ip_addr_t;
// IP Protocol definitions
_Pragma ("pack(push)")
typedef enum __attribute__((packed)) ip_proto {
IP_PROTO_IP = 0,
IP_PROTO_ICMP = 1,
IP_PROTO_TCP = 6,
IP_PROTO_UDP = 17,
} ip_proto_t;
_Pragma ("pack(pop)")
_Pragma ("pack(push)")
typedef enum __attribute__((packed)) ip_tos_prec {
// RFC 791
IP_TOS_PREC_ROUTINE = 1, // 0b000
IP_TOS_PREC_PRIORITY = 2, // 0b001
IP_TOS_PREC_IMMEDIATE = 3, // 0b010
IP_TOS_PREC_FLASH = 4, // 0b011
IP_TOS_PREC_FLASH_OVERRIDE = 5, // 0b100
IP_TOS_PREC_CRITIC_ECP = 5, // 0b101
IP_TOS_PREC_INTERNETWORK_CONTROL = 6, // 0b110
IP_TOS_PREC_NETWORK_CONTROL = 7, // 0b111
} ip_tos_prec_t;
_Pragma ("pack(pop)")
// ECN Operations definitions
// (NOT backward-compatible with the old ToS field.)
_Pragma ("pack(push)")
typedef enum __attribute__((packed)) ip_ecn_code {
// RFC 3168
IP_ECN_NOT_ECT = 0, // 0b00: Not ECN-Capable Transport
// RFC 8311 / RFC Errata 5399 / RFC 9331
IP_ECN_ECT_1 = 1, // 0b01: ECN-Capable Transport 1 (experimental)
// RFC 3168
IP_ECN_ECT_0 = 2, // 0b10: ECN-Capable Transport 0
IP_ECN_CE = 3, // 0b11: Congestion Experienced (CE)
} ip_ecn_code_t;
_Pragma ("pack(pop)")
// DSCP definitions / Per-Hop Behaviors
// (somewhat backward-compatible with the old ToS precedence bits.)
// iana.org/assignments/dscp-registry/dscp-registry.xhtml
_Pragma ("pack(push)")
typedef enum __attribute__((packed)) ip_dscp_code {
/* DSCP Pool 1 Codepoints () */
// RFC 2474 (Class Selector PHBs)
IP_DSCP_DF = 0, // Default Forwarding (DF) PHB
IP_DSCP_CS0 = 0, // CS 0 (standard)
IP_DSCP_CS1 = 8, // CS 1 (low-priority data)
IP_DSCP_CS2 = 16, // CS 2 (network operations/OAM)
IP_DSCP_CS3 = 24, // CS 3 (broadcast video))
IP_DSCP_CS4 = 32, // CS 4 (real-time interactive)
IP_DSCP_CS5 = 40, // CS 5 (signaling)
IP_DSCP_CS6 = 48, // CS 6 (network control)
IP_DSCP_CS7 = 56, // CS 7 (reserved)
// RFC 2597 (Assured Forwarding (AF) PHB)
IP_DSCP_AF11 = 10, // AF 11 (high-throughput)
IP_DSCP_AF12 = 12, // AF 12 (high-throughput)
IP_DSCP_AF13 = 14, // AF 13 (high-throughput)
IP_DSCP_AF21 = 18, // AF 21 (low-latency)
IP_DSCP_AF22 = 20, // AF 22 (low-latency)
IP_DSCP_AF23 = 22, // AF 23 (low-latency)
IP_DSCP_AF31 = 26, // AF 31 (multimedia stream)
IP_DSCP_AF32 = 28, // AF 32 (multimedia stream)
IP_DSCP_AF33 = 30, // AF 33 (multimedia stream)
IP_DSCP_AF41 = 34, // AF 41 (multimedia conference)
IP_DSCP_AF42 = 36, // AF 42 (multimedia conference)
IP_DSCP_AF43 = 38, // AF 43 (multimedia conference)
// RFC 3246 (Expedited Forwarding (EF) PHB)
IP_DSCP_EF = 46, // EF (telephony)
// RFC 5865 (Voice-Admit)
IP_DSCP_VA = 44, // Voice-Admit
/* DSCP Pool 2 Codepoints */
// "Reserved for Experimental and Local Use" (empty)
/* DSCP Pool 3 Codepoints */
// RFC 8622
IP_DSCP_LE = 1, // Lower-Effort PHB
} ip_dscp_code_t;
_Pragma ("pack(pop)")
// The "Type of Service" field is largely unused (or ignored) nowadays.
// It went through many changes, which makes it confusing to see how it
// maps out to values. I included the original (now deprecated) ToS
// field with its expanded bits as well as the "modern" DSCP + ECN.
union ip_tos_view {
uint8_t bitfield;
// RFC 791 and RFC 1349 (deprecated)
struct { // ToS
#if defined(__LITTLE_ENDIAN__)
bool must_be_zero : 1;
bool low_cost : 1; // RFC 1349
bool high_reliability : 1;
bool high_throughput : 1;
bool low_delay : 1;
ip_tos_prec_t precedence : 3;
#elif defined(__BIG_ENDIAN__)
ip_tos_prec_t precedence : 3;
bool low_delay : 1;
bool high_throughput : 1;
bool high_reliability : 1;
bool low_cost : 1;
bool must_be_zero : 1;
#endif
};
};
// IP flag definitions
_Pragma ("pack(push)")
typedef enum __attribute__((packed)) ip_flag {
IP_FLAG_EV = 1 << 2, // 0b100: Reserved bit (RFC 3514 "evil bit")
IP_FLAG_DF = 1 << 1, // 0b010: Don't Fragment
IP_FLAG_MF = 1 << 0, // 0b001: More Fragments
} ip_flag_t;
_Pragma ("pack(pop)")
// Cannot nest bitfields and pack them externally; read the top note.
//
// Basically, because the fragment offset (fragofs) and flags field
// in an IPv4 packet are packed in a weird way (13 + 3 bits = 16),
// C compilers packs those 3 bits (if they are defined inside their
// own struct) into a full 8-bit byte, meaning that it becomes 13 + 8
// = 21 bits. To fix that, I had to copy-paste this otherwise-neat
// union inside the ip_hdr struct and also add some hacky padding
// ("uint16_t __fragofs_bug : 13;") to work around this issue.
union ip_flag_view {
// View no. 1 (as an entire bitfield)
ip_flag_t bitfield : 3;
// View no. 2 (with expanded flag bits)
struct {
#if defined(__LITTLE_ENDIAN__)
bool mf : 1; // More Fragments
bool df : 1; // Don't Fragment
bool ev : 1; // Evil/reserved bit
#elif defined(__BIG_ENDIAN__)
bool ev : 1;
bool df : 1;
bool mf : 1;
#endif
};
};
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |Version| IHL | DSCP |ECN| Total Length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Identification |Flags| Fragment Offset |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Time to Live | Protocol | Header Checksum |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Source Address |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Destination Address |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Options | Padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
typedef struct ip_hdr {
#if defined(__LITTLE_ENDIAN__)
uint8_t ihl : 4; // Internet Header Length
uint8_t ver : 4; // IP Version
#elif defined(__BIG_ENDIAN__)
uint8_t ver : 4;
uint8_t ihl : 4;
#endif
union {
// RFC 791 and RFC 1349
union ip_tos_view tos; // Type of Service (ToS)
// RFC 2474 (DSCP + ECN)
struct {
#if defined(__LITTLE_ENDIAN__)
ip_ecn_code_t ecn : 2; // Explicit Congestion Notif
ip_dscp_code_t dscp : 6; // Differentiated Services
#elif defined(__BIG_ENDIAN__)
ip_dscp_code_t dscp : 6;
ip_ecn_code_t ecn : 2;
#endif
};
};
uint16_t len; // Total Length
uint16_t id; // Identification
#if defined(__LITTLE_ENDIAN__)
union {
struct {
uint16_t fragofs : 13; // Fragment Offset
//union ip_flag_view flags; // "Would-be" flags
ip_flag_t flag_bits : 3; // Workaround flag bitfield
};
struct {
uint16_t __struct_bug__ : 13; // [Read ip_flag_view note] (*)
uint8_t mf : 1; // More Fragments
uint8_t df : 1; // Don't Fragment
uint8_t ev : 1; // Evil/reserved bit
} flags;
};
#elif defined(__BIG_ENDIAN__)
union {
struct {
ip_flag_t flag_bits : 3;
//union ip_flag_view flags;
uint16_t fragofs : 13;
};
struct {
uint8_t mf : 1;
uint8_t df : 1;
uint8_t ev : 1;
uint16_t __struct_bug__ : 13; // [Read ip_flag_view note] (*)
} flags;
};
#endif
uint8_t ttl; // Time to live
ip_proto_t proto : 8; // Protocol
uint16_t chksum; // Header Checksum
ip_addr_t saddr; // Source Address
ip_addr_t daddr; // Destination Address
uint32_t options[]; // IP Options (0-320 bits)
} ip_hdr_t;
// TODO: Make a struct for IP options
static_assert(sizeof (ip_hdr_t) == 20,
"An empty ip_hdr struct should only be 20 bytes!");
/* END OF THE
Internet Protocol (IP)
Header Structure
*/
/* START OF THE
Transmission Control Protocol (TCP)
Header Structure
*/
// TCP flag definitions
_Pragma ("pack(push)")
typedef enum __attribute__((packed)) tcp_flag {
TCP_FLAG_FIN = 1 << 0, // 0b00000001: Finish
TCP_FLAG_SYN = 1 << 1, // 0b00000010: Synchronize
TCP_FLAG_RST = 1 << 2, // 0b00000100: Reset
TCP_FLAG_PSH = 1 << 3, // 0b00001000: Push
TCP_FLAG_ACK = 1 << 4, // 0b00010000: Acknowledgment
TCP_FLAG_URG = 1 << 5, // 0b00100000: Urgent
TCP_FLAG_ECE = 1 << 6, // 0b01000000: ECN-Echo
TCP_FLAG_CWR = 1 << 7, // 0b10000000: Congestion Window Reduced
} tcp_flag_t;
_Pragma ("pack(pop)")
union tcp_flag_view {
// View no. 1 (as an entire byte)
tcp_flag_t bitfield : 8;
// View no. 2 (with expanded flag bits)
struct {
#if defined(__LITTLE_ENDIAN__)
bool fin : 1; // Finish
bool syn : 1; // Synchronize
bool rst : 1; // Reset
bool psh : 1; // Push
bool ack : 1; // Acknowledgment
bool urg : 1; // Urgent
bool ece : 1; // ECN-Echo
bool cwr : 1; // Congestion Window Reduced
#elif defined(__BIG_ENDIAN__)
bool cwr : 1;
bool ece : 1;
bool urg : 1;
bool ack : 1;
bool psh : 1;
bool rst : 1;
bool syn : 1;
bool fin : 1;
#endif
};
};
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Source Port | Destination Port |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Sequence Number |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Acknowledgment Number |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Data | |C|E|U|A|P|R|S|F| |
// | Offset| Rsrvd |W|C|R|C|S|S|Y|I| Window |
// | | |R|E|G|K|H|T|N|N| |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Checksum | Urgent Pointer |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | [Options] |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | :
// : Data :
// : |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
typedef struct tcp_hdr {
uint16_t sport; // Source port number
uint16_t dport; // Destination port number
uint32_t seqnum; // Sequence number
uint32_t acknum; // Acknowledgement number
#if defined(__LITTLE_ENDIAN__)
uint8_t reserved : 4; // Reserved (unused) bits
uint8_t dataofs : 4; // Offset to data/options
#elif defined(__BIG_ENDIAN__)
uint8_t dataofs : 4; // Offset to data/options
uint8_t reserved : 4; // Reserved (unused) bits
#endif
union tcp_flag_view flags; // TCP flags
uint16_t window; // Window size
uint16_t chksum; // Checksum
uint16_t urgptr; // Pointer to urgent data
uint32_t options[]; // TCP Options (0-320 bits)
} tcp_hdr_t;
// TODO: Make options a struct
static_assert(sizeof (tcp_hdr_t) == 20,
"An empty tcp_hdr struct should only be 20 bytes!");
/* END OF THE
Transmission Control Protocol (TCP)
Header Structure
*/
#endif // NETINET_H
// ---------------------------------------------------------------------
// END OF FILE: netinet.h
// ---------------------------------------------------------------------
@Thraetaona
Copy link
Author

Note that this has been taken from my own project, Blitzping (https://github.com/Thraetaona/Blitzping), and re-released under the more permissive LGPLv3.0 license here; this file is a single-header definition of only IPv4 and TCP, while the files there will continue to get updated, and I will also add IPv6 and UDP in the future.

For some strange reason or due to mere oversight, the actual POSIX standard defines a barebones <netinet/tcp.h> without actually defining a tcphdr per se, making it entirely pointless to use in the first place, and <netinet/ip.h> does not even exist as part of the POSIX standard. In any case, those headers are not only nonstandard but they are also very ancient; they lag behind newer RFCs by decades. This warranted me to write my own "netinet.h", which I think is (by far) the most feature-complete and cleanest TCP/IP stack implementation in C; despite supporting new fields, I still made sure to remain backward-compatible with old RFCs (for example, in IPv4, I support both the newer DSCP/ECN codepoint pools and the older ToS/Precedence values).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment