From 06296a98b06d56069743bac357e8abab46868340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Mon, 8 Feb 2016 17:50:02 +0100 Subject: [PATCH 1/8] Add "bind_address" parameter to connectTCP. --- source/vibe/core/driver.d | 2 +- source/vibe/core/drivers/libasync.d | 4 +++- source/vibe/core/drivers/libevent2.d | 7 ++----- source/vibe/core/drivers/win32.d | 6 +----- source/vibe/core/net.d | 24 ++++++++++++++++++++---- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/source/vibe/core/driver.d b/source/vibe/core/driver.d index 948001ae42..b4f9256ca1 100644 --- a/source/vibe/core/driver.d +++ b/source/vibe/core/driver.d @@ -108,7 +108,7 @@ interface EventDriver { /** Establiches a tcp connection on the specified host/port. */ - TCPConnection connectTCP(NetworkAddress address); + TCPConnection connectTCP(NetworkAddress address, NetworkAddress bind_address); /** Listens on the specified port and interface for TCP connections. diff --git a/source/vibe/core/drivers/libasync.d b/source/vibe/core/drivers/libasync.d index 1398dd49ae..89c6b972ca 100644 --- a/source/vibe/core/drivers/libasync.d +++ b/source/vibe/core/drivers/libasync.d @@ -270,7 +270,7 @@ final class LibasyncDriver : EventDriver { } - LibasyncTCPConnection connectTCP(NetworkAddress addr) + LibasyncTCPConnection connectTCP(NetworkAddress addr, NetworkAddress bind_addr) { AsyncTCPConnection conn = new AsyncTCPConnection(getEventLoop()); @@ -285,6 +285,8 @@ final class LibasyncDriver : EventDriver { tcp_connection.acquireWriter(); tcp_connection.m_tcpImpl.conn = conn; + //conn.local = bind_addr; + conn.ip(bind_addr.toString(), bind_addr.port); conn.peer = addr; enforce(conn.run(&tcp_connection.handler), "An error occured while starting a new connection: " ~ conn.error); diff --git a/source/vibe/core/drivers/libevent2.d b/source/vibe/core/drivers/libevent2.d index adffc21284..bddfc26518 100644 --- a/source/vibe/core/drivers/libevent2.d +++ b/source/vibe/core/drivers/libevent2.d @@ -281,8 +281,9 @@ final class Libevent2Driver : EventDriver { return msg.addr; } - Libevent2TCPConnection connectTCP(NetworkAddress addr) + Libevent2TCPConnection connectTCP(NetworkAddress addr, NetworkAddress bind_addr) { + assert(addr.family == bind_addr.family, "Mismatching bind and target address."); auto sockfd_raw = socket(addr.family, SOCK_STREAM, 0); // on Win64 socket() returns a 64-bit value but libevent expects an int @@ -290,10 +291,6 @@ final class Libevent2Driver : EventDriver { auto sockfd = cast(int)sockfd_raw; socketEnforce(sockfd != -1, "Failed to create socket."); - NetworkAddress bind_addr; - bind_addr.family = addr.family; - if (addr.family == AF_INET) bind_addr.sockAddrInet4.sin_addr.s_addr = 0; - else bind_addr.sockAddrInet6.sin6_addr.s6_addr[] = 0; socketEnforce(bind(sockfd, bind_addr.sockAddr, bind_addr.sockAddrLen) == 0, "Failed to bind socket."); socklen_t balen = bind_addr.sockAddrLen; socketEnforce(getsockname(sockfd, bind_addr.sockAddr, &balen) == 0, "getsockname failed."); diff --git a/source/vibe/core/drivers/win32.d b/source/vibe/core/drivers/win32.d index 4c83595308..4fd1a1e1f8 100644 --- a/source/vibe/core/drivers/win32.d +++ b/source/vibe/core/drivers/win32.d @@ -277,17 +277,13 @@ final class Win32EventDriver : EventDriver { return addr; } - Win32TCPConnection connectTCP(NetworkAddress addr) + Win32TCPConnection connectTCP(NetworkAddress addr, NetworkAddress bind_addr) { assert(m_tid == GetCurrentThreadId()); auto sock = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, null, 0, WSA_FLAG_OVERLAPPED); socketEnforce(sock != INVALID_SOCKET, "Failed to create socket"); - NetworkAddress bind_addr; - bind_addr.family = addr.family; - if (addr.family == AF_INET) bind_addr.sockAddrInet4.sin_addr.s_addr = 0; - else bind_addr.sockAddrInet6.sin6_addr.s6_addr[] = 0; socketEnforce(bind(sock, bind_addr.sockAddr, bind_addr.sockAddrLen) == 0, "Failed to bind socket"); auto conn = new Win32TCPConnection(this, sock, addr); diff --git a/source/vibe/core/net.d b/source/vibe/core/net.d index a61b9da1ae..4dc260b985 100644 --- a/source/vibe/core/net.d +++ b/source/vibe/core/net.d @@ -88,15 +88,31 @@ TCPListener listenTCP_s(ushort port, void function(TCPConnection stream) connect /** Establishes a connection to the given host/port. */ -TCPConnection connectTCP(string host, ushort port) +TCPConnection connectTCP(string host, ushort port, string bind_interface = null, ushort bind_port = 0) { NetworkAddress addr = resolveHost(host); addr.port = port; - return connectTCP(addr); + NetworkAddress bind_address; + if (bind_interface.length) bind_address = resolveHost(bind_interface, addr.family); + else { + bind_address.family = addr.family; + if (addr.family == AF_INET) bind_address.sockAddrInet4.sin_addr.s_addr = 0; + else bind_address.sockAddrInet6.sin6_addr.s6_addr[] = 0; + } + bind_address.port = bind_port; + return getEventDriver().connectTCP(addr, bind_address); } /// ditto -TCPConnection connectTCP(NetworkAddress addr) { - return getEventDriver().connectTCP(addr); +TCPConnection connectTCP(NetworkAddress addr, NetworkAddress bind_address = NetworkAddress.init) +{ + if (bind_address == NetworkAddress.init) { + bind_address.family = addr.family; + if (addr.family == AF_INET) bind_address.sockAddrInet4.sin_addr.s_addr = 0; + else bind_address.sockAddrInet6.sin6_addr.s6_addr[] = 0; + bind_address.port = 0; + } + enforce(addr.family == bind_address.family, "Destination address and bind address have different address families."); + return getEventDriver().connectTCP(addr, bind_address); } From 352daa35032fe2d325fbc7c8856c2f130815bad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Mon, 8 Feb 2016 17:55:32 +0100 Subject: [PATCH 2/8] Add HTTPClientSettings.networkInterface field. --- source/vibe/http/client.d | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/source/vibe/http/client.d b/source/vibe/http/client.d index 92c21cce33..2f8dcc47ca 100644 --- a/source/vibe/http/client.d +++ b/source/vibe/http/client.d @@ -205,6 +205,9 @@ auto connectHTTP(string host, ushort port = 0, bool use_tls = false, HTTPClientS class HTTPClientSettings { URL proxyURL; Duration defaultKeepAliveTimeout = 10.seconds; + + /// Forces a specific network interface to use for outgoing connections. + NetworkAddress networkInterface; } /// @@ -524,10 +527,13 @@ final class HTTPClient { NetworkAddress proxyAddr = resolveHost(m_settings.proxyURL.host, 0, use_dns); proxyAddr.port = m_settings.proxyURL.port; - m_conn = connectTCP(proxyAddr); + m_conn = connectTCP(proxyAddr, m_settings.networkInterface); + } + else { + auto addr = resolveHost(m_server); + addr.port = m_port; + m_conn = connectTCP(addr, m_settings.networkInterface); } - else - m_conn = connectTCP(m_server, m_port); m_stream = m_conn; if (m_tls) { From 9a2846588cc513c3bc481e355b9e847c551f9481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Mon, 8 Feb 2016 19:37:45 +0100 Subject: [PATCH 3/8] Fix determining the local address for Libevent2Driver.connectTCP. --- source/vibe/core/drivers/libevent2.d | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/vibe/core/drivers/libevent2.d b/source/vibe/core/drivers/libevent2.d index bddfc26518..c92c63dae0 100644 --- a/source/vibe/core/drivers/libevent2.d +++ b/source/vibe/core/drivers/libevent2.d @@ -292,8 +292,6 @@ final class Libevent2Driver : EventDriver { socketEnforce(sockfd != -1, "Failed to create socket."); socketEnforce(bind(sockfd, bind_addr.sockAddr, bind_addr.sockAddrLen) == 0, "Failed to bind socket."); - socklen_t balen = bind_addr.sockAddrLen; - socketEnforce(getsockname(sockfd, bind_addr.sockAddr, &balen) == 0, "getsockname failed."); if( evutil_make_socket_nonblocking(sockfd) ) throw new Exception("Failed to make socket non-blocking."); @@ -330,6 +328,10 @@ final class Libevent2Driver : EventDriver { throw new Exception(format("Failed to connect to %s: %s", addr.toString(), e.msg)); } + socklen_t balen = bind_addr.sockAddrLen; + socketEnforce(getsockname(sockfd, bind_addr.sockAddr, &balen) == 0, "getsockname failed."); + cctx.local_addr = bind_addr; + logTrace("Connect result status: %d", cctx.status); enforce(cctx.status == BEV_EVENT_CONNECTED, format("Failed to connect to host %s: %s", addr.toString(), cctx.status)); From 4312966d712654e9628bb78eec338d5bbb14a267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Mon, 8 Feb 2016 19:38:23 +0100 Subject: [PATCH 4/8] Fix HTTP client to take network interface into account when selecting the connection pool. --- source/vibe/http/client.d | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/source/vibe/http/client.d b/source/vibe/http/client.d index 2f8dcc47ca..921993784f 100644 --- a/source/vibe/http/client.d +++ b/source/vibe/http/client.d @@ -170,14 +170,16 @@ unittest { */ auto connectHTTP(string host, ushort port = 0, bool use_tls = false, HTTPClientSettings settings = defaultSettings) { - static struct ConnInfo { string host; ushort port; bool useTLS; string proxyIP; ushort proxyPort; } + static struct ConnInfo { string host; ushort port; bool useTLS; string proxyIP; ushort proxyPort; NetworkAddress bind_addr; } static FixedRingBuffer!(Tuple!(ConnInfo, ConnectionPool!HTTPClient), 16) s_connections; if( port == 0 ) port = use_tls ? 443 : 80; - auto ckey = ConnInfo(host, port, use_tls, settings?settings.proxyURL.host:null, settings?settings.proxyURL.port:0); + auto ckey = ConnInfo(host, port, use_tls, + settings?settings.proxyURL.host:null, settings?settings.proxyURL.port:0, + settings ? settings.networkInterface : NetworkAddress.init); ConnectionPool!HTTPClient pool; foreach (c; s_connections) - if (c[0].host == host && c[0].port == port && c[0].useTLS == use_tls && ((c[0].proxyIP == settings.proxyURL.host && c[0].proxyPort == settings.proxyURL.port) || settings is null)) + if (c[0] == ckey) pool = c[1]; if (!pool) { From 36879017266be085fb3ca36384d2a2bdfe198057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Mon, 8 Feb 2016 20:08:11 +0100 Subject: [PATCH 5/8] Fix setting the TCP bind_address in the libasync driver. --- source/vibe/core/drivers/libasync.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/vibe/core/drivers/libasync.d b/source/vibe/core/drivers/libasync.d index 89c6b972ca..563f250414 100644 --- a/source/vibe/core/drivers/libasync.d +++ b/source/vibe/core/drivers/libasync.d @@ -286,7 +286,7 @@ final class LibasyncDriver : EventDriver { tcp_connection.m_tcpImpl.conn = conn; //conn.local = bind_addr; - conn.ip(bind_addr.toString(), bind_addr.port); + conn.ip(bind_addr.toAddressString(), bind_addr.port); conn.peer = addr; enforce(conn.run(&tcp_connection.handler), "An error occured while starting a new connection: " ~ conn.error); From 737c877b9fe452045b55e3c87b5a5c348ca54ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Mon, 8 Feb 2016 20:15:02 +0100 Subject: [PATCH 6/8] Add test for #1389. --- tests/vibe.http.client.1389/dub.json | 7 ++++ tests/vibe.http.client.1389/source/app.d | 42 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 tests/vibe.http.client.1389/dub.json create mode 100644 tests/vibe.http.client.1389/source/app.d diff --git a/tests/vibe.http.client.1389/dub.json b/tests/vibe.http.client.1389/dub.json new file mode 100644 index 0000000000..d055be11a6 --- /dev/null +++ b/tests/vibe.http.client.1389/dub.json @@ -0,0 +1,7 @@ +{ + "name": "1389", + "dependencies": { + "vibe-d:http": { "path": "../../" } + }, + "versions": ["VibeDefaultMain"] +} diff --git a/tests/vibe.http.client.1389/source/app.d b/tests/vibe.http.client.1389/source/app.d new file mode 100644 index 0000000000..7ef31bad17 --- /dev/null +++ b/tests/vibe.http.client.1389/source/app.d @@ -0,0 +1,42 @@ +import vibe.core.core; +import vibe.core.log; +import vibe.http.client; +import vibe.http.server; +import vibe.stream.operations : readAllUTF8; +import std.exception : assertThrown; + +shared static this() +{ + // determine external network interface + auto ec = connectTCP("vibed.org", 80); + auto li = ec.localAddress; + ec.close(); + li.port = 0; + logInfo("External interface: %s", li.toString()); + + auto settings = new HTTPServerSettings; + // 10k + issue number -> Avoid bind errors + settings.port = 11389; + settings.bindAddresses = [li.toAddressString()]; + listenHTTP(settings, (req, res) { + if (req.clientAddress.toAddressString() == "127.0.0.1") + res.writeBody("local"); + else res.writeBody("remote"); + }); + + runTask({ + scope(exit) exitEventLoop(true); + + auto url = "http://"~li.toAddressString~":11389/"; + + auto cs = new HTTPClientSettings; + cs.networkInterface = resolveHost("127.0.0.1"); + auto res = requestHTTP(url, null, cs).bodyReader.readAllUTF8(); + assert(res == "local", "Unexpected reply: "~res); + + auto cs2 = new HTTPClientSettings; + cs2.networkInterface = li; + res = requestHTTP(url, null, cs2).bodyReader.readAllUTF8(); + assert(res == "remote", "Unexpected reply: "~res); + }); +} From be6562d11f96b51461f9a402a3e230f80981344f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Mon, 8 Feb 2016 20:35:16 +0100 Subject: [PATCH 7/8] Avoid comparing NetworkAddresses. Doesn't work due to union members. --- source/vibe/core/net.d | 11 +++++++++-- source/vibe/http/client.d | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/source/vibe/core/net.d b/source/vibe/core/net.d index 4dc260b985..dbaab00ec6 100644 --- a/source/vibe/core/net.d +++ b/source/vibe/core/net.d @@ -103,9 +103,9 @@ TCPConnection connectTCP(string host, ushort port, string bind_interface = null, return getEventDriver().connectTCP(addr, bind_address); } /// ditto -TCPConnection connectTCP(NetworkAddress addr, NetworkAddress bind_address = NetworkAddress.init) +TCPConnection connectTCP(NetworkAddress addr, NetworkAddress bind_address = anyAddress) { - if (bind_address == NetworkAddress.init) { + if (bind_address.family == AF_UNSPEC) { bind_address.family = addr.family; if (addr.family == AF_INET) bind_address.sockAddrInet4.sin_addr.s_addr = 0; else bind_address.sockAddrInet6.sin6_addr.s6_addr[] = 0; @@ -124,6 +124,13 @@ UDPConnection listenUDP(ushort port, string bind_address = "0.0.0.0") return getEventDriver().listenUDP(port, bind_address); } +NetworkAddress anyAddress() +{ + NetworkAddress ret; + ret.family = AF_UNSPEC; + return ret; +} + version(VibeLibasyncDriver) { public import libasync.events : NetworkAddress; } else { diff --git a/source/vibe/http/client.d b/source/vibe/http/client.d index 921993784f..66624d622d 100644 --- a/source/vibe/http/client.d +++ b/source/vibe/http/client.d @@ -175,7 +175,7 @@ auto connectHTTP(string host, ushort port = 0, bool use_tls = false, HTTPClientS if( port == 0 ) port = use_tls ? 443 : 80; auto ckey = ConnInfo(host, port, use_tls, settings?settings.proxyURL.host:null, settings?settings.proxyURL.port:0, - settings ? settings.networkInterface : NetworkAddress.init); + settings ? settings.networkInterface : anyAddress); ConnectionPool!HTTPClient pool; foreach (c; s_connections) @@ -209,7 +209,7 @@ class HTTPClientSettings { Duration defaultKeepAliveTimeout = 10.seconds; /// Forces a specific network interface to use for outgoing connections. - NetworkAddress networkInterface; + NetworkAddress networkInterface = anyAddress; } /// From 70b4cc537f52557798bb9173c75f9dcdc0492759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Tue, 9 Feb 2016 09:29:31 +0100 Subject: [PATCH 8/8] Avoid redundant null checks. --- source/vibe/http/client.d | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/source/vibe/http/client.d b/source/vibe/http/client.d index 66624d622d..7197b6ab39 100644 --- a/source/vibe/http/client.d +++ b/source/vibe/http/client.d @@ -168,14 +168,15 @@ unittest { usually requestHTTP should be used for making requests instead of manually using a HTTPClient to do so. */ -auto connectHTTP(string host, ushort port = 0, bool use_tls = false, HTTPClientSettings settings = defaultSettings) +auto connectHTTP(string host, ushort port = 0, bool use_tls = false, HTTPClientSettings settings = null) { static struct ConnInfo { string host; ushort port; bool useTLS; string proxyIP; ushort proxyPort; NetworkAddress bind_addr; } static FixedRingBuffer!(Tuple!(ConnInfo, ConnectionPool!HTTPClient), 16) s_connections; + + if (!settings) settings = defaultSettings; + if( port == 0 ) port = use_tls ? 443 : 80; - auto ckey = ConnInfo(host, port, use_tls, - settings?settings.proxyURL.host:null, settings?settings.proxyURL.port:0, - settings ? settings.networkInterface : anyAddress); + auto ckey = ConnInfo(host, port, use_tls, settings.proxyURL.host, settings.proxyURL.port, settings.networkInterface); ConnectionPool!HTTPClient pool; foreach (c; s_connections) @@ -183,7 +184,7 @@ auto connectHTTP(string host, ushort port = 0, bool use_tls = false, HTTPClientS pool = c[1]; if (!pool) { - logDebug("Create HTTP client pool %s:%s %s proxy %s:%d", host, port, use_tls, ( settings ) ? settings.proxyURL.host : string.init, ( settings ) ? settings.proxyURL.port : 0); + logDebug("Create HTTP client pool %s:%s %s proxy %s:%d", host, port, use_tls, settings.proxyURL.host, settings.proxyURL.port); pool = new ConnectionPool!HTTPClient({ auto ret = new HTTPClient; ret.connect(host, port, use_tls, settings);