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..563f250414 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.toAddressString(), 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..c92c63dae0 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,13 +291,7 @@ 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."); if( evutil_make_socket_nonblocking(sockfd) ) throw new Exception("Failed to make socket non-blocking."); @@ -333,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)); 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..dbaab00ec6 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 = anyAddress) +{ + 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; + 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); } @@ -108,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 92c21cce33..7197b6ab39 100644 --- a/source/vibe/http/client.d +++ b/source/vibe/http/client.d @@ -168,20 +168,23 @@ 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; } + 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); + auto ckey = ConnInfo(host, port, use_tls, settings.proxyURL.host, settings.proxyURL.port, settings.networkInterface); 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) { - 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); @@ -205,6 +208,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 = anyAddress; } /// @@ -524,10 +530,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) { 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); + }); +}