Renamed the "bindAddress" setting to "bindAddresses". It is now an array of strings, allowing for multiple addresses to be specified in order of importance. Each address is tried in turn until one successfully binds, trying IPv6 first by default and then IPv4. This fixes an issue where the core failed to start on IPv4-only networks unless the bindAddress was manually overridden in Settings.json. This was reported in issue #25, and is also quite commonly reported by new users on Discord. It will now start correctly on both IPv4-only and IPv6-only networks, because it will automatically fall back to an IPv4 bindAddress if the IPv6 one fails to bind.

This commit is contained in:
CalDescent 2021-02-21 11:39:25 +00:00
parent 3920933fc7
commit 45a9654d5e
3 changed files with 49 additions and 23 deletions

View File

@ -46,6 +46,7 @@ import org.qortal.api.websocket.ChatMessagesWebSocket;
import org.qortal.api.websocket.PresenceWebSocket; import org.qortal.api.websocket.PresenceWebSocket;
import org.qortal.api.websocket.TradeBotWebSocket; import org.qortal.api.websocket.TradeBotWebSocket;
import org.qortal.api.websocket.TradeOffersWebSocket; import org.qortal.api.websocket.TradeOffersWebSocket;
import org.qortal.network.Network;
import org.qortal.settings.Settings; import org.qortal.settings.Settings;
public class ApiService { public class ApiService {
@ -118,13 +119,13 @@ public class ApiService {
ServerConnector portUnifiedConnector = new ServerConnector(this.server, ServerConnector portUnifiedConnector = new ServerConnector(this.server,
new DetectorConnectionFactory(sslConnectionFactory), new DetectorConnectionFactory(sslConnectionFactory),
httpConnectionFactory); httpConnectionFactory);
portUnifiedConnector.setHost(Settings.getInstance().getBindAddress()); portUnifiedConnector.setHost(Network.getInstance().getBindAddress());
portUnifiedConnector.setPort(Settings.getInstance().getApiPort()); portUnifiedConnector.setPort(Settings.getInstance().getApiPort());
this.server.addConnector(portUnifiedConnector); this.server.addConnector(portUnifiedConnector);
} else { } else {
// Non-SSL // Non-SSL
InetAddress bindAddr = InetAddress.getByName(Settings.getInstance().getBindAddress()); InetAddress bindAddr = InetAddress.getByName(Network.getInstance().getBindAddress());
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getApiPort()); InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getApiPort());
this.server = new Server(endpoint); this.server = new Server(endpoint);
} }

View File

@ -108,6 +108,8 @@ public class Network {
private ServerSocketChannel serverChannel; private ServerSocketChannel serverChannel;
private Iterator<SelectionKey> channelIterator = null; private Iterator<SelectionKey> channelIterator = null;
private String bindAddress = null;
// volatile because value is updated inside any one of the EPC threads // volatile because value is updated inside any one of the EPC threads
private volatile long nextConnectTaskTimestamp = 0L; // ms - try first connect once NTP syncs private volatile long nextConnectTaskTimestamp = 0L; // ms - try first connect once NTP syncs
@ -138,9 +140,16 @@ public class Network {
// Grab P2P port from settings // Grab P2P port from settings
int listenPort = Settings.getInstance().getListenPort(); int listenPort = Settings.getInstance().getListenPort();
// Grab P2P bind address from settings // Grab P2P bind addresses from settings
String[] bindAddresses = Settings.getInstance().getBindAddresses();
for (int i=0; i<bindAddresses.length; i++) {
String bindAddress = bindAddresses[i];
try { try {
InetAddress bindAddr = InetAddress.getByName(Settings.getInstance().getBindAddress()); LOGGER.info(String.format("Binding to address %s", bindAddress));
InetAddress bindAddr = InetAddress.getByName(bindAddress);
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, listenPort); InetSocketAddress endpoint = new InetSocketAddress(bindAddr, listenPort);
channelSelector = Selector.open(); channelSelector = Selector.open();
@ -151,13 +160,23 @@ public class Network {
serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
serverChannel.bind(endpoint, LISTEN_BACKLOG); serverChannel.bind(endpoint, LISTEN_BACKLOG);
serverChannel.register(channelSelector, SelectionKey.OP_ACCEPT); serverChannel.register(channelSelector, SelectionKey.OP_ACCEPT);
this.bindAddress = bindAddress; // Store the selected address, so that it can be used by other parts of the app
break; // We don't want to bind to more than one address
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
LOGGER.error(String.format("Can't bind listen socket to address %s", Settings.getInstance().getBindAddress())); LOGGER.error(String.format("Can't bind listen socket to address %s", bindAddress));
if (i == bindAddresses.length-1) { // Only throw an exception if all addresses have been tried
throw new IOException("Can't bind listen socket to address", e); throw new IOException("Can't bind listen socket to address", e);
}
} catch (IOException e) { } catch (IOException e) {
LOGGER.error(String.format("Can't create listen socket: %s", e.getMessage())); LOGGER.error(String.format("Can't create listen socket: %s", e.getMessage()));
if (i == bindAddresses.length-1) { // Only throw an exception if all addresses have been tried
throw new IOException("Can't create listen socket", e); throw new IOException("Can't create listen socket", e);
} }
}
}
// Load all known peers from repository // Load all known peers from repository
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
@ -179,6 +198,10 @@ public class Network {
return instance; return instance;
} }
public String getBindAddress() {
return this.bindAddress;
}
public byte[] getMessageMagic() { public byte[] getMessageMagic() {
return Settings.getInstance().isTestNet() ? TESTNET_MESSAGE_MAGIC : MAINNET_MESSAGE_MAGIC; return Settings.getInstance().isTestNet() ? TESTNET_MESSAGE_MAGIC : MAINNET_MESSAGE_MAGIC;
} }

View File

@ -46,8 +46,10 @@ public class Settings {
// General // General
private String localeLang = Locale.getDefault().getLanguage(); private String localeLang = Locale.getDefault().getLanguage();
// Common to all networking (API/P2P) // Common to all networking (API/P2P). They are tried in order until one successfully binds.
private String bindAddress = "::"; // Use IPv6 wildcard to listen on all local addresses private String[] bindAddresses = new String[] {
"::", "0.0.0.0"
};
// UI servers // UI servers
private int uiPort = 12388; private int uiPort = 12388;
@ -376,8 +378,8 @@ public class Settings {
return this.isTestNet ? TESTNET_LISTEN_PORT : MAINNET_LISTEN_PORT; return this.isTestNet ? TESTNET_LISTEN_PORT : MAINNET_LISTEN_PORT;
} }
public String getBindAddress() { public String[] getBindAddresses() {
return this.bindAddress; return this.bindAddresses;
} }
public int getMinBlockchainPeers() { public int getMinBlockchainPeers() {