Use sockets with new IP Address structs

This commit is contained in:
Eugen Wissner 2021-05-29 09:50:47 +02:00
parent be8fcb3e1c
commit c15a8993ec
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
12 changed files with 66 additions and 252 deletions

View File

@ -35,9 +35,6 @@ type information at compile-time, to transform from one type to another. It has
also different algorithms for iterating, searching and modifying template also different algorithms for iterating, searching and modifying template
arguments. arguments.
* `net`: URL-Parsing, network programming. * `net`: URL-Parsing, network programming.
* `network`: Socket implementation. `network` is currently under rework.
After finishing the new socket implementation will land in the `net` package and
`network` will be deprecated.
* `os`: Platform-independent interfaces to operating system functionality. * `os`: Platform-independent interfaces to operating system functionality.
* `range`: Generic functions and templates for D ranges. * `range`: Generic functions and templates for D ranges.
* `test`: Test suite for unittest-blocks. * `test`: Test suite for unittest-blocks.
@ -167,7 +164,7 @@ parameter is used)
| DMD | GCC | | DMD | GCC |
|:-------:|:---------:| |:-------:|:---------:|
| 2.091.1 | gdc trunk | | 2.096.0 | 10.3 |
## Further characteristics ## Further characteristics

View File

@ -31,7 +31,7 @@ import tanya.async.transport;
import tanya.async.watcher; import tanya.async.watcher;
import tanya.container.array; import tanya.container.array;
import tanya.memory.allocator; import tanya.memory.allocator;
import tanya.network.socket; import tanya.net.socket;
extern (C) nothrow @nogc extern (C) nothrow @nogc
{ {

View File

@ -26,7 +26,7 @@ import tanya.async.watcher;
import tanya.container.array; import tanya.container.array;
import tanya.container.buffer; import tanya.container.buffer;
import tanya.memory.allocator; import tanya.memory.allocator;
import tanya.network.socket; import tanya.net.socket;
/** /**
* Transport for stream sockets. * Transport for stream sockets.

View File

@ -34,7 +34,8 @@
* *
* void main() * void main()
* { * {
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192); * auto address = address4("127.0.0.1");
* auto endpoint = Endpoint(address.get, cast(ushort) 8192);
* *
* version (Windows) * version (Windows)
* { * {
@ -46,7 +47,7 @@
* sock.blocking = false; * sock.blocking = false;
* } * }
* *
* sock.bind(address); * sock.bind(endpoint);
* sock.listen(5); * sock.listen(5);
* *
* auto io = defaultAllocator.make!ConnectionWatcher(sock); * auto io = defaultAllocator.make!ConnectionWatcher(sock);
@ -58,7 +59,6 @@
* sock.shutdown(); * sock.shutdown();
* defaultAllocator.dispose(io); * defaultAllocator.dispose(io);
* defaultAllocator.dispose(sock); * defaultAllocator.dispose(sock);
* defaultAllocator.dispose(address);
* } * }
* --- * ---
* *
@ -78,7 +78,7 @@ import tanya.bitmanip;
import tanya.container.buffer; import tanya.container.buffer;
import tanya.container.list; import tanya.container.list;
import tanya.memory.allocator; import tanya.memory.allocator;
import tanya.network.socket; import tanya.net.socket;
version (DisableBackends) version (DisableBackends)
{ {

View File

@ -19,7 +19,7 @@
module tanya.async.protocol; module tanya.async.protocol;
import tanya.async.transport; import tanya.async.transport;
import tanya.network.socket; import tanya.net.socket;
/** /**
* Common protocol interface. * Common protocol interface.

View File

@ -16,7 +16,7 @@
module tanya.async.transport; module tanya.async.transport;
import tanya.async.protocol; import tanya.async.protocol;
import tanya.network.socket; import tanya.net.socket;
/** /**
* Base transport interface. * Base transport interface.

View File

@ -20,7 +20,7 @@ import tanya.async.transport;
import tanya.container.buffer; import tanya.container.buffer;
import tanya.container.list; import tanya.container.list;
import tanya.memory.allocator; import tanya.memory.allocator;
import tanya.network.socket; import tanya.net.socket;
/** /**
* A watcher is an opaque structure that you allocate and register to record * A watcher is an opaque structure that you allocate and register to record

View File

@ -142,3 +142,23 @@ String indexToName(uint index) @nogc nothrow @trusted
return String(findNullTerminated(buffer)); return String(findNullTerminated(buffer));
} }
} }
/**
* $(D_PSYMBOL AddressFamily) specifies a communication domain; this selects
* the protocol family which will be used for communication.
*/
enum AddressFamily : int
{
unspec = 0, /// Unspecified.
local = 1, /// Local to host (pipes and file-domain).
unix = local, /// POSIX name for PF_LOCAL.
inet = 2, /// IP protocol family.
ax25 = 3, /// Amateur Radio AX.25.
ipx = 4, /// Novell Internet Protocol.
appletalk = 5, /// Appletalk DDP.
netrom = 6, /// Amateur radio NetROM.
bridge = 7, /// Multiprotocol bridge.
atmpvc = 8, /// ATM PVCs.
x25 = 9, /// Reserved for X.25 project.
inet6 = 10, /// IP version 6.
}

View File

@ -14,7 +14,6 @@
*/ */
module tanya.net.ip; module tanya.net.ip;
import core.sys.posix.sys.socket;
import std.algorithm.comparison; import std.algorithm.comparison;
import std.ascii; import std.ascii;
import std.typecons; import std.typecons;
@ -1331,7 +1330,7 @@ struct Address
*/ */
struct Endpoint struct Endpoint
{ {
private sa_family_t family = AF_UNSPEC; private AddressFamily family = AddressFamily.unspec;
private ubyte[ushort.sizeof] service; private ubyte[ushort.sizeof] service;
private Address4 address4; // Unused sin6_flowinfo if IPv6 private Address4 address4; // Unused sin6_flowinfo if IPv6
private Address6 address6; // Unused if IPv4 private Address6 address6; // Unused if IPv4
@ -1345,10 +1344,12 @@ struct Endpoint
* Constructs an endpoint. * Constructs an endpoint.
* *
* Params: * Params:
* T = Address type (IPv4 or IPv6).
* address = IP address that should be associated with the endpoint. * address = IP address that should be associated with the endpoint.
* port = Port number in network byte order. * port = Port number in network byte order.
*/ */
this(Address address, const ushort port = anyPort) this(T)(T address, const ushort port = anyPort)
if (is(T == Address) || is(T == Address4) || is(T == Address6))
{ {
this.address = address; this.address = address;
this.port = port; this.port = port;
@ -1357,7 +1358,7 @@ struct Endpoint
/** /**
* Returns: Port number in network byte order. * Returns: Port number in network byte order.
*/ */
@property ushort port() const @nogc nothrow pure @safe @property inout(ushort) port() inout const @nogc nothrow pure @safe
{ {
return this.service[].toHostOrder!ushort(); return this.service[].toHostOrder!ushort();
} }
@ -1374,13 +1375,13 @@ struct Endpoint
/** /**
* Returns: IP address associated with the endpoint. * Returns: IP address associated with the endpoint.
*/ */
@property Address address() @nogc nothrow pure @safe @property inout(Address) address() inout @nogc nothrow pure @safe
{ {
if (this.family == AF_INET) if (this.family == AddressFamily.inet)
{ {
return Address(this.address4); return Address(this.address4);
} }
else if (this.family == AF_INET6) else if (this.family == AddressFamily.inet6)
{ {
return Address(this.address6); return Address(this.address6);
} }
@ -1395,14 +1396,26 @@ struct Endpoint
{ {
if (address.isV4()) if (address.isV4())
{ {
this.family = AF_INET; this.address = address.toV4();
this.address4 = address.toV4();
} }
else if (address.isV6()) else if (address.isV6())
{ {
this.family = AF_INET6; this.address = address.toV6();
this.address4 = Address4(0);
this.address6 = address.toV6();
} }
} }
/// ditto
@property void address(Address4 address) @nogc nothrow pure @safe
{
this.family = AddressFamily.inet;
this.address4 = address;
}
/// ditto
@property void address(Address6 address) @nogc nothrow pure @safe
{
this.family = AddressFamily.inet6;
this.address4 = Address4(0);
this.address6 = address;
}
} }

View File

@ -17,4 +17,5 @@ module tanya.net;
public import tanya.net.iface; public import tanya.net.iface;
public import tanya.net.inet; public import tanya.net.inet;
public import tanya.net.ip; public import tanya.net.ip;
public import tanya.net.socket;
public import tanya.net.uri; public import tanya.net.uri;

View File

@ -7,37 +7,6 @@
* *
* Current API supports only server-side TCP communication. * Current API supports only server-side TCP communication.
* *
* Here is an example of a cross-platform blocking server:
*
* ---
* import std.stdio;
* import tanya.memory;
* import tanya.network;
*
* void main()
* {
* auto socket = defaultAllocator.make!StreamSocket(AddressFamily.inet);
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1",
* cast(ushort) 8192);
*
* socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
* socket.blocking = true;
* socket.bind(address);
* socket.listen(5);
*
* auto client = socket.accept();
* client.send(cast(const(ubyte)[]) "Test\n");
*
* ubyte[100] buf;
* auto response = client.receive(buf[]);
*
* writeln(cast(const(char)[]) buf[0 .. response]);
*
* defaultAllocator.dispose(client);
* defaultAllocator.dispose(socket);
* }
* ---
*
* For an example of an asynchronous server refer to the documentation of the * For an example of an asynchronous server refer to the documentation of the
* $(D_PSYMBOL tanya.async.loop) module. * $(D_PSYMBOL tanya.async.loop) module.
* *
@ -48,7 +17,7 @@
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/network/socket.d, * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/network/socket.d,
* tanya/network/socket.d) * tanya/network/socket.d)
*/ */
module tanya.network.socket; module tanya.net.socket;
import core.stdc.errno; import core.stdc.errno;
import core.time; import core.time;
@ -57,6 +26,8 @@ import tanya.bitmanip;
import tanya.memory.allocator; import tanya.memory.allocator;
import tanya.meta.trait; import tanya.meta.trait;
import tanya.os.error; import tanya.os.error;
import tanya.net.iface;
import tanya.net.ip;
/// Value returned by socket operations on error. /// Value returned by socket operations on error.
enum int socketError = -1; enum int socketError = -1;
@ -588,26 +559,6 @@ shared static this()
} }
} }
/**
* $(D_PSYMBOL AddressFamily) specifies a communication domain; this selects
* the protocol family which will be used for communication.
*/
enum AddressFamily : int
{
unspec = 0, /// Unspecified.
local = 1, /// Local to host (pipes and file-domain).
unix = local, /// POSIX name for PF_LOCAL.
inet = 2, /// IP protocol family.
ax25 = 3, /// Amateur Radio AX.25.
ipx = 4, /// Novell Internet Protocol.
appletalk = 5, /// Appletalk DDP.
netrom = 6, /// Amateur radio NetROM.
bridge = 7, /// Multiprotocol bridge.
atmpvc = 8, /// ATM PVCs.
x25 = 9, /// Reserved for X.25 project.
inet6 = 10, /// IP version 6.
}
/** /**
* $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions * $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions
* $(D_PSYMBOL socketError) and sets $(D_PSYMBOL errno), because * $(D_PSYMBOL socketError) and sets $(D_PSYMBOL errno), because
@ -1064,13 +1015,13 @@ class StreamSocket : Socket, ConnectionOrientedSocket
* Associate a local address with this socket. * Associate a local address with this socket.
* *
* Params: * Params:
* address = Local address. * endpoint = Local address.
* *
* Throws: $(D_PSYMBOL SocketException) if unable to bind. * Throws: $(D_PSYMBOL SocketException) if unable to bind.
*/ */
void bind(Address address) const @trusted @nogc void bind(const Endpoint endpoint) const @nogc
{ {
if (.bind(handle_, address.name, address.length) == socketError) if (.bind(handle_, cast(const(sockaddr)*) &endpoint, Endpoint.sizeof))
{ {
throw defaultAllocator.make!SocketException("Unable to bind socket"); throw defaultAllocator.make!SocketException("Unable to bind socket");
} }
@ -1268,157 +1219,6 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
} }
} }
/**
* Socket address representation.
*/
abstract class Address
{
/**
* Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
*/
abstract @property inout(sockaddr)* name() inout pure nothrow @nogc;
/**
* Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.
*/
abstract @property inout(socklen_t) length() inout const pure nothrow @nogc;
}
class InternetAddress : Address
{
version (Windows)
{
/// Internal internet address representation.
protected SOCKADDR_STORAGE storage;
}
else version (Posix)
{
/// Internal internet address representation.
protected sockaddr_storage storage;
}
const ushort port_;
enum ushort anyPort = 0;
this(string host, const ushort port = anyPort) @nogc
{
if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
{
throw make!SocketException(defaultAllocator,
"Address info lookup is not available on this system");
}
addrinfo* ai_res;
this.port_ = port;
// Make C-string from host.
auto node = cast(char[]) allocator.allocate(host.length + 1);
node[0 .. $ - 1] = host;
node[$ - 1] = '\0';
scope (exit)
{
allocator.deallocate(node);
}
// Convert port to a C-string.
char[6] service = [0, 0, 0, 0, 0, 0];
const(char)* servicePointer;
if (port)
{
ushort originalPort = port;
ushort start;
for (ushort j = 10, i = 4; i > 0; j *= 10, --i)
{
ushort rest = originalPort % 10;
if (rest != 0)
{
service[i] = cast(char) (rest + '0');
start = i;
}
originalPort /= 10;
}
servicePointer = service[start .. $].ptr;
}
auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);
if (ret)
{
throw defaultAllocator.make!SocketException("Address info lookup failed");
}
scope (exit)
{
freeaddrinfoPointer(ai_res);
}
ubyte* dp = cast(ubyte*) &storage, sp = cast(ubyte*) ai_res.ai_addr;
for (auto i = ai_res.ai_addrlen; i > 0; --i, *dp++, *sp++)
{
*dp = *sp;
}
if (ai_res.ai_family != AddressFamily.inet && ai_res.ai_family != AddressFamily.inet6)
{
throw defaultAllocator.make!SocketException("Wrong address family");
}
}
///
unittest
{
auto address = defaultAllocator.make!InternetAddress("127.0.0.1");
assert(address.port == InternetAddress.anyPort);
assert(address.name !is null);
assert(address.family == AddressFamily.inet);
defaultAllocator.dispose(address);
}
/**
* Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
*/
override @property inout(sockaddr)* name() inout pure nothrow @nogc
{
return cast(sockaddr*) &storage;
}
/**
* Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.
*/
override @property inout(socklen_t) length() inout const pure nothrow @nogc
{
// FreeBSD wants to know the exact length of the address on bind.
switch (family)
{
case AddressFamily.inet:
return sockaddr_in.sizeof;
case AddressFamily.inet6:
return sockaddr_in6.sizeof;
default:
assert(false);
}
}
/**
* Returns: Family of this address.
*/
@property inout(AddressFamily) family() inout const pure nothrow @nogc
{
return cast(AddressFamily) storage.ss_family;
}
@property inout(ushort) port() inout const pure nothrow @nogc
{
return port_;
}
///
unittest
{
auto address = defaultAllocator.make!InternetAddress("127.0.0.1",
cast(ushort) 1234);
assert(address.port == 1234);
defaultAllocator.dispose(address);
}
}
/** /**
* Checks if the last error is a serious error or just a special * Checks if the last error is a serious error or just a special
* behaviour error of non-blocking sockets (for example an error * behaviour error of non-blocking sockets (for example an error

View File

@ -1,17 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Network programming.
*
* Copyright: Eugene Wissner 2016-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/network/package.d,
* tanya/network/package.d)
*/
module tanya.network;
public import tanya.network.socket;