Skip to content

Commit

Permalink
fix(serve): check all network interfaces for an available port
Browse files Browse the repository at this point in the history
  • Loading branch information
imhoffd committed Jul 31, 2018
1 parent 474788b commit 30fd6ef
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 9 deletions.
60 changes: 57 additions & 3 deletions packages/@ionic/cli-framework/src/utils/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@ const debug = Debug('ionic:cli-framework:utils:network');

export const ERROR_NETWORK_ADDRESS_NOT_AVAIL = 'NETWORK_ADDRESS_NOT_AVAIL';

export const DEFAULT_ADDRESSES: ReadonlyArray<string> = getDefaultAddresses();

function getDefaultAddresses(): string[] {
const addresses: string[] = ['0.0.0.0'];

try {
const networkInterfaces = os.networkInterfaces();

for (const device of Object.keys(networkInterfaces)) {
const networkInterface = networkInterfaces[device];

addresses.push(...networkInterface.map(i => i.address));
}
} catch (e) {
// swallow
}

return addresses;
}

export function getExternalIPv4Interfaces(): NetworkInterface[] {
const networkInterfaces = os.networkInterfaces();
const devices: NetworkInterface[] = [];
Expand All @@ -26,9 +46,17 @@ export function getExternalIPv4Interfaces(): NetworkInterface[] {
return devices;
}

export async function findClosestOpenPort(port: number, host?: string): Promise<number> {
/**
* Attempts to locate a port number starting from `port` and incrementing by 1.
*
* This function looks through all internal network interfaces, attempting
* host/port combinations until it finds an available port on all interfaces.
*
* @param port The port at which to start checking.
*/
export async function findClosestOpenPort(port: number): Promise<number> {
async function t(portToCheck: number): Promise<number> {
if (await isPortAvailable(portToCheck, host)) {
if (await isPortAvailable(portToCheck)) {
return portToCheck;
}

Expand All @@ -38,7 +66,33 @@ export async function findClosestOpenPort(port: number, host?: string): Promise<
return t(port);
}

export async function isPortAvailable(port: number, host?: string): Promise<boolean> {
/**
* Checks whether a port is open or closed.
*
* This function looks through all internal network interfaces, checking
* whether all host/port combinations are open. If one or more is not, the port
* is not available.
*/
export async function isPortAvailable(port: number): Promise<boolean> {
let available = true;

for (const address of DEFAULT_ADDRESSES) {
try {
debug('checking for open port on %s:%d', address, port);
available = await isPortAvailableForHost(address, port);

if (!available) {
return false;
}
} catch (e) {
debug('error while checking %s:%d: %o', address, port, e);
}
}

return available;
}

export function isPortAvailableForHost(host: string, port: number): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
const tester = net.createServer()
.once('error', (err: any) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/@ionic/cli-utils/src/lib/project/angular/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ ${chalk.cyan('[1]')}: ${chalk.bold('https://github.com/angular/angular-cli/wiki/
async serveProject(options: AngularServeOptions): Promise<ServeDetails> {
const [ externalIP, availableInterfaces ] = await this.selectExternalIP(options);

const ngPort = options.port = await findClosestOpenPort(options.port, '0.0.0.0');
const ngPort = options.port = await findClosestOpenPort(options.port);
const { program } = await this.serveCommandWrapper(options);

const interval = setInterval(() => {
Expand Down
6 changes: 3 additions & 3 deletions packages/@ionic/cli-utils/src/lib/project/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export interface Ports {
export async function findOpenIonicPorts(address: string, ports: Ports): Promise<Ports> {
try {
const [ port, livereloadPort, notificationPort ] = await Promise.all([
findClosestOpenPort(ports.port, '0.0.0.0'),
findClosestOpenPort(ports.livereloadPort, '0.0.0.0'),
findClosestOpenPort(ports.notificationPort, '0.0.0.0'),
findClosestOpenPort(ports.port),
findClosestOpenPort(ports.livereloadPort),
findClosestOpenPort(ports.notificationPort),
]);

if (ports.port !== port) {
Expand Down
4 changes: 2 additions & 2 deletions packages/@ionic/cli-utils/src/lib/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ export abstract class ServeRunner<T extends ServeOptions> extends EventEmitter i
const { port } = details;

// the comm server always binds to 0.0.0.0 to target every possible interface
const commPort = await findClosestOpenPort(DEFAULT_DEVAPP_COMM_PORT, '0.0.0.0');
const commPort = await findClosestOpenPort(DEFAULT_DEVAPP_COMM_PORT);

return { port, commPort, interfaces };
}
Expand Down Expand Up @@ -317,7 +317,7 @@ export abstract class ServeRunner<T extends ServeOptions> extends EventEmitter i
const labDetails: LabServeDetails = {
protocol: options.ssl ? 'https' : 'http',
address: options.labHost,
port: await findClosestOpenPort(options.labPort, options.labHost),
port: await findClosestOpenPort(options.labPort),
};

if (options.ssl) {
Expand Down

0 comments on commit 30fd6ef

Please sign in to comment.