Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std: Add basic smoke test for net functionality #6838

Merged
merged 4 commits into from
Oct 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/std/c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ pub extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint
pub extern "c" fn listen(sockfd: fd_t, backlog: c_uint) c_int;
pub extern "c" fn getsockname(sockfd: fd_t, noalias addr: *sockaddr, noalias addrlen: *socklen_t) c_int;
pub extern "c" fn connect(sockfd: fd_t, sock_addr: *const sockaddr, addrlen: socklen_t) c_int;
pub extern "c" fn accept(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t) c_int;
pub extern "c" fn accept4(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t, flags: c_uint) c_int;
pub extern "c" fn accept(sockfd: fd_t, addr: ?*sockaddr, addrlen: ?*socklen_t) c_int;
pub extern "c" fn accept4(sockfd: fd_t, addr: ?*sockaddr, addrlen: ?*socklen_t, flags: c_uint) c_int;
pub extern "c" fn getsockopt(sockfd: fd_t, level: u32, optname: u32, optval: ?*c_void, optlen: *socklen_t) c_int;
pub extern "c" fn setsockopt(sockfd: fd_t, level: u32, optname: u32, optval: ?*const c_void, optlen: socklen_t) c_int;
pub extern "c" fn send(sockfd: fd_t, buf: *const c_void, len: usize, flags: u32) isize;
Expand Down
42 changes: 24 additions & 18 deletions lib/std/net.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ const mem = std.mem;
const os = std.os;
const fs = std.fs;

test "" {
_ = @import("net/test.zig");
}

const has_unix_sockets = @hasDecl(os, "sockaddr_un");
pub const has_unix_sockets = @hasDecl(os, "sockaddr_un");

pub const Address = extern union {
any: os.sockaddr,
Expand Down Expand Up @@ -610,7 +606,7 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,
0,
);
errdefer os.close(sockfd);
errdefer os.closeSocket(sockfd);

var addr = try std.net.Address.initUnix(path);

Expand All @@ -629,7 +625,7 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
fn if_nametoindex(name: []const u8) !u32 {
var ifr: os.ifreq = undefined;
var sockfd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM | os.SOCK_CLOEXEC, 0);
defer os.close(sockfd);
defer os.closeSocket(sockfd);

std.mem.copy(u8, &ifr.ifrn.name, name);
ifr.ifrn.name[name.len] = 0;
Expand Down Expand Up @@ -677,7 +673,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File {
const sock_flags = os.SOCK_STREAM | nonblock |
(if (builtin.os.tag == .windows) 0 else os.SOCK_CLOEXEC);
const sockfd = try os.socket(address.any.family, sock_flags, os.IPPROTO_TCP);
errdefer os.close(sockfd);
errdefer os.closeSocket(sockfd);

if (std.io.is_async) {
const loop = std.event.Loop.instance orelse return error.WouldBlock;
Expand Down Expand Up @@ -912,7 +908,7 @@ fn linuxLookupName(
var prefixlen: i32 = 0;
const sock_flags = os.SOCK_DGRAM | os.SOCK_CLOEXEC;
if (os.socket(addr.addr.any.family, sock_flags, os.IPPROTO_UDP)) |fd| syscalls: {
defer os.close(fd);
defer os.closeSocket(fd);
os.connect(fd, da, dalen) catch break :syscalls;
key |= DAS_USABLE;
os.getsockname(fd, sa, &salen) catch break :syscalls;
Expand Down Expand Up @@ -1392,7 +1388,7 @@ fn resMSendRc(
},
else => |e| return e,
};
defer os.close(fd);
defer os.closeSocket(fd);
try os.bind(fd, &sa.any, sl);

// Past this point, there are no errors. Each individual query will
Expand Down Expand Up @@ -1546,16 +1542,14 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
if (data.len != 4) return error.InvalidDnsARecord;
const new_addr = try ctx.addrs.addOne();
new_addr.* = LookupAddr{
// TODO slice [0..4] to make this *[4]u8 without @ptrCast
.addr = Address.initIp4(@ptrCast(*const [4]u8, data.ptr).*, ctx.port),
.addr = Address.initIp4(data[0..4].*, ctx.port),
};
},
os.RR_AAAA => {
if (data.len != 16) return error.InvalidDnsAAAARecord;
const new_addr = try ctx.addrs.addOne();
new_addr.* = LookupAddr{
// TODO slice [0..16] to make this *[16]u8 without @ptrCast
.addr = Address.initIp6(@ptrCast(*const [16]u8, data.ptr).*, ctx.port, 0, 0),
.addr = Address.initIp6(data[0..16].*, ctx.port, 0, 0),
};
},
os.RR_CNAME => {
Expand All @@ -1579,7 +1573,7 @@ pub const StreamServer = struct {
/// `undefined` until `listen` returns successfully.
listen_address: Address,

sockfd: ?os.fd_t,
sockfd: ?os.socket_t,

pub const Options = struct {
/// How many connections the kernel will accept on the application's behalf.
Expand Down Expand Up @@ -1616,13 +1610,13 @@ pub const StreamServer = struct {
const sockfd = try os.socket(address.any.family, sock_flags, proto);
self.sockfd = sockfd;
errdefer {
os.close(sockfd);
os.closeSocket(sockfd);
self.sockfd = null;
}

if (self.reuse_address) {
try os.setsockopt(
self.sockfd.?,
sockfd,
os.SOL_SOCKET,
os.SO_REUSEADDR,
&mem.toBytes(@as(c_int, 1)),
Expand All @@ -1640,7 +1634,7 @@ pub const StreamServer = struct {
/// not listening.
pub fn close(self: *StreamServer) void {
if (self.sockfd) |fd| {
os.close(fd);
os.closeSocket(fd);
self.sockfd = null;
self.listen_address = undefined;
}
Expand Down Expand Up @@ -1670,6 +1664,14 @@ pub const StreamServer = struct {
/// Permission to create a socket of the specified type and/or
/// protocol is denied.
PermissionDenied,

FileDescriptorNotASocket,

ConnectionResetByPeer,

NetworkSubsystemFailed,

OperationNotSupported,
} || os.UnexpectedError;

pub const Connection = struct {
Expand Down Expand Up @@ -1701,3 +1703,7 @@ pub const StreamServer = struct {
}
}
};

test "" {
_ = @import("net/test.zig");
}
75 changes: 67 additions & 8 deletions lib/std/net/test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,81 @@ test "parse and render IPv4 addresses" {
}

test "resolve DNS" {
if (builtin.os.tag == .wasi) return error.SkipZigTest;

if (std.builtin.os.tag == .windows) {
_ = try std.os.windows.WSAStartup(2, 2);
}
if (builtin.os.tag == .wasi) {
// DNS resolution not implemented on Windows yet.
return error.SkipZigTest;
defer {
if (std.builtin.os.tag == .windows) {
std.os.windows.WSACleanup() catch unreachable;
}
}

const address_list = net.getAddressList(testing.allocator, "example.com", 80) catch |err| switch (err) {
// Resolve localhost, this should not fail.
{
const localhost_v4 = try net.Address.parseIp("127.0.0.1", 80);
const localhost_v6 = try net.Address.parseIp("::2", 80);

const result = try net.getAddressList(testing.allocator, "localhost", 80);
defer result.deinit();
for (result.addrs) |addr| {
if (addr.eql(localhost_v4) or addr.eql(localhost_v6)) break;
} else @panic("unexpected address for localhost");
}

{
// The tests are required to work even when there is no Internet connection,
// so some of these errors we must accept and skip the test.
error.UnknownHostName => return error.SkipZigTest,
error.TemporaryNameServerFailure => return error.SkipZigTest,
else => return err,
const result = net.getAddressList(testing.allocator, "example.com", 80) catch |err| switch (err) {
error.UnknownHostName => return error.SkipZigTest,
error.TemporaryNameServerFailure => return error.SkipZigTest,
else => return err,
};
result.deinit();
}
}

test "listen on a port, send bytes, receive bytes" {
if (builtin.single_threaded) return error.SkipZigTest;
if (builtin.os.tag == .wasi) return error.SkipZigTest;

if (std.builtin.os.tag == .windows) {
_ = try std.os.windows.WSAStartup(2, 2);
}
defer {
if (std.builtin.os.tag == .windows) {
std.os.windows.WSACleanup() catch unreachable;
}
}

// Try only the IPv4 variant as some CI builders have no IPv6 localhost
// configured.
const localhost = try net.Address.parseIp("127.0.0.1", 8080);

var server = net.StreamServer.init(.{});
defer server.deinit();

try server.listen(localhost);

const S = struct {
fn clientFn(server_address: net.Address) !void {
const socket = try net.tcpConnectToAddress(server_address);
defer socket.close();

_ = try socket.writer().writeAll("Hello world!");
}
};
address_list.deinit();

const t = try std.Thread.spawn(server.listen_address, S.clientFn);
defer t.wait();

var client = try server.accept();
var buf: [16]u8 = undefined;
const n = try client.file.reader().read(&buf);

testing.expectEqual(@as(usize, 12), n);
testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
}

test "listen on a port, send bytes, receive bytes" {
Expand Down
64 changes: 48 additions & 16 deletions lib/std/os.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2687,6 +2687,14 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t
}
}

pub fn closeSocket(sock: socket_t) void {
if (builtin.os.tag == .windows) {
windows.closesocket(sock) catch unreachable;
} else {
close(sock);
}
}

pub const BindError = error{
/// The address is protected, and the user is not the superuser.
/// For UNIX domain sockets: Search permission is denied on a component
Expand Down Expand Up @@ -2731,8 +2739,8 @@ pub const BindError = error{

/// addr is `*const T` where T is one of the sockaddr
pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!void {
const rc = system.bind(sock, addr, len);
if (builtin.os.tag == .windows) {
const rc = windows.bind(sock, addr, len);
if (rc == windows.ws2_32.SOCKET_ERROR) {
switch (windows.ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable, // not initialized WSA
Expand All @@ -2750,6 +2758,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi
}
return;
} else {
const rc = system.bind(sock, addr, len);
switch (errno(rc)) {
0 => return,
EACCES => return error.AccessDenied,
Expand Down Expand Up @@ -2800,8 +2809,8 @@ const ListenError = error{
} || UnexpectedError;

pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
const rc = system.listen(sock, backlog);
if (builtin.os.tag == .windows) {
const rc = windows.listen(sock, backlog);
if (rc == windows.ws2_32.SOCKET_ERROR) {
switch (windows.ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable, // not initialized WSA
Expand All @@ -2818,6 +2827,7 @@ pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
}
return;
} else {
const rc = system.listen(sock, backlog);
switch (errno(rc)) {
0 => return,
EADDRINUSE => return error.AddressInUse,
Expand Down Expand Up @@ -2905,6 +2915,8 @@ pub fn accept(
const accepted_sock = while (true) {
const rc = if (have_accept4)
system.accept4(sock, addr, addr_size, flags)
else if (builtin.os.tag == .windows)
windows.accept(sock, addr, addr_size)
else
system.accept(sock, addr, addr_size);

Expand Down Expand Up @@ -3077,8 +3089,8 @@ pub const GetSockNameError = error{
} || UnexpectedError;

pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void {
const rc = system.getsockname(sock, addr, addrlen);
if (builtin.os.tag == .windows) {
const rc = windows.getsockname(sock, addr, addrlen);
if (rc == windows.ws2_32.SOCKET_ERROR) {
switch (windows.ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable,
Expand All @@ -3091,6 +3103,7 @@ pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock
}
return;
} else {
const rc = system.getsockname(sock, addr, addrlen);
switch (errno(rc)) {
0 => return,
else => |err| return unexpectedErrno(err),
Expand Down Expand Up @@ -5378,22 +5391,41 @@ pub const SetSockOptError = error{

/// Insufficient resources are available in the system to complete the call.
SystemResources,

NetworkSubsystemFailed,
FileDescriptorNotASocket,
SocketNotBound,
} || UnexpectedError;

/// Set a socket's options.
pub fn setsockopt(fd: fd_t, level: u32, optname: u32, opt: []const u8) SetSockOptError!void {
switch (errno(system.setsockopt(fd, level, optname, opt.ptr, @intCast(socklen_t, opt.len)))) {
0 => {},
EBADF => unreachable, // always a race condition
ENOTSOCK => unreachable, // always a race condition
EINVAL => unreachable,
EFAULT => unreachable,
EDOM => return error.TimeoutTooBig,
EISCONN => return error.AlreadyConnected,
ENOPROTOOPT => return error.InvalidProtocolOption,
ENOMEM => return error.SystemResources,
ENOBUFS => return error.SystemResources,
else => |err| return unexpectedErrno(err),
pub fn setsockopt(fd: socket_t, level: u32, optname: u32, opt: []const u8) SetSockOptError!void {
if (builtin.os.tag == .windows) {
const rc = windows.ws2_32.setsockopt(fd, level, optname, opt.ptr, @intCast(socklen_t, opt.len));
if (rc == windows.ws2_32.SOCKET_ERROR) {
switch (windows.ws2_32.WSAGetLastError()) {
.WSANOTINITIALISED => unreachable,
.WSAENETDOWN => return error.NetworkSubsystemFailed,
.WSAEFAULT => unreachable,
.WSAENOTSOCK => return error.FileDescriptorNotASocket,
.WSAEINVAL => return error.SocketNotBound,
else => |err| return windows.unexpectedWSAError(err),
}
}
return;
} else {
switch (errno(system.setsockopt(fd, level, optname, opt.ptr, @intCast(socklen_t, opt.len)))) {
0 => {},
EBADF => unreachable, // always a race condition
ENOTSOCK => unreachable, // always a race condition
EINVAL => unreachable,
EFAULT => unreachable,
EDOM => return error.TimeoutTooBig,
EISCONN => return error.AlreadyConnected,
ENOPROTOOPT => return error.InvalidProtocolOption,
ENOMEM => return error.SystemResources,
ENOBUFS => return error.SystemResources,
else => |err| return unexpectedErrno(err),
}
}
}

Expand Down
Loading