Installation
Build from source
Requires CMake and a C/C++ toolchain. Run these commands from the repository root:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
cmake --install build --config Release
The default build produces a static library. Add -DBUILD_SHARED_LIBS=ON to the configure command
for a shared library, or -DCMAKE_INSTALL_PREFIX=/your/prefix to choose an install location.
CMake and pkg-config
After installing, link the exported CMake target:
find_package(ipv6-parse REQUIRED)
target_link_libraries(myapp PRIVATE ipv6-parse::ipv6-parse)
Or compile with pkg-config:
cc myapp.c $(pkg-config --cflags --libs ipv6-parse) -o myapp
To include the source in an existing CMake project, use add_subdirectory and link the
ipv6-parse target. The implementation is in ipv6.c, with declarations in
ipv6.h and a configuration header generated by CMake.
Packages
Download Debian, RPM, and WebAssembly assets from the release page. For native packages, use the actual downloaded filename:
sudo apt install ./<package-file>.deb
# Fedora / RHEL:
sudo dnf install ./<package-file>.rpm
The repository also contains a Homebrew formula, a Conan recipe, and a vcpkg port. Setup and integration examples: Homebrew, Conan, and vcpkg.
Parse an address and read its fields
Save this as example.c. A successful parse fills a structure; flags distinguish missing fields
from fields whose value is zero.
#include "ipv6.h"
#include <stdio.h>
#include <string.h>
int main(void) {
const char *input = "[fe80::7/64%eth0]:8080";
ipv6_address_full_t addr = {0};
if (!ipv6_from_str(input, strlen(input), &addr)) {
fputs("Invalid address\n", stderr);
return 1;
}
if (addr.flags & IPV6_FLAG_HAS_PORT) {
printf("Port: %u\n", (unsigned)addr.port);
}
if (addr.flags & IPV6_FLAG_HAS_MASK) {
printf("Prefix length: %u\n", (unsigned)addr.mask);
}
if (addr.iface_len) {
printf("Zone: %.*s\n", (int)addr.iface_len, addr.iface);
}
char output[IPV6_STRING_SIZE];
ipv6_to_str(&addr, output, sizeof(output));
puts(output); // [fe80::7/64%eth0]:8080
return 0;
}
After installing the library, compile and run it:
cc example.c $(pkg-config --cflags --libs ipv6-parse) -o example
./example
The header also supports C++ callers through extern "C".
Keep the input alive for zone IDs
address, port, and mask are values stored in the result.
iface is a pointer into your input string, with length iface_len.
It is not copied and is not necessarily NUL-terminated. Keep the input alive and unchanged while reading
the zone or formatting the result. Copying the structure does not copy the zone text.
The native API uses caller-owned memory without dynamic allocation. Allocate
IPV6_STRING_SIZE bytes for formatted output. Plain IPv4 parsing retains a CIDR mask in
the structure, but its formatter currently omits that mask; read mask directly when needed.
Find out why parsing failed
Use a diagnostic callback when a boolean result is not enough. This example reports the event and input position.
#include "ipv6.h"
#include <stdio.h>
#include <string.h>
static void report_error(ipv6_diag_event_t event,
const ipv6_diag_info_t *info, void *user_data) {
FILE *stream = (FILE *)user_data;
fprintf(stream, "Event %u at position %u: %s\n",
(unsigned)event, (unsigned)info->position, info->message);
}
int main(void) {
const char *input = "2001:gggg::7";
ipv6_address_full_t addr = {0};
bool valid = ipv6_from_str_diag(input, strlen(input), &addr,
report_error, stderr);
return valid ? 1 : 0; // This example expects a parse failure.
}
Use the result only after a successful parse. Consume diagnostic information in the callback; copy anything you need to retain.
Compare values rather than spellings
Expanded and compressed forms can identify the same address. Parse both inputs before comparing them.
#include "ipv6.h"
#include <stdio.h>
#include <string.h>
int main(void) {
const char *left = "[2001:db8::7]:80";
const char *right = "[2001:0db8:0:0:0:0:0:7]:443";
ipv6_address_full_t a = {0}, b = {0};
if (!ipv6_from_str(left, strlen(left), &a) ||
!ipv6_from_str(right, strlen(right), &b)) {
return 1;
}
bool equal = ipv6_compare(&a, &b, IPV6_FLAG_HAS_PORT) == IPV6_COMPARE_OK;
puts(equal ? "Same address, ignoring port" : "Different addresses");
return equal ? 0 : 1;
}
Comparison checks address values and the selected metadata. It does not test whether one address belongs to another address's prefix. Zone IDs are not compared; compare their lengths and contents separately if your application distinguishes interfaces.
Plain and embedded IPv4
Plain IPv4 sets IPV6_FLAG_IPV4_COMPAT and uses the first two 16-bit components:
192.0.2.1 becomes 0xc000, 0x0201.
Dotted IPv4 embedded in IPv6 uses the last two components and sets IPV6_FLAG_IPV4_EMBED.
These flags describe the parsed representation. With default comparison flags, different representations can compare unequal even when their address bits match. Use the documented ignore flags deliberately.
Function reference
Include ipv6.h. Parsing and formatting use caller-owned memory and do not allocate dynamically.
The complete declarations are also available in the
public header.
Parsing
bool ipv6_from_str(const char *input, size_t input_bytes,
ipv6_address_full_t *out);
bool ipv6_from_str_diag(const char *input, size_t input_bytes,
ipv6_address_full_t *out,
ipv6_diag_func_t func, void *user_data);
Pass the input length in bytes, excluding the terminating NUL. Both functions return true
on success and false on failure. Use strlen(input) for a C string.
Only use the parsed result after success.
The diagnostic variant calls your callback with an event code, a message, and the position in the input
associated with the error. user_data is passed through to the callback.
Formatting
size_t ipv6_to_str(const ipv6_address_full_t *in,
char *output, size_t output_bytes);
Supply a buffer of IPV6_STRING_SIZE bytes. The return value is the number of characters written,
excluding the terminating NUL. IPv6 formatting includes the mask, zone ID, and port when present.
Comparison
ipv6_compare_result_t ipv6_compare(const ipv6_address_full_t *a,
const ipv6_address_full_t *b,
uint32_t ignore_flags);
Returns IPV6_COMPARE_OK when equal, or a code identifying a format, address, mask, or port mismatch.
Pass 0 for the default comparison. Combine IPV6_FLAG_HAS_MASK and
IPV6_FLAG_HAS_PORT in ignore_flags to ignore those fields.
Passing IPV6_FLAG_IPV4_EMBED or IPV6_FLAG_IPV4_COMPAT allows comparisons
between plain and embedded IPv4 representations. Zone IDs are not compared.
Address structure and flags
Components are 16-bit values in address order: aa11:bb22:: begins with
0xaa11, 0xbb22. Flags indicate whether a port or mask is present and which IPv4 form is used.
typedef enum {
IPV6_FLAG_HAS_PORT = 0x00000001, // the address specifies a port setting
IPV6_FLAG_HAS_MASK = 0x00000002, // the address specifies a CIDR mask
IPV6_FLAG_IPV4_EMBED = 0x00000004, // the address has an embedded IPv4 address in the last 32bits
IPV6_FLAG_IPV4_COMPAT = 0x00000008, // the address is IPv4 compatible (1.2.3.4:5555)
} ipv6_flag_t;
#define IPV6_NUM_COMPONENTS 8
#define IPV4_NUM_COMPONENTS 2
#define IPV4_EMBED_INDEX 6
typedef struct {
uint16_t components[IPV6_NUM_COMPONENTS];
} ipv6_address_t;
typedef struct {
ipv6_address_t address; // address components
uint16_t port; // port binding
uint16_t pad0; // first padding
uint32_t mask; // number of mask bits N specified for example in ::1/N
const char* iface; // pointer to place in address string where interface is defined
uint32_t iface_len; // number of bytes in the name of the interface
uint32_t flags; // flags indicating features of address
} ipv6_address_full_t;
Diagnostics and result codes
Callback types and result codes
typedef enum {
IPV6_COMPARE_OK = 0,
IPV6_COMPARE_FORMAT_MISMATCH, // address differ in their
IPV6_COMPARE_MASK_MISMATCH, // the CIDR mask does not match
IPV6_COMPARE_PORT_MISMATCH, // the port does not match
IPV6_COMPARE_ADDRESS_MISMATCH, // address components do not match
} ipv6_compare_result_t;
typedef enum {
IPV6_DIAG_STRING_SIZE_EXCEEDED = 0,
IPV6_DIAG_INVALID_INPUT = 1,
IPV6_DIAG_INVALID_INPUT_CHAR = 2,
IPV6_DIAG_TRAILING_ZEROES = 3,
IPV6_DIAG_V6_BAD_COMPONENT_COUNT = 4,
IPV6_DIAG_V4_BAD_COMPONENT_COUNT = 5,
IPV6_DIAG_V6_COMPONENT_OUT_OF_RANGE = 6,
IPV6_DIAG_V4_COMPONENT_OUT_OF_RANGE = 7,
IPV6_DIAG_INVALID_PORT = 8,
IPV6_DIAG_INVALID_CIDR_MASK = 9,
IPV6_DIAG_IPV4_REQUIRED_BITS = 10,
IPV6_DIAG_IPV4_INCORRECT_POSITION = 11,
IPV6_DIAG_INVALID_BRACKETS = 12,
IPV6_DIAG_INVALID_ABBREV = 13,
IPV6_DIAG_INVALID_DECIMAL_TOKEN = 14,
IPV6_DIAG_INVALID_HEX_TOKEN = 15,
} ipv6_diag_event_t;
typedef struct {
const char* message; // English ascii debug message
const char* input; // Input string that generated the diagnostic
uint32_t position; // Position in input that caused the diagnostic
uint32_t pad0;
} ipv6_diag_info_t;
typedef void (*ipv6_diag_func_t) (
ipv6_diag_event_t event,
const ipv6_diag_info_t* info,
void* user_data);