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() {}
}