mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-03-21 11:35:18 +00:00
Merge pull request #820 from balena-io/typescript-network
Convert network module to typescript
This commit is contained in:
commit
3e6e2fc9a2
107
package-lock.json
generated
107
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "balena-supervisor",
|
||||
"version": "8.4.1",
|
||||
"version": "8.4.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -1125,6 +1125,27 @@
|
||||
"type-is": "~1.6.16"
|
||||
}
|
||||
},
|
||||
"bonjour": {
|
||||
"version": "git+https://github.com/resin-io/bonjour.git#e018851dc823b4b3f670f658f71d0c1c7f3e637c",
|
||||
"from": "git+https://github.com/resin-io/bonjour.git#fixed-mdns",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-flatten": "^2.1.0",
|
||||
"deep-equal": "^1.0.1",
|
||||
"dns-equal": "^1.0.0",
|
||||
"dns-txt": "^2.0.2",
|
||||
"multicast-dns": "git+https://github.com/resin-io-modules/multicast-dns.git#a15c63464eb43e8925b187ed5cb9de6892e8aacc",
|
||||
"multicast-dns-service-types": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"array-flatten": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz",
|
||||
"integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@ -1274,6 +1295,12 @@
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
|
||||
"dev": true
|
||||
},
|
||||
"buffer-indexof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
|
||||
"integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==",
|
||||
"dev": true
|
||||
},
|
||||
"buffer-xor": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||
@ -2159,6 +2186,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"deep-equal": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
|
||||
"integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
|
||||
"dev": true
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
@ -2402,6 +2435,31 @@
|
||||
"path-type": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"dns-equal": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
|
||||
"integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
|
||||
"dev": true
|
||||
},
|
||||
"dns-packet": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
|
||||
"integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ip": "^1.1.0",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"dns-txt": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
|
||||
"integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-indexof": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"docker-delta": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/docker-delta/-/docker-delta-2.2.2.tgz",
|
||||
@ -6471,6 +6529,21 @@
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
},
|
||||
"multicast-dns": {
|
||||
"version": "git+https://github.com/resin-io-modules/multicast-dns.git#a15c63464eb43e8925b187ed5cb9de6892e8aacc",
|
||||
"from": "git+https://github.com/resin-io-modules/multicast-dns.git#listen-on-all-interfaces",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dns-packet": "^1.0.1",
|
||||
"thunky": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"multicast-dns-service-types": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
|
||||
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
|
||||
"dev": true
|
||||
},
|
||||
"mute-stream": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
|
||||
@ -6565,9 +6638,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"network-checker": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "http://registry.npmjs.org/network-checker/-/network-checker-0.0.6.tgz",
|
||||
"integrity": "sha1-165Y5NuVJE0tJnbM1LIlJRf5Mq4=",
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/network-checker/-/network-checker-0.1.1.tgz",
|
||||
"integrity": "sha512-dc/LiwC0pp37njpe8TA+oRa5BWkH8+WyFVY0aeuDZqMLHq4kaLHqWJowEfKBI7KT39vmTapWJN0SoHxhx6aL4A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird": "^3.0.0",
|
||||
@ -8193,27 +8266,9 @@
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bluebird": "^3.0.0",
|
||||
"bonjour": "git+https://github.com/resin-io/bonjour.git#e018851dc823b4b3f670f658f71d0c1c7f3e637c",
|
||||
"ip": "^1.1.4",
|
||||
"lodash": "^4.17.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"array-flatten": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz",
|
||||
"integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY="
|
||||
},
|
||||
"bonjour": {
|
||||
"version": "git+https://github.com/resin-io/bonjour.git#e018851dc823b4b3f670f658f71d0c1c7f3e637c",
|
||||
"from": "git+https://github.com/resin-io/bonjour.git#e018851dc823b4b3f670f658f71d0c1c7f3e637c",
|
||||
"requires": {
|
||||
"array-flatten": "^2.1.0",
|
||||
"deep-equal": "^1.0.1",
|
||||
"dns-equal": "^1.0.0",
|
||||
"dns-txt": "^2.0.2",
|
||||
"multicast-dns": "git+https://github.com/resin-io-modules/multicast-dns.git#a15c63464eb43e8925b187ed5cb9de6892e8aacc",
|
||||
"multicast-dns-service-types": "^1.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"resin-errors": {
|
||||
@ -10301,6 +10356,12 @@
|
||||
"integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=",
|
||||
"dev": true
|
||||
},
|
||||
"thunky": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "http://registry.npmjs.org/thunky/-/thunky-0.1.0.tgz",
|
||||
"integrity": "sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=",
|
||||
"dev": true
|
||||
},
|
||||
"tildify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tildify/-/tildify-1.0.0.tgz",
|
||||
|
@ -70,7 +70,7 @@
|
||||
"mocha": "^5.1.1",
|
||||
"mochainon": "^2.0.0",
|
||||
"mz": "^2.7.0",
|
||||
"network-checker": "~0.0.5",
|
||||
"network-checker": "^0.1.1",
|
||||
"pinejs-client": "^2.4.0",
|
||||
"prettier": "^1.14.3",
|
||||
"register-coffee-coverage": "0.0.1",
|
||||
|
@ -1,107 +0,0 @@
|
||||
Promise = require 'bluebird'
|
||||
_ = require 'lodash'
|
||||
url = require 'url'
|
||||
networkCheck = require 'network-checker'
|
||||
os = require 'os'
|
||||
fs = Promise.promisifyAll(require('fs'))
|
||||
|
||||
constants = require './lib/constants'
|
||||
{ checkTruthy } = require './lib/validation'
|
||||
blink = require './lib/blink'
|
||||
{ EEXIST } = require './lib/errors'
|
||||
|
||||
networkPattern =
|
||||
blinks: 4
|
||||
pause: 1000
|
||||
|
||||
pauseConnectivityCheck = false
|
||||
enableConnectivityCheck = true
|
||||
|
||||
# options: An object of net.connect options, with the addition of:
|
||||
# timeout: 10s
|
||||
checkHost = (options) ->
|
||||
if !enableConnectivityCheck or pauseConnectivityCheck
|
||||
return true
|
||||
else
|
||||
return networkCheck.checkHost(options)
|
||||
|
||||
# Custom monitor that uses checkHost function above.
|
||||
customMonitor = (options, fn) ->
|
||||
networkCheck.monitor(checkHost, options, fn)
|
||||
|
||||
# enable: A Boolean to enable/disable the connectivity checks
|
||||
exports.enableCheck = enableCheck = (enable) ->
|
||||
enableConnectivityCheck = enable
|
||||
|
||||
# Call back for inotify triggered when the VPN status is changed.
|
||||
vpnStatusInotifyCallback = ->
|
||||
fs.lstatAsync(constants.vpnStatusPath + '/active')
|
||||
.then ->
|
||||
pauseConnectivityCheck = true
|
||||
.catch ->
|
||||
pauseConnectivityCheck = false
|
||||
|
||||
exports.startConnectivityCheck = _.once (apiEndpoint, enable, onChangeCallback) ->
|
||||
exports.enableConnectivityCheck(enable)
|
||||
if !apiEndpoint?
|
||||
console.log('No API endpoint specified, skipping connectivity check')
|
||||
return
|
||||
parsedUrl = url.parse(apiEndpoint)
|
||||
fs.mkdirAsync(constants.vpnStatusPath)
|
||||
.catch EEXIST, (err) ->
|
||||
console.log('VPN status path exists.')
|
||||
.then ->
|
||||
fs.watch(constants.vpnStatusPath, vpnStatusInotifyCallback)
|
||||
|
||||
# Manually trigger the call back to detect cases when VPN was switched on before the supervisor starts.
|
||||
vpnStatusInotifyCallback() if enable
|
||||
customMonitor
|
||||
host: parsedUrl.hostname
|
||||
port: parsedUrl.port ? (if parsedUrl.protocol is 'https:' then 443 else 80)
|
||||
interval: 10 * 1000
|
||||
(connected) ->
|
||||
onChangeCallback?(connected)
|
||||
if connected
|
||||
console.log('Internet Connectivity: OK')
|
||||
blink.pattern.stop()
|
||||
else
|
||||
console.log('Waiting for connectivity...')
|
||||
blink.pattern.start(networkPattern)
|
||||
|
||||
# Callback function to enable/disable tcp pings
|
||||
exports.enableConnectivityCheck = (val) ->
|
||||
enabled = checkTruthy(val) ? true
|
||||
enableCheck(enabled)
|
||||
console.log("Connectivity check enabled: #{enabled}")
|
||||
|
||||
exports.connectivityCheckEnabled = Promise.method ->
|
||||
return enableConnectivityCheck
|
||||
|
||||
exports.getIPAddresses = ->
|
||||
# We get IP addresses but ignore:
|
||||
# - docker and balena bridges (docker0, docker1, balena0, etc)
|
||||
# - legacy rce bridges (rce0, etc)
|
||||
# - tun interfaces like the legacy vpn
|
||||
# - the resin VPN interface (resin-vpn)
|
||||
# - loopback interface (lo)
|
||||
# - the bridge for dnsmasq (resin-dns)
|
||||
# - the docker network for the supervisor API (supervisor0)
|
||||
# - custom docker network bridges (br- + 12 hex characters)
|
||||
_.flatten(_.map(_.omitBy(os.networkInterfaces(), (interfaceFields, interfaceName) ->
|
||||
/^(?:balena|docker|rce|tun)[0-9]+|tun[0-9]+|resin-vpn|lo|resin-dns|supervisor0|balena-redsocks|resin-redsocks|br-[0-9a-f]{12}$/.test(interfaceName))
|
||||
, (validInterfaces) ->
|
||||
_.map(_.pickBy(validInterfaces, family: 'IPv4'), 'address'))
|
||||
)
|
||||
|
||||
exports.startIPAddressUpdate = do ->
|
||||
_lastIPValues = null
|
||||
return (callback, interval) ->
|
||||
getAndReportIP = ->
|
||||
ips = exports.getIPAddresses()
|
||||
if !_.isEmpty(_.xor(ips , _lastIPValues))
|
||||
_lastIPValues = ips
|
||||
callback(ips)
|
||||
setInterval( ->
|
||||
getAndReportIP()
|
||||
, interval)
|
||||
getAndReportIP()
|
156
src/network.ts
Normal file
156
src/network.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import * as Bluebird from 'bluebird';
|
||||
import * as _ from 'lodash';
|
||||
import { fs } from 'mz';
|
||||
import * as networkCheck from 'network-checker';
|
||||
import * as os from 'os';
|
||||
import * as url from 'url';
|
||||
|
||||
import * as constants from './lib/constants';
|
||||
import { EEXIST } from './lib/errors';
|
||||
import { checkTruthy } from './lib/validation';
|
||||
|
||||
import blink = require('./lib/blink');
|
||||
|
||||
const networkPattern = {
|
||||
blinks: 4,
|
||||
pause: 1000,
|
||||
};
|
||||
|
||||
let isConnectivityCheckPaused = false;
|
||||
let isConnectivityCheckEnabled = true;
|
||||
|
||||
function checkHost(
|
||||
opts: networkCheck.ConnectOptions,
|
||||
): boolean | PromiseLike<boolean> {
|
||||
return (
|
||||
!isConnectivityCheckEnabled ||
|
||||
isConnectivityCheckPaused ||
|
||||
networkCheck.checkHost(opts)
|
||||
);
|
||||
}
|
||||
|
||||
function customMonitor(
|
||||
options: networkCheck.ConnectOptions,
|
||||
fn: networkCheck.MonitorChangeFunction,
|
||||
) {
|
||||
return networkCheck.monitor(checkHost, options, fn);
|
||||
}
|
||||
|
||||
export function enableCheck(enable: boolean) {
|
||||
isConnectivityCheckEnabled = enable;
|
||||
}
|
||||
|
||||
async function vpnStatusInotifyCallback(): Promise<void> {
|
||||
try {
|
||||
await fs.lstat(constants.vpnStatusPath);
|
||||
isConnectivityCheckPaused = true;
|
||||
} catch {
|
||||
isConnectivityCheckPaused = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const startConnectivityCheck = _.once(
|
||||
async (
|
||||
apiEndpoint: string,
|
||||
enable: boolean,
|
||||
onChangeCallback?: networkCheck.MonitorChangeFunction,
|
||||
) => {
|
||||
enableConnectivityCheck(enable);
|
||||
if (!apiEndpoint) {
|
||||
console.log('No API endpoint specified, skipping connectivity check');
|
||||
return;
|
||||
}
|
||||
|
||||
await Bluebird.resolve(fs.mkdir(constants.vpnStatusPath))
|
||||
.catch(EEXIST, () => {
|
||||
console.log('VPN status path exists.');
|
||||
})
|
||||
.then(() => {
|
||||
fs.watch(constants.vpnStatusPath, vpnStatusInotifyCallback);
|
||||
});
|
||||
|
||||
if (enable) {
|
||||
vpnStatusInotifyCallback();
|
||||
}
|
||||
|
||||
const parsedUrl = url.parse(apiEndpoint);
|
||||
const port = parseInt(parsedUrl.port!, 10);
|
||||
|
||||
customMonitor(
|
||||
{
|
||||
host: parsedUrl.hostname,
|
||||
port: port || (parsedUrl.protocol === 'https' ? 443 : 80),
|
||||
path: parsedUrl.path || '/',
|
||||
interval: 10 * 1000,
|
||||
},
|
||||
connected => {
|
||||
if (_.isFunction(onChangeCallback)) {
|
||||
onChangeCallback(connected);
|
||||
}
|
||||
if (connected) {
|
||||
console.log('Internet Connectivity: OK');
|
||||
blink.pattern.stop();
|
||||
} else {
|
||||
console.log('Waiting for connectivity...');
|
||||
blink.pattern.start(networkPattern);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export function enableConnectivityCheck(enable: boolean) {
|
||||
const boolEnable = checkTruthy(enable);
|
||||
enable = boolEnable != null ? boolEnable : true;
|
||||
enableCheck(enable);
|
||||
console.log(`Connectivity check enabled: ${enable}`);
|
||||
}
|
||||
|
||||
export const connectivityCheckEnabled = Bluebird.method(
|
||||
() => isConnectivityCheckEnabled,
|
||||
);
|
||||
|
||||
const IP_REGEX = /^(?:balena|docker|rce|tun)[0-9]+|tun[0-9]+|resin-vpn|lo|resin-dns|supervisor0|balena-redsocks|resin-redsocks|br-[0-9a-f]{12}$/;
|
||||
export function getIPAddresses(): string[] {
|
||||
// We get IP addresses but ignore:
|
||||
// - docker and balena bridges (docker0, docker1, balena0, etc)
|
||||
// - legacy rce bridges (rce0, etc)
|
||||
// - tun interfaces like the legacy vpn
|
||||
// - the resin VPN interface (resin-vpn)
|
||||
// - loopback interface (lo)
|
||||
// - the bridge for dnsmasq (resin-dns)
|
||||
// - the docker network for the supervisor API (supervisor0)
|
||||
// - custom docker network bridges (br- + 12 hex characters)
|
||||
return _(os.networkInterfaces())
|
||||
.omitBy((_interfaceFields, interfaceName) => IP_REGEX.test(interfaceName))
|
||||
.flatMap(validInterfaces => {
|
||||
return _(validInterfaces)
|
||||
.pickBy({ family: 'IPv4' })
|
||||
.map('address')
|
||||
.value();
|
||||
})
|
||||
.value();
|
||||
}
|
||||
|
||||
export function startIPAddressUpdate(): (
|
||||
callback: (ips: string[]) => void,
|
||||
interval: number,
|
||||
) => void {
|
||||
let lastIPValues: string[] | null = null;
|
||||
return (cb, interval) => {
|
||||
const getAndReportIP = () => {
|
||||
const ips = getIPAddresses();
|
||||
if (
|
||||
!_(ips)
|
||||
.xor(lastIPValues)
|
||||
.isEmpty()
|
||||
) {
|
||||
lastIPValues = ips;
|
||||
cb(ips);
|
||||
}
|
||||
};
|
||||
|
||||
setInterval(getAndReportIP, interval);
|
||||
getAndReportIP();
|
||||
};
|
||||
}
|
2
typings/blinking.d.ts
vendored
2
typings/blinking.d.ts
vendored
@ -11,7 +11,7 @@ declare module 'blinking' {
|
||||
stop: () => void;
|
||||
}
|
||||
|
||||
function blinking(ledFile: string): Blink;
|
||||
function blinking(ledFile: string): { pattern: Blink };
|
||||
|
||||
export = blinking;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user