Using ipv6-parse in C

Build the native library, parse into a structure, and format or compare the result.

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);