diff --git a/src/shadowbox/server/outline_shadowsocks_server.ts b/src/shadowbox/server/outline_shadowsocks_server.ts index e7c403701..1abe1b2c0 100644 --- a/src/shadowbox/server/outline_shadowsocks_server.ts +++ b/src/shadowbox/server/outline_shadowsocks_server.ts @@ -21,12 +21,26 @@ import * as file from '../infrastructure/file'; import * as logging from '../infrastructure/logging'; import {ShadowsocksAccessKey, ShadowsocksServer} from '../model/shadowsocks_server'; +/** Represents an outline-ss-server configuration with multiple services. */ +export interface OutlineSSServerConfig { + services: { + listeners: { + type: string; + address: string; + }[]; + keys: { + id: string; + cipher: string; + secret: string; + }[]; + }[]; +} + // Runs outline-ss-server. export class OutlineShadowsocksServer implements ShadowsocksServer { private ssProcess: child_process.ChildProcess; private ipCountryFilename?: string; private ipAsnFilename?: string; - private isAsnMetricsEnabled = false; private isReplayProtectionEnabled = false; /** @@ -81,22 +95,39 @@ export class OutlineShadowsocksServer implements ShadowsocksServer { private writeConfigFile(keys: ShadowsocksAccessKey[]): Promise { return new Promise((resolve, reject) => { - const keysJson = {keys: [] as ShadowsocksAccessKey[]}; - for (const key of keys) { + const validKeys: ShadowsocksAccessKey[] = keys.filter((key) => { if (!isAeadCipher(key.cipher)) { logging.error( `Cipher ${key.cipher} for access key ${key.id} is not supported: use an AEAD cipher instead.` ); - continue; + return false; } + return true; + }); - keysJson.keys.push(key); + const config: OutlineSSServerConfig = {services: []}; + const keysByPort: Record = {}; + for (const key of validKeys) { + (keysByPort[key.port] ??= []).push(key); + } + for (const port in keysByPort) { + const service = { + listeners: [ + {type: 'tcp', address: `[::]:${port}`}, + {type: 'udp', address: `[::]:${port}`}, + ], + keys: keysByPort[port].map((key) => ({ + id: key.id, + cipher: key.cipher, + secret: key.secret, + })), + }; + config.services.push(service); } mkdirp.sync(path.dirname(this.configFilename)); - try { - file.atomicWriteFileSync(this.configFilename, jsyaml.safeDump(keysJson, {sortKeys: true})); + file.atomicWriteFileSync(this.configFilename, jsyaml.safeDump(config, {sortKeys: true})); resolve(); } catch (error) { reject(error);