Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specification of the local network interface for HTTP requests. Fixes #1389. #1407

Merged
merged 8 commits into from
Feb 15, 2016
2 changes: 1 addition & 1 deletion source/vibe/core/driver.d
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
4 changes: 3 additions & 1 deletion source/vibe/core/drivers/libasync.d
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ final class LibasyncDriver : EventDriver {

}

LibasyncTCPConnection connectTCP(NetworkAddress addr)
LibasyncTCPConnection connectTCP(NetworkAddress addr, NetworkAddress bind_addr)
{
AsyncTCPConnection conn = new AsyncTCPConnection(getEventLoop());

Expand All @@ -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);
Expand Down
13 changes: 6 additions & 7 deletions source/vibe/core/drivers/libevent2.d
Original file line number Diff line number Diff line change
Expand Up @@ -281,22 +281,17 @@ 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
static if (typeof(sockfd_raw).max > int.max) assert(sockfd_raw <= int.max || sockfd_raw == ~0);
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.");
Expand Down Expand Up @@ -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));

Expand Down
6 changes: 1 addition & 5 deletions source/vibe/core/drivers/win32.d
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
31 changes: 27 additions & 4 deletions source/vibe/core/net.d
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}


Expand All @@ -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 {
Expand Down
25 changes: 17 additions & 8 deletions source/vibe/http/client.d
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}

///
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 7 additions & 0 deletions tests/vibe.http.client.1389/dub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "1389",
"dependencies": {
"vibe-d:http": { "path": "../../" }
},
"versions": ["VibeDefaultMain"]
}
42 changes: 42 additions & 0 deletions tests/vibe.http.client.1389/source/app.d
Original file line number Diff line number Diff line change
@@ -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);
});
}