Getting an IP address from an instance of std.posix.sockaddr

Learning Zig + Network stuff at the same time, and decided to try my hand at implementing a little local network chat server. While working through it, I ran into something that gave me a bit of trouble, and I’m sure there’s a better way to do it that I’ve not yet seen/figured out. Looking for someone to hopefully direct me to some resources that will help me on how to do this in a more “proper” way. Here’s how I currently have the server implemented -

const std = @import("std");
const net = std.net;
const posix = std.posix;
const print = std.debug.print;

pub fn main() !void {
    const mimic_addr = try net.Address.parseIp4("127.0.0.1", 32100);

    // socket boiler; specify attributes of the socket
    const sock = try posix.socket(
        posix.AF.INET,
        posix.SOCK.DGRAM, // DGRAM signals UDP
        posix.IPPROTO.UDP,
    );
    defer posix.close(sock);

    // once you've got a socket built, bind it to a specific port;
    // we defined the port above when parsing an IP4 address of localhost:32100
    try posix.bind(sock, &mimic_addr.any, mimic_addr.getOsSockLen());

    // Client info
    var other_addr: posix.sockaddr align(4) = undefined;
    var other_addrlen: posix.socklen_t = mimic_addr.getOsSockLen();

    var buf: [1024]u8 = undefined;

    print("Listening on {any}...\n", .{mimic_addr});

    var running = true;
     
    while (running) {
        const n_recv = try posix.recvfrom(
            sock,
            buf[0..],
            0,
            &other_addr,
            &other_addrlen,
        );

        const client_address: *align(4) const std.posix.sockaddr.in = @ptrCast(@alignCast(&other_addr));

        print(
            "received {d} byte(s) from {any};\n     string: {s}\n",
            .{ n_recv, @as([4]u8, @bitCast(client_address.addr)), buf[0..n_recv] },
        );

        const quit_cmd = "quit()";
        const is_equal = std.mem.eql(u8, buf[0..6], quit_cmd);
        if (is_equal) {
            print("Received quit command. Exiting.\n", .{});
            running = false;
        } else {
            const reply = buf[0..n_recv];
            _ = try posix.sendto(
                sock,
                reply,
                0,
                &other_addr,
                other_addrlen
            );
            print("Sent read receipt.\n", .{});
        }

    }
}

The piece I’m curious about is getting the client’s IP address from other_addr after I call posix.recvfrom(...

Once the server receives a message from a client, as currently written, it prints the address as {127, 0, 0, 1}. Ideally, I’d like it to print as 127.0.0.1 per the std.net.Address.format definition, and avoid using align(4), @bitCast, @as, @ptrCast, and @alignCast in favor of a more elegant solution.

Like I said above, new to Zig (and honestly, new to lower-level languages in general) so there may be some basic concepts that I’m missing too - apologies and thanks in advance!

1 Like

Hello @twistee_bones Welcome to ziggit :slight_smile:

mimic_addr is an std.net.Address that knows how to format an address and a port.
When using any as format specifier, zig shows the values of the internal structure.
When you don’t specify a format, the default format for Ip4Address.format is used.

print("Listening on {any}...\n", .{mimic_addr});

// Listening on 127.0.0.1:port...
print("Listening on {}...\n", .{mimic_addr});

std.net.Address is a union that contains a sockaddr for the any member. You can declare other_addr: Address instead of other_addr: sockaddr and use &other_addr.any instead of &other_addr.

3 Likes

Thanks for the reply!

For anyone in the future, here’s the code after the fix @dimdin described:

const std = @import("std");
const net = std.net;
const posix = std.posix;
const print = std.debug.print;

pub fn main() !void {
    const mimic_addr = try net.Address.parseIp4("127.0.0.1", 32100);

    const sock = try posix.socket(
        posix.AF.INET,
        posix.SOCK.DGRAM, // DGRAM signals UDP
        posix.IPPROTO.UDP,
    );
    defer posix.close(sock);

    // once you've got a socket built, bind it to a specific port;
    // we defined the port above when parsing an IP4 address of localhost:32100
    try posix.bind(sock, &mimic_addr.any, mimic_addr.getOsSockLen());

    // Client info
    // var other_addr: posix.sockaddr align(4) = undefined;
    var other_addr: net.Address = undefined;
    var other_addrlen: posix.socklen_t = mimic_addr.getOsSockLen();

    var buf: [1024]u8 = undefined;

    print("Listening on {any}...\n", .{mimic_addr});

    var running = true;
     
    while (running) {
        const n_recv = try posix.recvfrom(
            sock,
            buf[0..],
            0,
            &other_addr.any,
            &other_addrlen,
        );

        // const client_address: *align(4) const std.posix.sockaddr.in = @ptrCast(@alignCast(&other_addr));

        print(
            "received {d} byte(s) from {any};\n     string: {s}\n",
            // .{ n_recv, @as([4]u8, @bitCast(client_address.addr)), buf[0..n_recv] },
            .{n_recv, &other_addr.in, buf[0..n_recv]},
        );

        const quit_cmd = "quit()";
        const is_equal = std.mem.eql(u8, buf[0..6], quit_cmd);
        if (is_equal) {
            print("Received quit command. Exiting.\n", .{});
            running = false;
        } else {
            const reply = buf[0..n_recv];
            _ = try posix.sendto(
                sock,
                reply,
                0,
                &other_addr.any,
                other_addrlen
            );
            print("Sent read receipt.\n", .{});
        }

    }
}

@dimdin a follow up question if I could; I was under the impression that Unions are a way to organize things into categories and be able to define generic props/methods on the Union that then apply to all instances of it, no matter the “sub-type”. I wasn’t expecting to be able to define other_addr as a net.Address and then be able do things with both other_addr.any and other_addr.in - I was thinking of it as “it’s one or the other, not both types”. Is this a common pattern to watch out for when using Unions?

1 Like

Address uses extern union that is compatible with C union. The trick here is that any can hold all the addresses (ipv4, ipv6 and unix domain) and the various other types (in, in6, and un) have the same memory layout with all. The Address delegates all the calls to the other types depending on the address family. (e.g. format, getOsSockLen)