Adding PortAllocator option to support cases where sockets can't be bound.
This CL adds the flag "PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS", which will force the creation of ports not bound to any specific network interface. These are normally only used when network enumeration fails or is disabled, but in some circumstances (such as the one the test case adds), they're the only thing that works. This will result in extra ports being gathered, which is why it's only enabled behind a flag for now. In the future, we could probably introduce more sophisticated "pruning" logic that would lessen the impact of the extra ports when they're redundant, and make the flag the default. Some other minor changes that were required to make this use case work: * Allow a TCPPort to be used for outgoing connections even if it tries and fails to create a server socket. * Allow Bind to fail if being called before Connect, and the IP is an "any" address (0.0.0.0 or ::), since this bind would have been mostly pointless anyway. * Prevent P2PTransprotChannel from keeping a "backup" candidate pair using an "any address" network; we only want this for actual networks. BUG=webrtc:7798 Review-Url: https://codereview.webrtc.org/2936553003 Cr-Commit-Position: refs/heads/master@{#18578}
This commit is contained in:
parent
1d560e1b9a
commit
1ee2125909
@ -24,6 +24,14 @@ class FirewallSocket : public AsyncSocketAdapter {
|
||||
: AsyncSocketAdapter(socket), server_(server), type_(type) {
|
||||
}
|
||||
|
||||
int Bind(const SocketAddress& addr) override {
|
||||
if (!server_->IsBindableIp(addr.ipaddr())) {
|
||||
SetError(EINVAL);
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
return AsyncSocketAdapter::Bind(addr);
|
||||
}
|
||||
|
||||
int Connect(const SocketAddress& addr) override {
|
||||
if (type_ == SOCK_STREAM) {
|
||||
if (!server_->Check(FP_TCP, GetLocalAddress(), addr)) {
|
||||
@ -176,6 +184,16 @@ bool FirewallSocketServer::Check(FirewallProtocol p,
|
||||
return true;
|
||||
}
|
||||
|
||||
void FirewallSocketServer::SetUnbindableIps(
|
||||
const std::vector<rtc::IPAddress>& unbindable_ips) {
|
||||
unbindable_ips_ = unbindable_ips;
|
||||
}
|
||||
|
||||
bool FirewallSocketServer::IsBindableIp(const rtc::IPAddress& ip) {
|
||||
return std::find(unbindable_ips_.begin(), unbindable_ips_.end(), ip) ==
|
||||
unbindable_ips_.end();
|
||||
}
|
||||
|
||||
Socket* FirewallSocketServer::CreateSocket(int type) {
|
||||
return CreateSocket(AF_INET, type);
|
||||
}
|
||||
|
||||
@ -58,6 +58,16 @@ class FirewallSocketServer : public SocketServer {
|
||||
bool Check(FirewallProtocol p,
|
||||
const SocketAddress& src, const SocketAddress& dst);
|
||||
|
||||
// Set the IP addresses for which Bind will fail. By default this list is
|
||||
// empty. This can be used to simulate a real OS that refuses to bind to
|
||||
// addresses under various circumstances.
|
||||
//
|
||||
// No matter how many addresses are added (including INADDR_ANY), the server
|
||||
// will still allow creating outgoing TCP connections, since they don't
|
||||
// require explicitly binding a socket.
|
||||
void SetUnbindableIps(const std::vector<rtc::IPAddress>& unbindable_ips);
|
||||
bool IsBindableIp(const rtc::IPAddress& ip);
|
||||
|
||||
Socket* CreateSocket(int type) override;
|
||||
Socket* CreateSocket(int family, int type) override;
|
||||
|
||||
@ -83,6 +93,7 @@ class FirewallSocketServer : public SocketServer {
|
||||
SocketAddress dst;
|
||||
};
|
||||
std::vector<Rule> rules_;
|
||||
std::vector<rtc::IPAddress> unbindable_ips_;
|
||||
bool should_delete_server_;
|
||||
bool udp_sockets_enabled_;
|
||||
bool tcp_sockets_enabled_;
|
||||
|
||||
@ -97,29 +97,20 @@ class NATSocket : public AsyncSocket, public sigslot::has_slots<> {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int result;
|
||||
socket_ = sf_->CreateInternalSocket(family_, type_, addr, &server_addr_);
|
||||
result = (socket_) ? socket_->Bind(addr) : -1;
|
||||
if (result >= 0) {
|
||||
socket_->SignalConnectEvent.connect(this, &NATSocket::OnConnectEvent);
|
||||
socket_->SignalReadEvent.connect(this, &NATSocket::OnReadEvent);
|
||||
socket_->SignalWriteEvent.connect(this, &NATSocket::OnWriteEvent);
|
||||
socket_->SignalCloseEvent.connect(this, &NATSocket::OnCloseEvent);
|
||||
} else {
|
||||
server_addr_.Clear();
|
||||
delete socket_;
|
||||
socket_ = nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
return BindInternal(addr);
|
||||
}
|
||||
|
||||
int Connect(const SocketAddress& addr) override {
|
||||
if (!socket_) { // socket must be bound, for now
|
||||
return -1;
|
||||
int result = 0;
|
||||
// If we're not already bound (meaning |socket_| is null), bind to ANY
|
||||
// address.
|
||||
if (!socket_) {
|
||||
result = BindInternal(SocketAddress(GetAnyIP(family_), 0));
|
||||
if (result < 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
if (type_ == SOCK_STREAM) {
|
||||
result = socket_->Connect(server_addr_.IsNil() ? addr : server_addr_);
|
||||
} else {
|
||||
@ -225,8 +216,16 @@ class NATSocket : public AsyncSocket, public sigslot::has_slots<> {
|
||||
AsyncSocket* Accept(SocketAddress* paddr) override {
|
||||
return socket_->Accept(paddr);
|
||||
}
|
||||
int GetError() const override { return socket_->GetError(); }
|
||||
void SetError(int error) override { socket_->SetError(error); }
|
||||
int GetError() const override {
|
||||
return socket_ ? socket_->GetError() : error_;
|
||||
}
|
||||
void SetError(int error) override {
|
||||
if (socket_) {
|
||||
socket_->SetError(error);
|
||||
} else {
|
||||
error_ = error;
|
||||
}
|
||||
}
|
||||
ConnState GetState() const override {
|
||||
return connected_ ? CS_CONNECTED : CS_CLOSED;
|
||||
}
|
||||
@ -266,6 +265,26 @@ class NATSocket : public AsyncSocket, public sigslot::has_slots<> {
|
||||
}
|
||||
|
||||
private:
|
||||
int BindInternal(const SocketAddress& addr) {
|
||||
RTC_DCHECK(!socket_);
|
||||
|
||||
int result;
|
||||
socket_ = sf_->CreateInternalSocket(family_, type_, addr, &server_addr_);
|
||||
result = (socket_) ? socket_->Bind(addr) : -1;
|
||||
if (result >= 0) {
|
||||
socket_->SignalConnectEvent.connect(this, &NATSocket::OnConnectEvent);
|
||||
socket_->SignalReadEvent.connect(this, &NATSocket::OnReadEvent);
|
||||
socket_->SignalWriteEvent.connect(this, &NATSocket::OnWriteEvent);
|
||||
socket_->SignalCloseEvent.connect(this, &NATSocket::OnCloseEvent);
|
||||
} else {
|
||||
server_addr_.Clear();
|
||||
delete socket_;
|
||||
socket_ = nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Makes sure the buffer is at least the given size.
|
||||
void Grow(size_t new_size) {
|
||||
if (size_ < new_size) {
|
||||
@ -302,6 +321,8 @@ class NATSocket : public AsyncSocket, public sigslot::has_slots<> {
|
||||
SocketAddress remote_addr_;
|
||||
SocketAddress server_addr_; // address of the NAT server
|
||||
AsyncSocket* socket_;
|
||||
// Need to hold error in case it occurs before the socket is created.
|
||||
int error_ = 0;
|
||||
char* buf_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
@ -118,6 +118,10 @@ class NetworkManager : public DefaultLocalAddressProvider {
|
||||
// IP address. (i.e. INADDR_ANY for IPv4 or in6addr_any for IPv6). This is
|
||||
// useful as binding to such interfaces allow default routing behavior like
|
||||
// http traffic.
|
||||
//
|
||||
// This method appends the "any address" networks to the list, such that this
|
||||
// can optionally be called after GetNetworks.
|
||||
//
|
||||
// TODO(guoweis): remove this body when chromium implements this.
|
||||
virtual void GetAnyAddressNetworks(NetworkList* networks) {}
|
||||
|
||||
|
||||
@ -114,10 +114,17 @@ AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket(
|
||||
}
|
||||
|
||||
if (BindSocket(socket, local_address, 0, 0) < 0) {
|
||||
LOG(LS_ERROR) << "TCP bind failed with error "
|
||||
<< socket->GetError();
|
||||
delete socket;
|
||||
return NULL;
|
||||
// Allow BindSocket to fail if we're binding to the ANY address, since this
|
||||
// is mostly redundant in the first place. The socket will be bound when we
|
||||
// call Connect() instead.
|
||||
if (local_address.IsAnyIP()) {
|
||||
LOG(LS_WARNING) << "TCP bind failed with error " << socket->GetError()
|
||||
<< "; ignoring since socket is using 'any' address.";
|
||||
} else {
|
||||
LOG(LS_ERROR) << "TCP bind failed with error " << socket->GetError();
|
||||
delete socket;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// If using a proxy, wrap the socket in a proxy socket.
|
||||
|
||||
@ -1353,15 +1353,26 @@ void P2PTransportChannel::PruneConnections() {
|
||||
// switch. If |best_conn_on_network| is not connected, we may be reconnecting
|
||||
// a TCP connection and should not prune connections in this network.
|
||||
// See the big comment in CompareConnectionStates.
|
||||
//
|
||||
// An exception is made for connections on an "any address" network, meaning
|
||||
// not bound to any specific network interface. We don't want to keep one of
|
||||
// these alive as a backup, since it could be using the same network
|
||||
// interface as the higher-priority, selected candidate pair.
|
||||
auto best_connection_by_network = GetBestConnectionByNetwork();
|
||||
for (Connection* conn : connections_) {
|
||||
// Do not prune connections if the current best connection on this network
|
||||
// is weak. Otherwise, it may delete connections prematurely.
|
||||
Connection* best_conn_on_network =
|
||||
best_connection_by_network[conn->port()->Network()];
|
||||
if (best_conn_on_network && conn != best_conn_on_network &&
|
||||
!best_conn_on_network->weak() &&
|
||||
CompareConnectionCandidates(best_conn_on_network, conn) >= 0) {
|
||||
Connection* best_conn = selected_connection_;
|
||||
if (!rtc::IPIsAny(conn->port()->Network()->ip())) {
|
||||
// If the connection is bound to a specific network interface (not an
|
||||
// "any address" network), compare it against the best connection for
|
||||
// that network interface rather than the best connection overall. This
|
||||
// ensures that at least one connection per network will be left
|
||||
// unpruned.
|
||||
best_conn = best_connection_by_network[conn->port()->Network()];
|
||||
}
|
||||
// Do not prune connections if the connection being compared against is
|
||||
// weak. Otherwise, it may delete connections prematurely.
|
||||
if (best_conn && conn != best_conn && !best_conn->weak() &&
|
||||
CompareConnectionCandidates(best_conn, conn) >= 0) {
|
||||
conn->Prune();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1057,6 +1057,7 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
RTC_NOTREACHED();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1566,6 +1567,32 @@ TEST_F(P2PTransportChannelTest, IncomingOnlyOpen) {
|
||||
DestroyChannels();
|
||||
}
|
||||
|
||||
// Test that two peers can connect when one can only make outgoing TCP
|
||||
// connections. This has been observed in some scenarios involving
|
||||
// VPNs/firewalls.
|
||||
TEST_F(P2PTransportChannelTest, CanOnlyMakeOutgoingTcpConnections) {
|
||||
// The PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS flag is required if the
|
||||
// application needs this use case to work, since the application must accept
|
||||
// the tradeoff that more candidates need to be allocated.
|
||||
//
|
||||
// TODO(deadbeef): Later, make this flag the default, and do more elegant
|
||||
// things to ensure extra candidates don't waste resources?
|
||||
ConfigureEndpoints(
|
||||
OPEN, OPEN,
|
||||
kDefaultPortAllocatorFlags | PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS,
|
||||
kDefaultPortAllocatorFlags);
|
||||
// In order to simulate nothing working but outgoing TCP connections, prevent
|
||||
// the endpoint from binding to its interface's address as well as the
|
||||
// "any" addresses. It can then only make a connection by using "Connect()".
|
||||
fw()->SetUnbindableIps({rtc::GetAnyIP(AF_INET), rtc::GetAnyIP(AF_INET6),
|
||||
kPublicAddrs[0].ipaddr()});
|
||||
CreateChannels();
|
||||
// Expect a "prflx" candidate on the side that can only make outgoing
|
||||
// connections, endpoint 0.
|
||||
Test(kPrflxTcpToLocalTcp);
|
||||
DestroyChannels();
|
||||
}
|
||||
|
||||
TEST_F(P2PTransportChannelTest, TestTcpConnectionsFromActiveToPassive) {
|
||||
rtc::ScopedFakeClock clock;
|
||||
AddAddress(0, kPublicAddrs[0]);
|
||||
|
||||
@ -77,6 +77,15 @@ enum {
|
||||
|
||||
// When specified, do not collect IPv6 ICE candidates on Wi-Fi.
|
||||
PORTALLOCATOR_ENABLE_IPV6_ON_WIFI = 0x4000,
|
||||
|
||||
// When this flag is set, ports not bound to any specific network interface
|
||||
// will be used, in addition to normal ports bound to the enumerated
|
||||
// interfaces. Without this flag, these "any address" ports would only be
|
||||
// used when network enumeration fails or is disabled. But under certain
|
||||
// conditions, these ports may succeed where others fail, so they may allow
|
||||
// the application to work in a wider variety of environments, at the expense
|
||||
// of having to allocate additional candidates.
|
||||
PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS = 0x8000,
|
||||
};
|
||||
|
||||
// Defines various reasons that have caused ICE regathering.
|
||||
|
||||
@ -96,23 +96,9 @@ TCPPort::TCPPort(rtc::Thread* thread,
|
||||
error_(0) {
|
||||
// TODO(mallinath) - Set preference value as per RFC 6544.
|
||||
// http://b/issue?id=7141794
|
||||
}
|
||||
|
||||
bool TCPPort::Init() {
|
||||
if (allow_listen_) {
|
||||
// Treat failure to create or bind a TCP socket as fatal. This
|
||||
// should never happen.
|
||||
socket_ = socket_factory()->CreateServerTcpSocket(
|
||||
rtc::SocketAddress(ip(), 0), min_port(), max_port(),
|
||||
false /* ssl */);
|
||||
if (!socket_) {
|
||||
LOG_J(LS_ERROR, this) << "TCP socket creation failed.";
|
||||
return false;
|
||||
}
|
||||
socket_->SignalNewConnection.connect(this, &TCPPort::OnNewConnection);
|
||||
socket_->SignalAddressReady.connect(this, &TCPPort::OnAddressReady);
|
||||
TryCreateServerSocket();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TCPPort::~TCPPort() {
|
||||
@ -274,6 +260,18 @@ void TCPPort::OnNewConnection(rtc::AsyncPacketSocket* socket,
|
||||
incoming_.push_back(incoming);
|
||||
}
|
||||
|
||||
void TCPPort::TryCreateServerSocket() {
|
||||
socket_ = socket_factory()->CreateServerTcpSocket(
|
||||
rtc::SocketAddress(ip(), 0), min_port(), max_port(), false /* ssl */);
|
||||
if (!socket_) {
|
||||
LOG_J(LS_WARNING, this)
|
||||
<< "TCP server socket creation failed; continuing anyway.";
|
||||
return;
|
||||
}
|
||||
socket_->SignalNewConnection.connect(this, &TCPPort::OnNewConnection);
|
||||
socket_->SignalAddressReady.connect(this, &TCPPort::OnAddressReady);
|
||||
}
|
||||
|
||||
rtc::AsyncPacketSocket* TCPPort::GetIncoming(
|
||||
const rtc::SocketAddress& addr, bool remove) {
|
||||
rtc::AsyncPacketSocket* socket = NULL;
|
||||
|
||||
@ -39,13 +39,8 @@ class TCPPort : public Port {
|
||||
const std::string& username,
|
||||
const std::string& password,
|
||||
bool allow_listen) {
|
||||
TCPPort* port = new TCPPort(thread, factory, network, ip, min_port,
|
||||
max_port, username, password, allow_listen);
|
||||
if (!port->Init()) {
|
||||
delete port;
|
||||
port = NULL;
|
||||
}
|
||||
return port;
|
||||
return new TCPPort(thread, factory, network, ip, min_port, max_port,
|
||||
username, password, allow_listen);
|
||||
}
|
||||
~TCPPort() override;
|
||||
|
||||
@ -73,7 +68,6 @@ class TCPPort : public Port {
|
||||
const std::string& username,
|
||||
const std::string& password,
|
||||
bool allow_listen);
|
||||
bool Init();
|
||||
|
||||
// Handles sending using the local TCP socket.
|
||||
int SendTo(const void* data,
|
||||
@ -92,6 +86,8 @@ class TCPPort : public Port {
|
||||
rtc::AsyncPacketSocket* socket;
|
||||
};
|
||||
|
||||
void TryCreateServerSocket();
|
||||
|
||||
rtc::AsyncPacketSocket* GetIncoming(
|
||||
const rtc::SocketAddress& addr, bool remove = false);
|
||||
|
||||
|
||||
@ -555,8 +555,9 @@ std::vector<rtc::Network*> BasicPortAllocatorSession::GetNetworks() {
|
||||
network_manager->GetNetworks(&networks);
|
||||
// If network enumeration fails, use the ANY address as a fallback, so we
|
||||
// can at least try gathering candidates using the default route chosen by
|
||||
// the OS.
|
||||
if (networks.empty()) {
|
||||
// the OS. Or, if the PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS flag is
|
||||
// set, we'll use ANY address candidates either way.
|
||||
if (networks.empty() || flags() & PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS) {
|
||||
network_manager->GetAnyAddressNetworks(&networks);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user