diff --git a/release/release_data.json b/release/release_data.json index c5ac6cb0..953aca15 100644 --- a/release/release_data.json +++ b/release/release_data.json @@ -1,24 +1,24 @@ { "__COMMENTS__": [ - "There is no version 100; this file exists now only to prompt users of versions 1.0.5 and ", + "This file exists now only to prompt users of versions 1.0.5 and ", "earlier to update to the latest, auto-updates-by-electron-builder, version." ], "latestVersions": { "outline-manager-darwin-x64": { "location": "https://raw.githubusercontent.com/Jigsaw-Code/outline-releases/master/manager/Outline-Manager.dmg", - "version": "100.0.0" + "version": "1.1.0" }, "outline-manager-linux-ia32": { "location": "https://raw.githubusercontent.com/Jigsaw-Code/outline-releases/master/manager/Outline-Manager.AppImage", - "version": "100.0.0" + "version": "1.1.0" }, "outline-manager-linux-x64": { "location": "https://raw.githubusercontent.com/Jigsaw-Code/outline-releases/master/manager/Outline-Manager.AppImage", - "version": "100.0.0" + "version": "1.1.0" }, "outline-manager-win32-ia32": { "location": "https://raw.githubusercontent.com/Jigsaw-Code/outline-releases/master/manager/Outline-Manager.exe", - "version": "100.0.0" + "version": "1.1.0" } } } diff --git a/src/server_manager/model/server.ts b/src/server_manager/model/server.ts index c158dc7b..0c721a5a 100644 --- a/src/server_manager/model/server.ts +++ b/src/server_manager/model/server.ts @@ -69,8 +69,8 @@ export interface ManagedServer extends Server { // The managed machine where the Outline Server is running. export interface ManagedServerHost { - // Returns the monthly transfer limit. - getMonthlyTransferLimit(): DataAmount; + // Returns the monthly outbound transfer limit. + getMonthlyOutboundTransferLimit(): DataAmount; // Returns the monthly cost. getMonthlyCost(): MonetaryCost; // Returns the server region. diff --git a/src/server_manager/ui_components/outline-server-view.html b/src/server_manager/ui_components/outline-server-view.html index e2b10221..e79601b5 100644 --- a/src/server_manager/ui_components/outline-server-view.html +++ b/src/server_manager/ui_components/outline-server-view.html @@ -282,8 +282,8 @@

{{serverName}}

- [[_formatBytesTransferred(totalBytesTransferred, '0')]] - of [[_formatBytesTransferred(monthlyTransferBytes)]] Transfer + [[_formatBytesTransferred(totalInboundBytes, '0')]] + of [[_formatBytesTransferred(monthlyOutboundTransferBytes)]] Transfer , ${{monthlyCost}} US/Mo

@@ -371,20 +371,20 @@ // be unset in some old versions of Outline that allowed deleting this // row. myConnection: Object, - totalBytesTransferred: Number, + totalInboundBytes: Number, accessKeyRows: {type: Array, value: []}, hasNonAdminAccessKeys: Boolean, deleteEnabled: Boolean, forgetEnabled: Boolean, metricsEnabled: Boolean, serverId: String, - // Initialize monthlyTransferBytes and monthlyCost to 0, so they can + // Initialize monthlyOutboundTransferBytes and monthlyCost to 0, so they can // be bound to hidden attributes. Initializing to undefined does not // cause hidden$=... expressions to be evaluated and so elements may be // shown incorrectly. See: // https://stackoverflow.com/questions/33700125/polymer-1-0-hidden-attribute-negate-operator // https://www.polymer-project.org/1.0/docs/devguide/data-binding.html - monthlyTransferBytes: {type: Number, value: 0}, + monthlyOutboundTransferBytes: {type: Number, value: 0}, monthlyCost: {type: Number, value: 0} }, observers: [ @@ -470,7 +470,7 @@ }, _encodeURIComponent: encodeURIComponent, setServerTransferredData(total_bytes) { - this.totalBytesTransferred = total_bytes + this.totalInboundBytes = total_bytes }, updateAccessKeyRow(accessKeyId, fields) { if (accessKeyId === MY_CONNECTION_USER_ID) { diff --git a/src/server_manager/web_app/app.spec.ts b/src/server_manager/web_app/app.spec.ts index 24882920..8d597ae8 100644 --- a/src/server_manager/web_app/app.spec.ts +++ b/src/server_manager/web_app/app.spec.ts @@ -297,7 +297,7 @@ class FakeManagedServer extends FakeServer implements server.ManagedServer { } getHost() { return { - getMonthlyTransferLimit: () => ({terabytes: 1}), + getMonthlyOutboundTransferLimit: () => ({terabytes: 1}), getMonthlyCost: () => ({usd: 5}), getRegionId: () => 'fake-region', delete: () => Promise.resolve(), diff --git a/src/server_manager/web_app/app.ts b/src/server_manager/web_app/app.ts index 9e96d80c..13671882 100644 --- a/src/server_manager/web_app/app.ts +++ b/src/server_manager/web_app/app.ts @@ -486,15 +486,8 @@ export class App { view.monthlyCost = host.getMonthlyCost().usd; view.deleteEnabled = true; view.forgetEnabled = false; - // Set monthly transfer byte limit for UI. For UI simplicity we are: - // 1. Showing 1 TB as 1000 GB (not 1024) - // 2. Dividing the total transfer limit by 2 to account for inbound and - // outbound connections, i.e. if I download a 10 GB file, it has to - // first be download from destination to the Outline server, then from - // the Outline server to my client, and costs me 20 GB against my quota, - // in this case it's simpler to say I used 10/500GB instead of 20/1000GB. - const monthlyTransferGb = host.getMonthlyTransferLimit().terabytes * 1000 / 2; - view.monthlyTransferBytes = monthlyTransferGb * (2 ** 30); + const monthlyOutboundTransferGb = host.getMonthlyOutboundTransferLimit().terabytes * 1000; + view.monthlyOutboundTransferBytes = monthlyOutboundTransferGb * (2 ** 30); } else { // TODO(dborkan): consider using dom-if with restamp property // https://www.polymer-project.org/1.0/docs/api/elements/dom-if @@ -502,7 +495,7 @@ export class App { // the server-view when we display a new server. This should be fixed // once we support multiple servers. view.monthlyCost = undefined; - view.monthlyTransferBytes = undefined; + view.monthlyOutboundTransferBytes = undefined; view.deleteEnabled = false; view.forgetEnabled = true; } diff --git a/src/server_manager/web_app/digitalocean_server.ts b/src/server_manager/web_app/digitalocean_server.ts index 6dcb4a46..99e3d328 100644 --- a/src/server_manager/web_app/digitalocean_server.ts +++ b/src/server_manager/web_app/digitalocean_server.ts @@ -324,7 +324,9 @@ class DigitalOceanHost implements server.ManagedServerHost { private digitalOcean: DigitalOceanSession, private dropletInfo: DropletInfo, private deleteCallback: Function) {} - getMonthlyTransferLimit(): server.DataAmount { + getMonthlyOutboundTransferLimit(): server.DataAmount { + // Details on the bandwidth limits can be found at + // https://www.digitalocean.com/community/tutorials/digitalocean-bandwidth-billing-faq return {terabytes: this.dropletInfo.size.transfer}; } diff --git a/src/shadowbox/model/shadowsocks_server.ts b/src/shadowbox/model/shadowsocks_server.ts index 6f4a734a..5017f100 100644 --- a/src/shadowbox/model/shadowsocks_server.ts +++ b/src/shadowbox/model/shadowsocks_server.ts @@ -28,6 +28,6 @@ export interface ShadowsocksInstance { // Registers a callback to be invoked when the ShadowsocksInstance has // transferred data (inbound and outbond). bytes is the number of // bytes transferred since the last callback. - onBytesTransferred(callback: (bytes: number, ipAddresses: string[]) => void); + onInboundBytes(callback: (bytes: number, ipAddresses: string[]) => void); stop(); } diff --git a/src/shadowbox/server/libev_shadowsocks_server.ts b/src/shadowbox/server/libev_shadowsocks_server.ts index 531b9a7c..10908147 100644 --- a/src/shadowbox/server/libev_shadowsocks_server.ts +++ b/src/shadowbox/server/libev_shadowsocks_server.ts @@ -96,7 +96,16 @@ class LibevShadowsocksServerInstance implements ShadowsocksInstance { this.childProcess.kill(); } - public onBytesTransferred(callback: (bytes: number, ipAddresses: string[]) => void) { + // onBytesTransferred only reports inbound bytes, received from the client or from the target. + // + // This measure under-estimates outbound traffic because: + // 1) The traffic to and from the client has overhead from Shadowsocks + // 2) The overhead on the traffic to the client is larger than on the traffic from the client + // because, from the client perspective, download traffic is usually larger than upload. + // + // The measure is calculated here: + // https://github.com/shadowsocks/shadowsocks-libev/blob/a16826b83e73af386806d1b51149f8321820835e/src/server.c#L172 + public onInboundBytes(callback: (bytes: number, ipAddresses: string[]) => void) { if (this.eventEmitter.listenerCount(this.BYTES_TRANSFERRED_EVENT) === 0) { this.createStatsListener(); } @@ -104,7 +113,7 @@ class LibevShadowsocksServerInstance implements ShadowsocksInstance { } private createStatsListener() { - let lastBytesTransferred = 0; + let lastInboundBytes = 0; this.statsSocket.on('message', (buf: Buffer) => { let statsMessage; try { @@ -117,11 +126,11 @@ class LibevShadowsocksServerInstance implements ShadowsocksInstance { // Ignore stats for other ss-servers, which post to the same statsSocket. return; } - const delta = statsMessage.totalBytesTransferred - lastBytesTransferred; + const delta = statsMessage.totalInboundBytes - lastInboundBytes; if (delta > 0) { this.getConnectedClientIPAddresses() .then((ipAddresses: string[]) => { - lastBytesTransferred = statsMessage.totalBytesTransferred; + lastInboundBytes = statsMessage.totalInboundBytes; this.eventEmitter.emit(this.BYTES_TRANSFERRED_EVENT, delta, ipAddresses); }) .catch((err) => { @@ -159,20 +168,20 @@ class LibevShadowsocksServerInstance implements ShadowsocksInstance { interface StatsMessage { portNumber: number; - totalBytesTransferred: number; + totalInboundBytes: number; } function parseStatsMessage(buf): StatsMessage { const jsonString = buf.toString() .substr('stat: '.length) // remove leading "stat: " .replace(/\0/g, ''); // remove trailing null terminator - // statObj is in the form {"port#": totalBytesTransferred}, where + // statObj is in the form {"port#": totalInboundBytes}, where // there is always only 1 port# per JSON object. If there are multiple // ss-servers communicating to the same manager, we will get multiple // message events. const statObj = JSON.parse(jsonString); // Object.keys is used here because node doesn't support Object.values. const portNumber = parseInt(Object.keys(statObj)[0], 10); - const totalBytesTransferred = statObj[portNumber]; - return {portNumber, totalBytesTransferred}; + const totalInboundBytes = statObj[portNumber]; + return {portNumber, totalInboundBytes}; } diff --git a/src/shadowbox/server/managed_user.ts b/src/shadowbox/server/managed_user.ts index 9c89e2b7..aedfd151 100644 --- a/src/shadowbox/server/managed_user.ts +++ b/src/shadowbox/server/managed_user.ts @@ -125,7 +125,7 @@ class ManagedAccessKeyRepository implements AccessKeyRepository { accessKeyJson.port, accessKeyJson.password, statsSocket, accessKeyJson.encryptionMethod) .then((ssInstance) => { - ssInstance.onBytesTransferred(this.handleBytesTransferred.bind( + ssInstance.onInboundBytes(this.handleBytesTransferred.bind( this, accessKeyJson.id, accessKeyJson.metricsId)); const accessKey = new ManagedAccessKey( accessKeyJson.id, accessKeyJson.metricsId, accessKeyJson.name, ssInstance); @@ -148,7 +148,7 @@ class ManagedAccessKeyRepository implements AccessKeyRepository { this.reservedPorts.add(port); const id = this.allocateId(); const metricsId = uuidv4(); - ssInstance.onBytesTransferred(this.handleBytesTransferred.bind(this, id, metricsId)); + ssInstance.onInboundBytes(this.handleBytesTransferred.bind(this, id, metricsId)); const accessKey = new ManagedAccessKey(id, metricsId, '', ssInstance); this.accessKeys.set(accessKey.id, accessKey); this.persistState(); diff --git a/src/shadowbox/server/mocks/mocks.ts b/src/shadowbox/server/mocks/mocks.ts index 49bcd719..9e011a76 100644 --- a/src/shadowbox/server/mocks/mocks.ts +++ b/src/shadowbox/server/mocks/mocks.ts @@ -68,7 +68,7 @@ class MockShadowsocksInstance implements ShadowsocksInstance { public password = 'password', public encryptionMethod = 'encryption', public accessUrl = 'ss://somethingsomething') {} - onBytesTransferred(callback: (bytes: number, ipAddresses: string[]) => void) {} + onInboundBytes(callback: (bytes: number, ipAddresses: string[]) => void) {} stop() {} }