mirror of
https://github.com/ziglang/zig.git
synced 2025-12-08 23:33:07 +00:00
commit
14d235dd6e
@ -132,6 +132,7 @@ pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;
|
||||
pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;
|
||||
pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int;
|
||||
pub extern "c" fn flock(fd: fd_t, operation: c_int) c_int;
|
||||
pub extern "c" fn ioctl(fd: fd_t, request: c_int, ...) c_int;
|
||||
pub extern "c" fn uname(buf: *utsname) c_int;
|
||||
|
||||
pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;
|
||||
|
||||
174
lib/std/net.zig
174
lib/std/net.zig
@ -21,6 +21,9 @@ pub const Address = extern union {
|
||||
// TODO this crashed the compiler. https://github.com/ziglang/zig/issues/3512
|
||||
//pub const localhost = initIp4(parseIp4("127.0.0.1") catch unreachable, 0);
|
||||
|
||||
/// Parse the given IP address string into an Address value.
|
||||
/// It is recommended to use `resolveIp` instead, to handle
|
||||
/// IPv6 link-local unix addresses.
|
||||
pub fn parseIp(name: []const u8, port: u16) !Address {
|
||||
if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
|
||||
error.Overflow,
|
||||
@ -42,6 +45,28 @@ pub const Address = extern union {
|
||||
return error.InvalidIPAddressFormat;
|
||||
}
|
||||
|
||||
pub fn resolveIp(name: []const u8, port: u16) !Address {
|
||||
if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
|
||||
error.Overflow,
|
||||
error.InvalidEnd,
|
||||
error.InvalidCharacter,
|
||||
error.Incomplete,
|
||||
=> {},
|
||||
}
|
||||
|
||||
if (resolveIp6(name, port)) |ip6| return ip6 else |err| switch (err) {
|
||||
error.Overflow,
|
||||
error.InvalidEnd,
|
||||
error.InvalidCharacter,
|
||||
error.Incomplete,
|
||||
error.InvalidIpv4Mapping,
|
||||
=> {},
|
||||
else => return err,
|
||||
}
|
||||
|
||||
return error.InvalidIPAddressFormat;
|
||||
}
|
||||
|
||||
pub fn parseExpectingFamily(name: []const u8, family: os.sa_family_t, port: u16) !Address {
|
||||
switch (family) {
|
||||
os.AF_INET => return parseIp4(name, port),
|
||||
@ -51,6 +76,9 @@ pub const Address = extern union {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a given IPv6 address string into an Address.
|
||||
/// Assumes the Scope ID of the address is fully numeric.
|
||||
/// For non-numeric addresses, see `resolveIp6`.
|
||||
pub fn parseIp6(buf: []const u8, port: u16) !Address {
|
||||
var result = Address{
|
||||
.in6 = os.sockaddr_in6{
|
||||
@ -157,6 +185,136 @@ pub const Address = extern union {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolveIp6(buf: []const u8, port: u16) !Address {
|
||||
// TODO: Unify the implementations of resolveIp6 and parseIp6.
|
||||
var result = Address{
|
||||
.in6 = os.sockaddr_in6{
|
||||
.scope_id = 0,
|
||||
.port = mem.nativeToBig(u16, port),
|
||||
.flowinfo = 0,
|
||||
.addr = undefined,
|
||||
},
|
||||
};
|
||||
var ip_slice = result.in6.addr[0..];
|
||||
|
||||
var tail: [16]u8 = undefined;
|
||||
|
||||
var x: u16 = 0;
|
||||
var saw_any_digits = false;
|
||||
var index: u8 = 0;
|
||||
var abbrv = false;
|
||||
|
||||
var scope_id = false;
|
||||
var scope_id_value: [os.IFNAMESIZE - 1]u8 = undefined;
|
||||
var scope_id_index: usize = 0;
|
||||
|
||||
for (buf) |c, i| {
|
||||
if (scope_id) {
|
||||
// Handling of percent-encoding should be for an URI library.
|
||||
if ((c >= '0' and c <= '9') or
|
||||
(c >= 'A' and c <= 'Z') or
|
||||
(c >= 'a' and c <= 'z') or
|
||||
(c == '-') or (c == '.') or (c == '_') or (c == '~'))
|
||||
{
|
||||
if (scope_id_index >= scope_id_value.len) {
|
||||
return error.Overflow;
|
||||
}
|
||||
|
||||
scope_id_value[scope_id_index] = c;
|
||||
scope_id_index += 1;
|
||||
} else {
|
||||
return error.InvalidCharacter;
|
||||
}
|
||||
} else if (c == ':') {
|
||||
if (!saw_any_digits) {
|
||||
if (abbrv) return error.InvalidCharacter; // ':::'
|
||||
if (i != 0) abbrv = true;
|
||||
mem.set(u8, ip_slice[index..], 0);
|
||||
ip_slice = tail[0..];
|
||||
index = 0;
|
||||
continue;
|
||||
}
|
||||
if (index == 14) {
|
||||
return error.InvalidEnd;
|
||||
}
|
||||
ip_slice[index] = @truncate(u8, x >> 8);
|
||||
index += 1;
|
||||
ip_slice[index] = @truncate(u8, x);
|
||||
index += 1;
|
||||
|
||||
x = 0;
|
||||
saw_any_digits = false;
|
||||
} else if (c == '%') {
|
||||
if (!saw_any_digits) {
|
||||
return error.InvalidCharacter;
|
||||
}
|
||||
scope_id = true;
|
||||
saw_any_digits = false;
|
||||
} else if (c == '.') {
|
||||
if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
|
||||
// must start with '::ffff:'
|
||||
return error.InvalidIpv4Mapping;
|
||||
}
|
||||
const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
|
||||
const addr = (parseIp4(buf[start_index..], 0) catch {
|
||||
return error.InvalidIpv4Mapping;
|
||||
}).in.addr;
|
||||
ip_slice = result.in6.addr[0..];
|
||||
ip_slice[10] = 0xff;
|
||||
ip_slice[11] = 0xff;
|
||||
|
||||
const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]);
|
||||
|
||||
ip_slice[12] = ptr[0];
|
||||
ip_slice[13] = ptr[1];
|
||||
ip_slice[14] = ptr[2];
|
||||
ip_slice[15] = ptr[3];
|
||||
return result;
|
||||
} else {
|
||||
const digit = try std.fmt.charToDigit(c, 16);
|
||||
if (@mulWithOverflow(u16, x, 16, &x)) {
|
||||
return error.Overflow;
|
||||
}
|
||||
if (@addWithOverflow(u16, x, digit, &x)) {
|
||||
return error.Overflow;
|
||||
}
|
||||
saw_any_digits = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!saw_any_digits and !abbrv) {
|
||||
return error.Incomplete;
|
||||
}
|
||||
|
||||
if (scope_id and scope_id_index == 0) {
|
||||
return error.Incomplete;
|
||||
}
|
||||
|
||||
var resolved_scope_id: u32 = 0;
|
||||
if (scope_id_index > 0) {
|
||||
const scope_id_str = scope_id_value[0..scope_id_index];
|
||||
resolved_scope_id = std.fmt.parseInt(u32, scope_id_str, 10) catch |err| blk: {
|
||||
if (err != error.InvalidCharacter) return err;
|
||||
break :blk try if_nametoindex(scope_id_str);
|
||||
};
|
||||
}
|
||||
|
||||
result.in6.scope_id = resolved_scope_id;
|
||||
|
||||
if (index == 14) {
|
||||
ip_slice[14] = @truncate(u8, x >> 8);
|
||||
ip_slice[15] = @truncate(u8, x);
|
||||
return result;
|
||||
} else {
|
||||
ip_slice[index] = @truncate(u8, x >> 8);
|
||||
index += 1;
|
||||
ip_slice[index] = @truncate(u8, x);
|
||||
index += 1;
|
||||
mem.copy(u8, result.in6.addr[16 - index ..], ip_slice[0..index]);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parseIp4(buf: []const u8, port: u16) !Address {
|
||||
var result = Address{
|
||||
.in = os.sockaddr_in{
|
||||
@ -380,6 +538,20 @@ 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);
|
||||
|
||||
std.mem.copy(u8, &ifr.ifrn.name, name);
|
||||
ifr.ifrn.name[name.len] = 0;
|
||||
|
||||
// TODO investigate if this needs to be integrated with evented I/O.
|
||||
try os.ioctl_SIOCGIFINDEX(sockfd, &ifr);
|
||||
|
||||
return @bitCast(u32, ifr.ifru.ivalue);
|
||||
}
|
||||
|
||||
pub const AddressList = struct {
|
||||
arena: std.heap.ArenaAllocator,
|
||||
addrs: []Address,
|
||||
@ -1071,7 +1243,7 @@ fn linuxLookupNameFromNumericUnspec(
|
||||
name: []const u8,
|
||||
port: u16,
|
||||
) !void {
|
||||
const addr = try Address.parseIp(name, port);
|
||||
const addr = try Address.resolveIp(name, port);
|
||||
(try addrs.addOne()).* = LookupAddr{ .addr = addr };
|
||||
}
|
||||
|
||||
|
||||
@ -34,6 +34,12 @@ test "parse and render IPv6 addresses" {
|
||||
var addr = net.Address.parseIp6(ip, 0) catch unreachable;
|
||||
var newIp = std.fmt.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable;
|
||||
std.testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3]));
|
||||
|
||||
if (std.builtin.os.tag == .linux) {
|
||||
var addr_via_resolve = net.Address.resolveIp6(ip, 0) catch unreachable;
|
||||
var newResolvedIp = std.fmt.bufPrint(buffer[0..], "{}", .{addr_via_resolve}) catch unreachable;
|
||||
std.testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3]));
|
||||
}
|
||||
}
|
||||
|
||||
testing.expectError(error.InvalidCharacter, net.Address.parseIp6(":::", 0));
|
||||
@ -42,6 +48,22 @@ test "parse and render IPv6 addresses" {
|
||||
testing.expectError(error.InvalidEnd, net.Address.parseIp6("FF01:0:0:0:0:0:0:FB:", 0));
|
||||
testing.expectError(error.Incomplete, net.Address.parseIp6("FF01:", 0));
|
||||
testing.expectError(error.InvalidIpv4Mapping, net.Address.parseIp6("::123.123.123.123", 0));
|
||||
// TODO Make this test pass on other operating systems.
|
||||
if (std.builtin.os.tag == .linux) {
|
||||
testing.expectError(error.Incomplete, net.Address.resolveIp6("ff01::fb%", 0));
|
||||
testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%wlp3s0s0s0s0s0s0s0s0", 0));
|
||||
testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%12345678901234", 0));
|
||||
}
|
||||
}
|
||||
|
||||
test "invalid but parseable IPv6 scope ids" {
|
||||
if (std.builtin.os.tag != .linux) {
|
||||
// Currently, resolveIp6 with alphanumerical scope IDs only works on Linux.
|
||||
// TODO Make this test pass on other operating systems.
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
|
||||
testing.expectError(error.InterfaceNotFound, net.Address.resolveIp6("ff01::fb%123s45678901234", 0));
|
||||
}
|
||||
|
||||
test "parse and render IPv4 addresses" {
|
||||
|
||||
@ -405,7 +405,6 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
|
||||
while (true) {
|
||||
// TODO handle the case when iov_len is too large and get rid of this @intCast
|
||||
@ -2391,8 +2390,15 @@ pub fn isatty(handle: fd_t) bool {
|
||||
return true;
|
||||
}
|
||||
if (builtin.os.tag == .linux) {
|
||||
var wsz: linux.winsize = undefined;
|
||||
return linux.syscall3(.ioctl, @bitCast(usize, @as(isize, handle)), linux.TIOCGWINSZ, @ptrToInt(&wsz)) == 0;
|
||||
while (true) {
|
||||
var wsz: linux.winsize = undefined;
|
||||
const fd = @bitCast(usize, @as(isize, handle));
|
||||
switch (linux.syscall3(.ioctl, fd, linux.TIOCGWINSZ, @ptrToInt(&wsz))) {
|
||||
0 => return true,
|
||||
EINTR => continue,
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
@ -2451,9 +2457,8 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t
|
||||
if (builtin.os.tag == .windows) {
|
||||
// NOTE: windows translates the SOCK_NONBLOCK/SOCK_CLOEXEC flags into windows-analagous operations
|
||||
const filtered_sock_type = socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC);
|
||||
const flags : u32 = if ((socket_type & SOCK_CLOEXEC) != 0) windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT else 0;
|
||||
const rc = windows.ws2_32.WSASocketW(@intCast(c_int, domain), @intCast(c_int, filtered_sock_type),
|
||||
@intCast(c_int, protocol), null, 0, flags);
|
||||
const flags: u32 = if ((socket_type & SOCK_CLOEXEC) != 0) windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT else 0;
|
||||
const rc = windows.ws2_32.WSASocketW(@intCast(c_int, domain), @intCast(c_int, filtered_sock_type), @intCast(c_int, protocol), null, 0, flags);
|
||||
if (rc == windows.ws2_32.INVALID_SOCKET) switch (windows.ws2_32.WSAGetLastError()) {
|
||||
.WSAEMFILE => return error.ProcessFdQuotaExceeded,
|
||||
.WSAENOBUFS => return error.SystemResources,
|
||||
@ -2463,7 +2468,7 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t
|
||||
};
|
||||
errdefer windows.closesocket(rc) catch unreachable;
|
||||
if ((socket_type & SOCK_NONBLOCK) != 0) {
|
||||
var mode : c_ulong = 1; // nonblocking
|
||||
var mode: c_ulong = 1; // nonblocking
|
||||
if (windows.ws2_32.SOCKET_ERROR == windows.ws2_32.ioctlsocket(rc, windows.ws2_32.FIONBIO, &mode)) {
|
||||
switch (windows.ws2_32.WSAGetLastError()) {
|
||||
// have not identified any error codes that should be handled yet
|
||||
@ -2858,7 +2863,7 @@ pub fn connect(sockfd: socket_t, sock_addr: *const sockaddr, len: socklen_t) Con
|
||||
.WSAECONNREFUSED => return error.ConnectionRefused,
|
||||
.WSAETIMEDOUT => return error.ConnectionTimedOut,
|
||||
.WSAEHOSTUNREACH // TODO: should we return NetworkUnreachable in this case as well?
|
||||
,.WSAENETUNREACH => return error.NetworkUnreachable,
|
||||
, .WSAENETUNREACH => return error.NetworkUnreachable,
|
||||
.WSAEFAULT => unreachable,
|
||||
.WSAEINVAL => unreachable,
|
||||
.WSAEISCONN => unreachable,
|
||||
@ -4882,12 +4887,15 @@ pub fn getrusage(who: i32) rusage {
|
||||
pub const TermiosGetError = error{NotATerminal} || UnexpectedError;
|
||||
|
||||
pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
|
||||
var term: termios = undefined;
|
||||
switch (errno(system.tcgetattr(handle, &term))) {
|
||||
0 => return term,
|
||||
EBADF => unreachable,
|
||||
ENOTTY => return error.NotATerminal,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
while (true) {
|
||||
var term: termios = undefined;
|
||||
switch (errno(system.tcgetattr(handle, &term))) {
|
||||
0 => return term,
|
||||
EINTR => continue,
|
||||
EBADF => unreachable,
|
||||
ENOTTY => return error.NotATerminal,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4906,3 +4914,25 @@ pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) Termio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const IoCtl_SIOCGIFINDEX_Error = error{
|
||||
FileSystem,
|
||||
InterfaceNotFound,
|
||||
} || UnexpectedError;
|
||||
|
||||
pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
|
||||
while (true) {
|
||||
switch (errno(system.ioctl(fd, SIOCGIFINDEX, @ptrToInt(ifr)))) {
|
||||
0 => return,
|
||||
EINVAL => unreachable, // Bad parameters.
|
||||
ENOTTY => unreachable,
|
||||
ENXIO => unreachable,
|
||||
EBADF => unreachable, // Always a race condition.
|
||||
EFAULT => unreachable, // Bad pointer parameter.
|
||||
EINTR => continue,
|
||||
EIO => return error.FileSystem,
|
||||
ENODEV => return error.InterfaceNotFound,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1705,3 +1705,35 @@ pub const termios = extern struct {
|
||||
ispeed: speed_t,
|
||||
ospeed: speed_t,
|
||||
};
|
||||
|
||||
pub const SIOCGIFINDEX = 0x8933;
|
||||
pub const IFNAMESIZE = 16;
|
||||
|
||||
pub const ifmap = extern struct {
|
||||
mem_start: u32,
|
||||
mem_end: u32,
|
||||
base_addr: u16,
|
||||
irq: u8,
|
||||
dma: u8,
|
||||
port: u8,
|
||||
};
|
||||
|
||||
pub const ifreq = extern struct {
|
||||
ifrn: extern union {
|
||||
name: [IFNAMESIZE]u8,
|
||||
},
|
||||
ifru: extern union {
|
||||
addr: sockaddr,
|
||||
dstaddr: sockaddr,
|
||||
broadaddr: sockaddr,
|
||||
netmask: sockaddr,
|
||||
hwaddr: sockaddr,
|
||||
flags: i16,
|
||||
ivalue: i32,
|
||||
mtu: i32,
|
||||
map: ifmap,
|
||||
slave: [IFNAMESIZE - 1:0]u8,
|
||||
newname: [IFNAMESIZE - 1:0]u8,
|
||||
data: ?[*]u8,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1193,6 +1193,10 @@ pub fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) usi
|
||||
return syscall3(.ioctl, @bitCast(usize, @as(isize, fd)), TCSETS + @enumToInt(optional_action), @ptrToInt(termios_p));
|
||||
}
|
||||
|
||||
pub fn ioctl(fd: fd_t, request: u32, arg: usize) usize {
|
||||
return syscall3(.ioctl, @bitCast(usize, @as(isize, fd)), request, arg);
|
||||
}
|
||||
|
||||
test "" {
|
||||
if (builtin.os.tag == .linux) {
|
||||
_ = @import("linux/test.zig");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user