diff --git a/examples/net_raw_http.v b/examples/net_raw_http.v new file mode 100644 index 0000000000..e3b4f20a2d --- /dev/null +++ b/examples/net_raw_http.v @@ -0,0 +1,14 @@ +// Simple raw HTTP head request +import x.net as net +import time + +// Make a new connection +mut conn := net.dial_tcp('google.com:80')? +// Simple http HEAD request for a file +conn.write_string('HEAD /index.html HTTP/1.0\r\n\r\n')? +// Make sure to set a timeout so we can wait for a response! +conn.set_read_timeout(10 * time.second) +// Read all the data that is waiting +result := conn.read()? +// Cast to string and print result +println(result.bytestr()) \ No newline at end of file diff --git a/vlib/x/net/aasocket.c.v b/vlib/x/net/aasocket.c.v new file mode 100644 index 0000000000..c3aea7eff8 --- /dev/null +++ b/vlib/x/net/aasocket.c.v @@ -0,0 +1,95 @@ +module net + +// Select represents a select operation +enum Select { + read write except +} + +// SocketType are the available sockets +pub enum SocketType { + udp = C.SOCK_DGRAM + tcp = C.SOCK_STREAM +} + +// SocketFamily are the available address families +pub enum SocketFamily { + inet = C. AF_INET +} + +struct C.in_addr { +mut: + s_addr int +} + +struct C.sockaddr { +} + +struct C.sockaddr_in { +mut: + sin_family int + sin_port int + sin_addr C.in_addr +} + + +struct C.addrinfo { +mut: + ai_family int + ai_socktype int + ai_flags int + ai_protocol int + ai_addrlen int + ai_addr voidptr + ai_canonname voidptr + ai_next voidptr +} + +struct C.sockaddr_storage { +} + +fn C.socket() int + +fn C.setsockopt() int + +fn C.htonl() int + +fn C.htons() int + +fn C.bind() int + +fn C.listen() int + +fn C.accept() int + +fn C.getaddrinfo() int + +fn C.connect() int + +fn C.send() int +fn C.sendto() int + +fn C.recv() int +fn C.recvfrom() int + +fn C.shutdown() int + +fn C.ntohs() int + +fn C.inet_ntop() int + +fn C.getsockname() int + +// defined in builtin +// fn C.read() int +// fn C.close() int + +fn C.ioctlsocket() int +fn C.fcntl() int + +fn C.@select() int +fn C.FD_ZERO() +fn C.FD_SET() +fn C.FD_ISSET() bool + +[typedef] +struct C.fd_set {} diff --git a/vlib/x/net/address.v b/vlib/x/net/address.v new file mode 100644 index 0000000000..cf46f26c09 --- /dev/null +++ b/vlib/x/net/address.v @@ -0,0 +1,74 @@ +module net + +// Addr represents an ip address +pub struct Addr { + addr C.sockaddr + len int +pub: + saddr string + port int +} + +pub fn (a Addr) str() string { + return '${a.saddr}:${a.port}' +} + +const ( + max_ipv4_addr_len = 16 +) + +fn new_addr(addr C.sockaddr, _saddr string, _port int) ?Addr { + mut saddr := _saddr + if saddr == '' { + // Convert to string representation + buf := []byte{ len: max_ipv4_addr_len, init: 0 } + $if windows { + res := C.inet_ntop(SocketFamily.inet, &addr, buf.data, buf.len) + if res == 0 { + socket_error(-1)? + } + } $else { + res := C.inet_ntop(SocketFamily.inet, &addr, buf.data, buf.len) + if res == 0 { + socket_error(-1)? + } + } + saddr = buf.bytestr() + } + + mut port := _port + if port == 0 { + hport := (&C.sockaddr_in(&addr)).sin_port + port = C.ntohs(hport) + } + + return Addr { + addr int(sizeof(C.sockaddr)) saddr port + } +} + +pub fn resolve_addr(addr string, family SocketFamily, typ SocketType) ?Addr { + address, port := split_address(addr)? + + mut hints := C.addrinfo{} + hints.ai_family = family + hints.ai_socktype = typ + hints.ai_flags = C.AI_PASSIVE + hints.ai_protocol = 0 + hints.ai_addrlen = 0 + hints.ai_canonname = C.NULL + hints.ai_addr = C.NULL + hints.ai_next = C.NULL + info := &C.addrinfo(0) + + sport := '$port' + + // This might look silly but is reccomended by MSDN + $if windows { + socket_error(0-C.getaddrinfo(address.str, sport.str, &hints, &info))? + } $else { + wrap_error(C.getaddrinfo(address.str, sport.str, &hints, &info)) + } + + return new_addr(*info.ai_addr, address, port) +} \ No newline at end of file diff --git a/vlib/x/net/common.v b/vlib/x/net/common.v new file mode 100644 index 0000000000..0b86a7cce4 --- /dev/null +++ b/vlib/x/net/common.v @@ -0,0 +1,122 @@ +module net + +import time + +// Shutdown shutsdown a socket and closes it +fn shutdown(handle int) ? { + $if windows { + C.shutdown(handle, C.SD_BOTH) + socket_error(C.closesocket(handle))? + } $else { + C.shutdown(handle, C.SHUT_RDWR) + socket_error(C.close(handle))? + } + + return none +} + +// Select waits for an io operation (specified by parameter `test`) to be available +fn @select(handle int, test Select, timeout time.Duration) ?bool { + set := C.fd_set{} + + C.FD_ZERO(&set) + C.FD_SET(handle, &set) + + seconds := timeout.milliseconds() / 1000 + microseconds := timeout - (seconds * time.second) + + mut tt := C.timeval{ + tv_sec: u64(seconds) + tv_usec: u64(microseconds) + } + + mut timeval_timeout := &tt + + // infinite timeout is signaled by passing null as the timeout to + // select + if timeout == infinite_timeout { + timeval_timeout = &C.timeval(0) + } + + match test { + .read { + socket_error(C.@select(handle+1, &set, C.NULL, C.NULL, timeval_timeout))? + } + .write { + socket_error(C.@select(handle+1, C.NULL, &set, C.NULL, timeval_timeout))? + } + .except { + socket_error(C.@select(handle+1, C.NULL, C.NULL, &set, timeval_timeout))? + } + } + + return C.FD_ISSET(handle, &set) +} + +// wait_for_common wraps the common wait code +fn wait_for_common( + handle int, + deadline time.Time, + timeout time.Duration, + test Select) ? { + if deadline.unix == 0 { + // only accept infinite_timeout as a valid + // negative timeout - it is handled in @select however + if timeout < 0 && timeout != infinite_timeout { + return err_timed_out + } + ready := @select(handle, test, timeout)? + if ready { + return none + } + return err_timed_out + } + // Convert the deadline into a timeout + // and use that + d_timeout := deadline.unix - time.now().unix + if d_timeout < 0 { + // deadline is in the past so this has already + // timed out + return err_timed_out + } + + ready := @select(handle, test, d_timeout)? + if ready { + return none + } + return err_timed_out +} + +// wait_for_write waits for a write io operation to be available +fn wait_for_write( + handle int, + deadline time.Time, + timeout time.Duration) ? { + return wait_for_common(handle, deadline, timeout, .write) +} + +// wait_for_read waits for a read io operation to be available +fn wait_for_read( + handle int, + deadline time.Time, + timeout time.Duration) ? { + return wait_for_common(handle, deadline, timeout, .read) +} + +// no_deadline should be given to functions when no deadline is wanted (i.e. all functions +// return instantly) +const ( + no_deadline = time.Time{unix: 0} +) + +// no_timeout should be given to functions when no timeout is wanted (i.e. all functions +// return instantly) +const ( + no_timeout = time.Duration(0) +) + +// infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions +// only ever return with data) +const ( + infinite_timeout = time.Duration(-1) +) diff --git a/vlib/x/net/errors.v b/vlib/x/net/errors.v new file mode 100644 index 0000000000..f69b7edeab --- /dev/null +++ b/vlib/x/net/errors.v @@ -0,0 +1,44 @@ +module net + +// Well defined errors that are returned from socket functions +const ( + errors_base = 0 + err_new_socket_failed = error_with_code('net: new_socket failed to create socket', errors_base+1) + err_option_not_settable = error_with_code('net: set_option_xxx option not settable', errors_base+2) + err_option_wrong_type = error_with_code('net: set_option_xxx option wrong type', errors_base+3) + err_port_out_of_range = error_with_code('', errors_base+5) + err_no_udp_remote = error_with_code('', errors_base+6) + err_connect_failed = error_with_code('net: connect failed', errors_base+7) + err_connect_timed_out = error_with_code('net: connect timed out', errors_base+8) + + err_timed_out = error_with_code('net: op timed out', errors_base+9) + err_timed_out_code = errors_base+9 +) + +pub fn socket_error(potential_code int) ?int { + $if windows { + if potential_code < 0 { + last_error_int := C.WSAGetLastError() + last_error := wsa_error(last_error_int) + return error_with_code('net: socket error: ($last_error_int) $last_error', last_error) + } + } + $else { + if potential_code < 0 { + last_error := error_code() + return error_with_code('net: socket error: $last_error', last_error) + } + } + + return potential_code +} + +pub fn wrap_error(error_code int) ? { + $if windows { + enum_error := wsa_error(error_code) + return error_with_code('socket error: $enum_error', error_code) + } + $else { + return error_with_code('net: socket error: $error_code', error_code) + } +} diff --git a/vlib/x/net/net_nix.c.v b/vlib/x/net/net_nix.c.v new file mode 100644 index 0000000000..8cc83c6fba --- /dev/null +++ b/vlib/x/net/net_nix.c.v @@ -0,0 +1,28 @@ +module net + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +fn error_code() int { + return C.errno +} + +fn init() { +} + +pub const ( + msg_nosignal = 0x4000 +) + +const ( + error_ewouldblock = C.EWOULDBLOCK +) + +#flag solaris -lsocket diff --git a/vlib/x/net/net_windows.c.v b/vlib/x/net/net_windows.c.v new file mode 100644 index 0000000000..b8076547bd --- /dev/null +++ b/vlib/x/net/net_windows.c.v @@ -0,0 +1,868 @@ +module net + +// WsaError is all of the socket errors that WSA provides from WSAGetLastError +pub enum WsaError { + // + // MessageId: WSAEINTR + // + // MessageText: + // + // A blocking operation was interrupted by a call to WSACancelBlockingCall. + // + wsaeintr = 10004 + + // + // MessageId: WSAEBADF + // + // MessageText: + // + // The file handle supplied is not valid. + // + wsaebadf = 10009 + + // + // MessageId: WSAEACCES + // + // MessageText: + // + // An attempt was made to access a socket in a way forbidden by its access permissions. + // + wsaeacces = 10013 + + // + // MessageId: WSAEFAULT + // + // MessageText: + // + // The system detected an invalid pointer address in attempting to use a pointer argument in a call. + // + wsaefault = 10014 + + // + // MessageId: WSAEINVAL + // + // MessageText: + // + // An invalid argument was supplied. + // + wsaeinval = 10022 + + // + // MessageId: WSAEMFILE + // + // MessageText: + // + // Too many open sockets. + // + wsaemfile = 10024 + + // + // MessageId: WSAEWOULDBLOCK + // + // MessageText: + // + // A non-blocking socket operation could not be completed immediately. + // + wsaewouldblock = 10035 + + // + // MessageId: WSAEINPROGRESS + // + // MessageText: + // + // A blocking operation is currently executing. + // + wsaeinprogress = 10036 + + // + // MessageId: WSAEALREADY + // + // MessageText: + // + // An operation was attempted on a non-blocking socket that already had an operation in progress. + // + wsaealready = 10037 + + // + // MessageId: WSAENOTSOCK + // + // MessageText: + // + // An operation was attempted on something that is not a socket. + // + wsaenotsock = 10038 + + // + // MessageId: WSAEDESTADDRREQ + // + // MessageText: + // + // A required address was omitted from an operation on a socket. + // + wsaedestaddrreq = 10039 + + // + // MessageId: WSAEMSGSIZE + // + // MessageText: + // + // A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself. + // + wsaemsgsize = 10040 + + // + // MessageId: WSAEPROTOTYPE + // + // MessageText: + // + // A protocol was specified in the socket function call that does not support the semantics of the socket type requested. + // + wsaeprototype = 10041 + + // + // MessageId: WSAENOPROTOOPT + // + // MessageText: + // + // An unknown, invalid, or unsupported option or level was specified in a getsockopt or setsockopt call. + // + wsaenoprotoopt = 10042 + + // + // MessageId: WSAEPROTONOSUPPORT + // + // MessageText: + // + // The requested protocol has not been configured into the system, or no implementation for it exists. + // + wsaeprotonosupport = 10043 + + // + // MessageId: WSAESOCKTNOSUPPORT + // + // MessageText: + // + // The support for the specified socket type does not exist in this address family. + // + wsaesocktnosupport = 10044 + + // + // MessageId: WSAEOPNOTSUPP + // + // MessageText: + // + // The attempted operation is not supported for the type of object referenced. + // + wsaeopnotsupp = 10045 + + // + // MessageId: WSAEPFNOSUPPORT + // + // MessageText: + // + // The protocol family has not been configured into the system or no implementation for it exists. + // + wsaepfnosupport = 10046 + + // + // MessageId: WSAEAFNOSUPPORT + // + // MessageText: + // + // An address incompatible with the requested protocol was used. + // + wsaeafnosupport = 10047 + + // + // MessageId: WSAEADDRINUSE + // + // MessageText: + // + // Only one usage of each socket address (protocol/network address/port) is normally permitted. + // + wsaeaddrinuse = 10048 + + // + // MessageId: WSAEADDRNOTAVAIL + // + // MessageText: + // + // The requested address is not valid in its context. + // + wsaeaddrnotavail = 10049 + + // + // MessageId: WSAENETDOWN + // + // MessageText: + // + // A socket operation encountered a dead network. + // + wsaenetdown = 10050 + + // + // MessageId: WSAENETUNREACH + // + // MessageText: + // + // A socket operation was attempted to an unreachable network. + // + wsaenetunreach = 10051 + + // + // MessageId: WSAENETRESET + // + // MessageText: + // + // The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress. + // + wsaenetreset = 10052 + + // + // MessageId: WSAECONNABORTED + // + // MessageText: + // + // An established connection was aborted by the software in your host machine. + // + wsaeconnaborted = 10053 + + // + // MessageId: WSAECONNRESET + // + // MessageText: + // + // An existing connection was forcibly closed by the remote host. + // + wsaeconnreset = 10054 + + // + // MessageId: WSAENOBUFS + // + // MessageText: + // + // An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full. + // + wsaenobufs = 10055 + + // + // MessageId: WSAEISCONN + // + // MessageText: + // + // A connect request was made on an already connected socket. + // + wsaeisconn = 10056 + + // + // MessageId: WSAENOTCONN + // + // MessageText: + // + // A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied. + // + wsaenotconn = 10057 + + // + // MessageId: WSAESHUTDOWN + // + // MessageText: + // + // A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call. + // + wsaeshutdown = 10058 + + // + // MessageId: WSAETOOMANYREFS + // + // MessageText: + // + // Too many references to some kernel object. + // + wsaetoomanyrefs = 10059 + + // + // MessageId: WSAETIMEDOUT + // + // MessageText: + // + // A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. + // + wsaetimedout = 10060 + + // + // MessageId: WSAECONNREFUSED + // + // MessageText: + // + // No connection could be made because the target machine actively refused it. + // + wsaeconnrefused = 10061 + + // + // MessageId: WSAELOOP + // + // MessageText: + // + // Cannot translate name. + // + wsaeloop = 10062 + + // + // MessageId: WSAENAMETOOLONG + // + // MessageText: + // + // Name component or name was too long. + // + wsaenametoolong = 10063 + + // + // MessageId: WSAEHOSTDOWN + // + // MessageText: + // + // A socket operation failed because the destination host was down. + // + wsaehostdown = 10064 + + // + // MessageId: WSAEHOSTUNREACH + // + // MessageText: + // + // A socket operation was attempted to an unreachable host. + // + wsaehostunreach = 10065 + + // + // MessageId: WSAENOTEMPTY + // + // MessageText: + // + // Cannot remove a directory that is not empty. + // + wsaenotempty = 10066 + + // + // MessageId: WSAEPROCLIM + // + // MessageText: + // + // A Windows Sockets implementation may have a limit on the number of applications that may use it simultaneously. + // + wsaeproclim = 10067 + + // + // MessageId: WSAEUSERS + // + // MessageText: + // + // Ran out of quota. + // + wsaeusers = 10068 + + // + // MessageId: WSAEDQUOT + // + // MessageText: + // + // Ran out of disk quota. + // + wsaedquot = 10069 + + // + // MessageId: WSAESTALE + // + // MessageText: + // + // File handle reference is no longer available. + // + wsaestale = 10070 + + // + // MessageId: WSAEREMOTE + // + // MessageText: + // + // Item is not available locally. + // + wsaeremote = 10071 + + // + // MessageId: WSASYSNOTREADY + // + // MessageText: + // + // WSAStartup cannot function at this time because the underlying system it uses to provide network services is currently unavailable. + // + wsasysnotready = 10091 + + // + // MessageId: WSAVERNOTSUPPORTED + // + // MessageText: + // + // The Windows Sockets version requested is not supported. + // + wsavernotsupported = 10092 + + // + // MessageId: WSANOTINITIALISED + // + // MessageText: + // + // Either the application has not called WSAStartup, or WSAStartup failed. + // + wsanotinitialised = 10093 + + // + // MessageId: WSAEDISCON + // + // MessageText: + // + // Returned by WSARecv or WSARecvFrom to indicate the remote party has initiated a graceful shutdown sequence. + // + wsaediscon = 10101 + + // + // MessageId: WSAENOMORE + // + // MessageText: + // + // No more results can be returned by WSALookupServiceNext. + // + wsaenomore = 10102 + + // + // MessageId: WSAECANCELLED + // + // MessageText: + // + // A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled. + // + wsaecancelled = 10103 + + // + // MessageId: WSAEINVALIDPROCTABLE + // + // MessageText: + // + // The procedure call table is invalid. + // + wsaeinvalidproctable = 10104 + + // + // MessageId: WSAEINVALIDPROVIDER + // + // MessageText: + // + // The requested service provider is invalid. + // + wsaeinvalidprovider = 10105 + + // + // MessageId: WSAEPROVIDERFAILEDINIT + // + // MessageText: + // + // The requested service provider could not be loaded or initialized. + // + wsaeproviderfailedinit = 10106 + + // + // MessageId: WSASYSCALLFAILURE + // + // MessageText: + // + // A system call has failed. + // + wsasyscallfailure = 10107 + + // + // MessageId: WSASERVICE_NOT_FOUND + // + // MessageText: + // + // No such service is known. The service cannot be found in the specified name space. + // + wsaservice_not_found = 10108 + + // + // MessageId: WSATYPE_NOT_FOUND + // + // MessageText: + // + // The specified class was not found. + // + wsatype_not_found = 10109 + + // + // MessageId: WSA_E_NO_MORE + // + // MessageText: + // + // No more results can be returned by WSALookupServiceNext. + // + wsa_e_no_more = 10110 + + // + // MessageId: WSA_E_CANCELLED + // + // MessageText: + // + // A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled. + // + wsa_e_cancelled = 10111 + + // + // MessageId: WSAEREFUSED + // + // MessageText: + // + // A database query failed because it was actively refused. + // + wsaerefused = 10112 + + // + // MessageId: WSAHOST_NOT_FOUND + // + // MessageText: + // + // No such host is known. + // + wsahost_not_found = 11001 + + // + // MessageId: WSATRY_AGAIN + // + // MessageText: + // + // This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server. + // + wsatry_again = 11002 + + // + // MessageId: WSANO_RECOVERY + // + // MessageText: + // + // A non-recoverable error occurred during a database lookup. + // + wsano_recovery = 11003 + + // + // MessageId: WSANO_DATA + // + // MessageText: + // + // The requested name is valid, but no data of the requested type was found. + // + wsano_data = 11004 + + // + // MessageId: WSA_QOS_RECEIVERS + // + // MessageText: + // + // At least one reserve has arrived. + // + wsa_qos_receivers = 11005 + + // + // MessageId: WSA_QOS_SENDERS + // + // MessageText: + // + // At least one path has arrived. + // + wsa_qos_senders = 11006 + + // + // MessageId: WSA_QOS_NO_SENDERS + // + // MessageText: + // + // There are no senders. + // + wsa_qos_no_senders = 11007 + + // + // MessageId: WSA_QOS_NO_RECEIVERS + // + // MessageText: + // + // There are no receivers. + // + wsa_qos_no_receivers = 11008 + + // + // MessageId: WSA_QOS_REQUEST_CONFIRMED + // + // MessageText: + // + // Reserve has been confirmed. + // + wsa_qos_request_confirmed = 11009 + + // + // MessageId: WSA_QOS_ADMISSION_FAILURE + // + // MessageText: + // + // Error due to lack of resources. + // + wsa_qos_admission_failure = 11010 + + // + // MessageId: WSA_QOS_POLICY_FAILURE + // + // MessageText: + // + // Rejected for administrative reasons - bad credentials. + // + wsa_qos_policy_failure = 11011 + + // + // MessageId: WSA_QOS_BAD_STYLE + // + // MessageText: + // + // Unknown or conflicting style. + // + wsa_qos_bad_style = 11012 + + // + // MessageId: WSA_QOS_BAD_OBJECT + // + // MessageText: + // + // Problem with some part of the filterspec or providerspecific buffer in general. + // + wsa_qos_bad_object = 11013 + + // + // MessageId: WSA_QOS_TRAFFIC_CTRL_ERROR + // + // MessageText: + // + // Problem with some part of the flowspec. + // + wsa_qos_traffic_ctrl_error = 11014 + + // + // MessageId: WSA_QOS_GENERIC_ERROR + // + // MessageText: + // + // General QOS error. + // + wsa_qos_generic_error = 11015 + + // + // MessageId: WSA_QOS_ESERVICETYPE + // + // MessageText: + // + // An invalid or unrecognized service type was found in the flowspec. + // + wsa_qos_eservicetype = 11016 + + // + // MessageId: WSA_QOS_EFLOWSPEC + // + // MessageText: + // + // An invalid or inconsistent flowspec was found in the QOS structure. + // + wsa_qos_eflowspec = 11017 + + // + // MessageId: WSA_QOS_EPROVSPECBUF + // + // MessageText: + // + // Invalid QOS provider-specific buffer. + // + wsa_qos_eprovspecbuf = 11018 + + // + // MessageId: WSA_QOS_EFILTERSTYLE + // + // MessageText: + // + // An invalid QOS filter style was used. + // + wsa_qos_efilterstyle = 11019 + + // + // MessageId: WSA_QOS_EFILTERTYPE + // + // MessageText: + // + // An invalid QOS filter type was used. + // + wsa_qos_efiltertype = 11020 + + // + // MessageId: WSA_QOS_EFILTERCOUNT + // + // MessageText: + // + // An incorrect number of QOS FILTERSPECs were specified in the FLOWDESCRIPTOR. + // + wsa_qos_efiltercount = 11021 + + // + // MessageId: WSA_QOS_EOBJLENGTH + // + // MessageText: + // + // An object with an invalid ObjectLength field was specified in the QOS provider-specific buffer. + // + wsa_qos_eobjlength = 11022 + + // + // MessageId: WSA_QOS_EFLOWCOUNT + // + // MessageText: + // + // An incorrect number of flow descriptors was specified in the QOS structure. + // + wsa_qos_eflowcount = 11023 + + // + // MessageId: WSA_QOS_EUNKOWNPSOBJ + // + // MessageText: + // + // An unrecognized object was found in the QOS provider-specific buffer. + // + wsa_qos_eunkownpsobj = 11024 + + // + // MessageId: WSA_QOS_EPOLICYOBJ + // + // MessageText: + // + // An invalid policy object was found in the QOS provider-specific buffer. + // + wsa_qos_epolicyobj = 11025 + + // + // MessageId: WSA_QOS_EFLOWDESC + // + // MessageText: + // + // An invalid QOS flow descriptor was found in the flow descriptor list. + // + wsa_qos_eflowdesc = 11026 + + // + // MessageId: WSA_QOS_EPSFLOWSPEC + // + // MessageText: + // + // An invalid or inconsistent flowspec was found in the QOS provider specific buffer. + // + wsa_qos_epsflowspec = 11027 + + // + // MessageId: WSA_QOS_EPSFILTERSPEC + // + // MessageText: + // + // An invalid FILTERSPEC was found in the QOS provider-specific buffer. + // + wsa_qos_epsfilterspec = 11028 + + // + // MessageId: WSA_QOS_ESDMODEOBJ + // + // MessageText: + // + // An invalid shape discard mode object was found in the QOS provider specific buffer. + // + wsa_qos_esdmodeobj = 11029 + + // + // MessageId: WSA_QOS_ESHAPERATEOBJ + // + // MessageText: + // + // An invalid shaping rate object was found in the QOS provider-specific buffer. + // + wsa_qos_eshaperateobj = 11030 + + // + // MessageId: WSA_QOS_RESERVED_PETYPE + // + // MessageText: + // + // A reserved policy element was found in the QOS provider-specific buffer. + // + wsa_qos_reserved_petype = 11031 + + // + // MessageId: WSA_SECURE_HOST_NOT_FOUND + // + // MessageText: + // + // No such host is known securely. + // + wsa_secure_host_not_found = 11032 + + // + // MessageId: WSA_IPSEC_NAME_POLICY_ERROR + // + // MessageText: + // + // Name based IPSEC policy could not be added. + // + wsa_ipsec_name_policy_error = 11033 +} + +// wsa_error casts an int to its WsaError value +pub fn wsa_error(code int) WsaError { + return WsaError(code) +} + +const ( + error_ewouldblock = WsaError.wsaewouldblock +) + +// Link to Winsock library +#flag -lws2_32 +#include +#include + +// Constants that windows needs +const ( + fionbio = C.FIONBIO + msg_nosignal = 0 + wsa_v22 = 0x202 // C.MAKEWORD(2, 2) +) + +// Error code returns the last socket error +fn error_code() int { + return C.WSAGetLastError() +} + +struct C.WSAData { +mut: + wVersion u16 + wHighVersion u16 + szDescription [257]byte + szSystemStatus [129]byte + iMaxSockets u16 + iMaxUdpDg u16 + lpVendorInfo byteptr +} + +fn init() { + mut wsadata := C.WSAData{} + res := C.WSAStartup(wsa_v22, &wsadata) + if res != 0 { + panic('socket: WSAStartup failed') + } +} diff --git a/vlib/x/net/socket_options.c.v b/vlib/x/net/socket_options.c.v new file mode 100644 index 0000000000..705f2975fa --- /dev/null +++ b/vlib/x/net/socket_options.c.v @@ -0,0 +1,55 @@ +module net + +enum SocketOption { + // TODO: SO_ACCEPT_CONN is not here becuase windows doesnt support it + // and there is no easy way to define it + + broadcast = C.SO_BROADCAST + debug = C.SO_DEBUG + dont_route = C.SO_DONTROUTE + error = C.SO_ERROR + keep_alive = C.SO_KEEPALIVE + linger = C.SO_LINGER + oob_inline = C.SO_OOBINLINE + + reuse_addr = C.SO_REUSEADDR + + recieve_buf_size = C.SO_RCVBUF + recieve_low_size = C.SO_RCVLOWAT + recieve_timeout = C.SO_RCVTIMEO + + send_buf_size = C.SO_SNDBUF + send_low_size = C.SO_SNDLOWAT + send_timeout = C.SO_SNDTIMEO + + socket_type = C.SO_TYPE +} + +const ( + opts_bool = [SocketOption.broadcast, .debug, .dont_route, .error, .keep_alive, .oob_inline] + opts_int = [ + .recieve_buf_size, + .recieve_low_size, + .recieve_timeout, + + .send_buf_size, + .send_low_size, + .send_timeout, + ] + + opts_can_set = [ + SocketOption.broadcast, + .debug, + .dont_route, + .keep_alive, + .linger, + .oob_inline, + .recieve_buf_size, + .recieve_low_size, + .recieve_timeout, + + .send_buf_size, + .send_low_size, + .send_timeout, + ] +) \ No newline at end of file diff --git a/vlib/x/net/tcp.v b/vlib/x/net/tcp.v new file mode 100644 index 0000000000..04277bd4c8 --- /dev/null +++ b/vlib/x/net/tcp.v @@ -0,0 +1,336 @@ +module net + +import time + +pub struct TcpConn { +pub: + sock TcpSocket + +mut: + write_deadline time.Time + read_deadline time.Time + + read_timeout time.Duration + write_timeout time.Duration +} + +pub fn dial_tcp(address string) ?TcpConn { + s := new_tcp_socket()? + s.connect(address)? + + return TcpConn { + sock: s + + read_timeout: -1 + write_timeout: -1 + } +} + +pub fn (c TcpConn) close() ? { + c.sock.close()? + return none +} + +// write_ptr blocks and attempts to write all data +pub fn (c TcpConn) write_ptr(b byteptr, len int) ? { + unsafe { + mut ptr_base := byteptr(b) + mut total_sent := 0 + + for total_sent < len { + ptr := ptr_base + total_sent + remaining := len - total_sent + mut sent := C.send(c.sock.handle, ptr, remaining, msg_nosignal) + + if sent < 0 { + code := error_code() + match code { + error_ewouldblock { + c.wait_for_write() + continue + } + else { + wrap_error(code)? + } + } + } + total_sent += sent + } + } + return none +} + +// write blocks and attempts to write all data +pub fn (c TcpConn) write(bytes []byte) ? { + return c.write_ptr(bytes.data, bytes.len) +} + +// write_string blocks and attempts to write all data +pub fn (c TcpConn) write_string(s string) ? { + return c.write_ptr(s.str, s.len) +} + +pub fn (c TcpConn) read_into(mut buf []byte) ?int { + res := C.recv(c.sock.handle, buf.data, buf.len, 0) + + if res >= 0 { + return res + } + + code := error_code() + match code { + error_ewouldblock { + c.wait_for_read()? + return socket_error(C.recv(c.sock.handle, buf.data, buf.len, 0)) + } + else { + wrap_error(code)? + } + } +} + +pub fn (c TcpConn) read() ?[]byte { + buf := []byte { len: 1024 } + read := c.read_into(buf)? + return buf[..read] +} + +pub fn (c TcpConn) read_deadline() ?time.Time { + if c.read_deadline.unix == 0 { + return c.read_deadline + } + return none +} + +pub fn (mut c TcpConn) set_read_deadline(deadline time.Time) { + c.read_deadline = deadline +} + +pub fn (c TcpConn) write_deadline() ?time.Time { + if c.write_deadline.unix == 0 { + return c.write_deadline + } + return none +} + +pub fn (mut c TcpConn) set_write_deadline(deadline time.Time) { + c.write_deadline = deadline +} + +pub fn (c TcpConn) read_timeout() time.Duration { + return c.read_timeout +} + +pub fn(mut c TcpConn) set_read_timeout(t time.Duration) { + c.read_timeout = t +} + +pub fn (c TcpConn) write_timeout() time.Duration { + return c.write_timeout +} + +pub fn (mut c TcpConn) set_write_timeout(t time.Duration) { + c.write_timeout = t +} + +[inline] +pub fn (c TcpConn) wait_for_read() ? { + return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout) +} + +[inline] +pub fn (c TcpConn) wait_for_write() ? { + return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout) +} + +pub fn (c TcpConn) str() string { + // TODO + return 'TcpConn' +} + +pub struct TcpListener { + sock TcpSocket + +mut: + accept_timeout time.Duration + accept_deadline time.Time +} + +pub fn listen_tcp(port int) ?TcpListener { + s := new_tcp_socket()? + + validate_port(port)? + + mut addr := C.sockaddr_in{} + addr.sin_family = SocketFamily.inet + addr.sin_port = C.htons(port) + addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY) + size := sizeof(C.sockaddr_in) + + // cast to the correct type + sockaddr := &C.sockaddr(&addr) + + socket_error(C.bind(s.handle, sockaddr, size))? + socket_error(C.listen(s.handle, 128))? + + return TcpListener { + sock: s + accept_timeout: -1 + accept_deadline: no_deadline + } +} + +pub fn (l TcpListener) accept() ?TcpConn { + addr := C.sockaddr_storage{} + unsafe { + C.memset(&addr, 0, sizeof(C.sockaddr_storage)) + } + size := sizeof(C.sockaddr_storage) + + // cast to correct type + sock_addr := &C.sockaddr(&addr) + mut new_handle := C.accept(l.sock.handle, sock_addr, &size) + + if new_handle <= 0 { + l.wait_for_accept()? + + new_handle = C.accept(l.sock.handle, sock_addr, &size) + + if new_handle == -1 || new_handle == 0 { + return none + } + } + + new_sock := TcpSocket { + handle: new_handle + } + + return TcpConn{ + sock: new_sock + read_timeout: -1 + write_timeout: -1 + } +} + +pub fn (c TcpListener) accept_deadline() ?time.Time { + if c.accept_deadline.unix != 0 { + return c.accept_deadline + } + return none +} + +pub fn (mut c TcpListener) set_accept_deadline(deadline time.Time) { + c.accept_deadline = deadline +} + +pub fn (c TcpListener) accept_timeout() time.Duration { + return c.accept_timeout +} + +pub fn(mut c TcpListener) set_accept_timeout(t time.Duration) { + c.accept_timeout = t +} + +pub fn (c TcpListener) wait_for_accept() ? { + return wait_for_read(c.sock.handle, c.accept_deadline, c.accept_timeout) +} + +pub fn (c TcpListener) close() ? { + c.sock.close()? + return none +} + +pub fn (c TcpListener) address() ?Addr { + return c.sock.address() +} + +struct TcpSocket { +pub: + handle int +} + +fn new_tcp_socket() ?TcpSocket { + sockfd := socket_error(C.socket(SocketFamily.inet, SocketType.tcp, 0))? + s := TcpSocket { + handle: sockfd + } + s.set_option_bool(.reuse_addr, true)? + $if windows { + t := true + socket_error(C.ioctlsocket(sockfd, fionbio, &t))? + } $else { + socket_error(C.fcntl(sockfd, C.F_SETFD, C.O_NONBLOCK)) + } + return s +} + +pub fn (s TcpSocket) set_option_bool(opt SocketOption, value bool) ? { + // TODO reenable when this `in` operation works again + // if opt !in opts_can_set { + // return err_option_not_settable + // } + + // if opt !in opts_bool { + // return err_option_wrong_type + // } + + socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(bool)))? + + return none +} + +pub fn (s TcpSocket) set_option_int(opt SocketOption, value int) ? { + socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(int)))? + + return none +} + +fn (s TcpSocket) close() ? { + return shutdown(s.handle) +} + +fn (s TcpSocket) @select(test Select, timeout time.Duration) ?bool { + return @select(s.handle, test, timeout) +} + +const ( + connect_timeout = 5 * time.second +) + +fn (s TcpSocket) connect(a string) ? { + addr := resolve_addr(a, .inet, .tcp)? + + res := C.connect(s.handle, &addr.addr, addr.len) + + if res == 0 { + return none + } + + errcode := error_code() + + if errcode == error_ewouldblock { + write_result := s.@select(.write, connect_timeout)? + if write_result { + // succeeded + return none + } + except_result := s.@select(.except, connect_timeout)? + if except_result { + return err_connect_failed + } + // otherwise we timed out + return err_connect_timed_out + } + + return wrap_error(errcode) +} + +// address gets the address of a socket +pub fn (s TcpSocket) address() ?Addr { + mut addr := C.sockaddr_in{} + size := sizeof(C.sockaddr_in) + // cast to the correct type + sockaddr := &C.sockaddr(&addr) + C.getsockname(s.handle, sockaddr, &size) + return new_addr(sockaddr, '', 0) +} \ No newline at end of file diff --git a/vlib/x/net/tcp_test.v b/vlib/x/net/tcp_test.v new file mode 100644 index 0000000000..7232e099c9 --- /dev/null +++ b/vlib/x/net/tcp_test.v @@ -0,0 +1,75 @@ +import x.net as net +import time + +fn handle_conn(_c net.TcpConn) { + mut c := _c + // arbitrary timeouts to ensure that it doesnt + // instantly throw its hands in the air and give up + c.set_read_timeout(10 * time.second) + c.set_write_timeout(10 * time.second) + for { + buf := []byte{ len: 100, init: 0 } + read := c.read_into(mut buf) or { + println('Server: connection dropped') + return + } + + c.write(buf[..read]) or { + println('Server: connection dropped') + return + } + } +} + +fn echo_server(l net.TcpListener) ? { + for { + new_conn := l.accept() or { continue } + go handle_conn(new_conn) + } + + return none +} + +fn echo() ? { + mut c := net.dial_tcp('127.0.0.1:30000')? + defer { c.close() or {} } + + // arbitrary timeouts to ensure that it doesnt + // instantly throw its hands in the air and give up + c.set_read_timeout(10 * time.second) + c.set_write_timeout(10 * time.second) + + data := 'Hello from vlib/net!' + c.write_string(data)? + + buf := []byte{ len: 100, init: 0 } + read := c.read_into(mut buf)? + + assert read == data.len + + for i := 0; i < read; i++ { + assert buf[i] == data[i] + } + + println('Got "${buf.bytestr()}"') + + return none +} + +fn test_tcp() { + l := net.listen_tcp(30000) or { + panic(err) + } + + go echo_server(l) + echo() or { + panic(err) + } + + l.close() or { + } +} + +fn main() { + test_tcp() +} \ No newline at end of file diff --git a/vlib/x/net/udp.v b/vlib/x/net/udp.v new file mode 100644 index 0000000000..7eff651d59 --- /dev/null +++ b/vlib/x/net/udp.v @@ -0,0 +1,247 @@ +module net + +import time + +pub struct UdpConn { + sock UdpSocket + +mut: + write_deadline time.Time + read_deadline time.Time + + read_timeout time.Duration + write_timeout time.Duration +} + +pub fn dial_udp(laddr, raddr string) ?UdpConn { + // Dont have to do this when its fixed + // this just allows us to store this `none` optional in a struct + resolve_wrapper := fn(raddr string) ?Addr { + x := resolve_addr(raddr, .inet, .udp) or { return none } + return x + } + + local := resolve_addr(laddr, .inet, .udp)? + sbase := new_udp_socket(local.port)? + + sock := UdpSocket { + handle: sbase.handle + + l: local + r: resolve_wrapper(raddr) + } + + return UdpConn { + sock + } +} + +pub fn (c UdpConn) write_ptr(b byteptr, len int) ? { + remote := c.sock.remote() or { + return err_no_udp_remote + } + + return c.write_to_ptr(remote, b, len) +} + +pub fn (c UdpConn) write(buf []byte) ? { + return c.write_ptr(buf.data, buf.len) +} + +pub fn (c UdpConn) write_string(s string) ? { + return c.write_ptr(s.str, s.len) +} + +pub fn (c UdpConn) write_to_ptr(addr Addr, b byteptr, len int) ? { + res := C.sendto(c.sock.handle, b, len, 0, &addr.addr, addr.len) + + if res >= 0 { + return none + } + + code := error_code() + match code { + error_ewouldblock { + c.wait_for_write()? + socket_error(C.sendto(c.sock.handle, b, len, 0, &addr.addr, addr.len))? + } + else { + wrap_error(code)? + } + } + + return none +} + +// write_to blocks and writes the buf to the remote addr specified +pub fn (c UdpConn) write_to(addr Addr, buf []byte) ? { + return c.write_to_ptr(addr, buf.data, buf.len) +} + +// write_to_string blocks and writes the buf to the remote addr specified +pub fn (c UdpConn) write_to_string(addr Addr, s string) ? { + return c.write_to_ptr(addr, s.str, s.len) +} + +// read_into reads from the socket into buf up to buf.len returning the number of bytes read +pub fn (c UdpConn) read_into(mut buf []byte) ?(int, Addr) { + mut addr_from := C.sockaddr{} + len := sizeof(C.sockaddr) + + res := C.recvfrom(c.sock.handle, buf.data, buf.len, 0, &addr_from, &len) + + if res >= 0 { + port_from := (&C.sockaddr_in(&addr_from)).sin_port + addr := new_addr(addr_from, '', port_from)? + return res, addr + } + + code := error_code() + match code { + error_ewouldblock { + c.wait_for_read()? + res2 := socket_error(C.recvfrom(c.sock.handle, buf.data, buf.len, 0, &addr_from, &len))? + + port_from := (&C.sockaddr_in(&addr_from)).sin_port + addr := new_addr(addr_from, '', port_from)? + return res2, addr + } + else { + wrap_error(code)? + } + } + + return none +} + +pub fn (c UdpConn) read() ?([]byte, Addr) { + buf := []byte { len: 1024 } + read, addr := c.read_into(mut buf)? + return buf[..read], addr +} + +pub fn (c UdpConn) read_deadline() ?time.Time { + if c.read_deadline.unix == 0 { + return c.read_deadline + } + return none +} + +pub fn (mut c UdpConn) set_read_deadline(deadline time.Time) { + c.read_deadline = deadline +} + +pub fn (c UdpConn) write_deadline() ?time.Time { + if c.write_deadline.unix == 0 { + return c.write_deadline + } + return none +} + +pub fn (mut c UdpConn) set_write_deadline(deadline time.Time) { + c.write_deadline = deadline +} + +pub fn (c UdpConn) read_timeout() time.Duration { + return c.read_timeout +} + +pub fn(mut c UdpConn) set_read_timeout(t time.Duration) { + c.read_timeout = t +} + +pub fn (c UdpConn) write_timeout() time.Duration { + return c.write_timeout +} + +pub fn (mut c UdpConn) set_write_timeout(t time.Duration) { + c.write_timeout = t +} + +[inline] +pub fn (c UdpConn) wait_for_read() ? { + return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout) +} + +[inline] +pub fn (c UdpConn) wait_for_write() ? { + return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout) +} + +pub fn (c UdpConn) str() string { + // TODO + return 'UdpConn' +} + +pub fn (c UdpConn) close() ? { + return c.sock.close() +} + +pub fn listen_udp(port int) ?UdpConn { + s := new_udp_socket(port)? + + return UdpConn { + sock: s + } +} + +struct UdpSocket { + handle int + + l Addr + r ?Addr +} + +fn new_udp_socket(local_port int) ?UdpSocket { + sockfd := socket_error(C.socket(SocketFamily.inet, SocketType.udp, 0))? + s := UdpSocket { + handle: sockfd + } + s.set_option_bool(.reuse_addr, true)? + $if windows { + t := true + socket_error(C.ioctlsocket(sockfd, fionbio, &t))? + } $else { + socket_error(C.fcntl(sockfd, C.F_SETFD, C.O_NONBLOCK)) + } + + // In UDP we always have to bind to a port + validate_port(local_port)? + + mut addr := C.sockaddr_in{} + addr.sin_family = SocketFamily.inet + addr.sin_port = C.htons(local_port) + addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY) + size := sizeof(C.sockaddr_in) + + // cast to the correct type + sockaddr := &C.sockaddr(&addr) + + socket_error(C.bind(s.handle, sockaddr, size))? + + return s +} + +pub fn (s UdpSocket) remote() ?Addr { + return s.r +} + +pub fn (s UdpSocket) set_option_bool(opt SocketOption, value bool) ? { + // TODO reenable when this `in` operation works again + // if opt !in opts_can_set { + // return err_option_not_settable + // } + // if opt !in opts_bool { + // return err_option_wrong_type + // } + socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(bool)))? + return none +} + +fn (s UdpSocket) close() ? { + return shutdown(s.handle) +} + +fn (s UdpSocket) @select(test Select, timeout time.Duration) ?bool { + return @select(s.handle, test, timeout) +} diff --git a/vlib/x/net/udp_test.v b/vlib/x/net/udp_test.v new file mode 100644 index 0000000000..d47e692065 --- /dev/null +++ b/vlib/x/net/udp_test.v @@ -0,0 +1,74 @@ +import x.net as net +import time + +fn echo_server(_c net.UdpConn) { + mut c := _c + // arbitrary timeouts to ensure that it doesnt + // instantly throw its hands in the air and give up + c.set_read_timeout(10 * time.second) + c.set_write_timeout(10 * time.second) + for { + buf := []byte{ len: 100, init: 0 } + read, addr := c.read_into(mut buf) or { + continue + } + + c.write_to(addr, buf[..read]) or { + println('Server: connection dropped') + return + } + } +} + +fn echo() ? { + mut c := net.dial_udp('127.0.0.1:40003', '127.0.0.1:40001')? + defer { c.close() or { } } + + // arbitrary timeouts to ensure that it doesnt + // instantly throw its hands in the air and give up + c.set_read_timeout(10 * time.second) + c.set_write_timeout(10 * time.second) + + data := 'Hello from vlib/net!' + + c.write_string(data)? + + buf := []byte{ len: 100, init: 0 } + read, addr := c.read_into(mut buf)? + + assert read == data.len + println('Got address $addr') + // Can't test this here because loopback addresses + // are mapped to other addresses + // assert addr.str() == '127.0.0.1:30001' + + for i := 0; i < read; i++ { + assert buf[i] == data[i] + } + + println('Got "${buf.bytestr()}"') + + c.close()? + + return none +} + +fn test_udp() { + l := net.listen_udp(40001) or { + println(err) + assert false + panic('') + } + + go echo_server(l) + echo() or { + println(err) + assert false + } + + l.close() or { } +} + +fn main() { + test_udp() +} \ No newline at end of file diff --git a/vlib/x/net/util.v b/vlib/x/net/util.v new file mode 100644 index 0000000000..c2c715cf78 --- /dev/null +++ b/vlib/x/net/util.v @@ -0,0 +1,24 @@ +module net + +const ( + socket_max_port = u16(0xFFFF) +) + +// validate_port checks whether a port is valid +// and returns the port or an error +pub fn validate_port(port int) ?u16 { + if port <= socket_max_port { + return u16(port) + } else { + return err_port_out_of_range + } +} + +// split address splits an address into its host name and its port +pub fn split_address(addr string) ?(string, u16) { + port := addr.all_after_last(':').int() + address := addr.all_before_last(':') + + p := validate_port(port)? + return address, p +}