1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-21 09:38:01 +02:00

Server: Resolves #5738: Check for time drift when the server starts

This commit is contained in:
Laurent Cozic 2021-11-17 12:54:34 +00:00
parent 483b2e769f
commit 0bf323dc63
11 changed files with 90 additions and 67 deletions

View File

@ -1176,9 +1176,9 @@ packages/lib/models/utils/paginationToSql.js.map
packages/lib/models/utils/types.d.ts
packages/lib/models/utils/types.js
packages/lib/models/utils/types.js.map
packages/lib/ntpDate.d.ts
packages/lib/ntpDate.js
packages/lib/ntpDate.js.map
packages/lib/ntp.d.ts
packages/lib/ntp.js
packages/lib/ntp.js.map
packages/lib/onedrive-api.d.ts
packages/lib/onedrive-api.js
packages/lib/onedrive-api.js.map

6
.gitignore vendored
View File

@ -1159,9 +1159,9 @@ packages/lib/models/utils/paginationToSql.js.map
packages/lib/models/utils/types.d.ts
packages/lib/models/utils/types.js
packages/lib/models/utils/types.js.map
packages/lib/ntpDate.d.ts
packages/lib/ntpDate.js
packages/lib/ntpDate.js.map
packages/lib/ntp.d.ts
packages/lib/ntp.js
packages/lib/ntp.js.map
packages/lib/onedrive-api.d.ts
packages/lib/onedrive-api.js
packages/lib/onedrive-api.js.map

61
packages/lib/ntp.ts Normal file
View File

@ -0,0 +1,61 @@
import shim from './shim';
const ntpClient_ = require('./vendor/ntp-client');
const server = {
domain: 'pool.ntp.org',
port: 123,
};
function ntpClient() {
ntpClient_.dgram = shim.dgram();
return ntpClient_;
}
export async function getNetworkTime(): Promise<Date> {
return new Promise(function(resolve: Function, reject: Function) {
ntpClient().getNetworkTime(server.domain, server.port, function(error: any, date: Date) {
if (error) {
reject(error);
return;
}
resolve(date);
});
});
}
export async function getDeviceTimeDrift(): Promise<number> {
let ntpTime: Date = null;
try {
ntpTime = await getNetworkTime();
} catch (error) {
error.message = `Cannot retrieve the network time: ${error.message}`;
throw error;
}
return ntpTime.getTime() - Date.now();
}
// export default async function(): Promise<Date> {
// if (shouldSyncTime()) {
// const release = await fetchingTimeMutex.acquire();
// try {
// if (shouldSyncTime()) {
// const date = await networkTime();
// nextSyncTime = Date.now() + 60 * 1000;
// timeOffset = date.getTime() - Date.now();
// }
// } catch (error) {
// logger.warn('Could not get NTP time - falling back to device time:', error);
// // Fallback to device time since
// // most of the time it's actually correct
// nextSyncTime = Date.now() + 20 * 1000;
// timeOffset = 0;
// } finally {
// release();
// }
// }
// return new Date(Date.now() + timeOffset);
// }

View File

@ -1,59 +0,0 @@
const ntpClient = require('./vendor/ntp-client');
import Logger from './Logger';
const Mutex = require('async-mutex').Mutex;
let nextSyncTime = 0;
let timeOffset = 0;
let logger = new Logger();
const fetchingTimeMutex = new Mutex();
const server = {
domain: 'pool.ntp.org',
port: 123,
};
async function networkTime(): Promise<Date> {
return new Promise(function(resolve: Function, reject: Function) {
ntpClient.getNetworkTime(server.domain, server.port, function(error: any, date: Date) {
if (error) {
reject(error);
return;
}
resolve(date);
});
});
}
function shouldSyncTime() {
return !nextSyncTime || Date.now() > nextSyncTime;
}
export function setLogger(v: any) {
logger = v;
}
export default async function(): Promise<Date> {
if (shouldSyncTime()) {
const release = await fetchingTimeMutex.acquire();
try {
if (shouldSyncTime()) {
const date = await networkTime();
nextSyncTime = Date.now() + 60 * 1000;
timeOffset = date.getTime() - Date.now();
}
} catch (error) {
logger.warn('Could not get NTP time - falling back to device time:', error);
// Fallback to device time since
// most of the time it's actually correct
nextSyncTime = Date.now() + 20 * 1000;
timeOffset = 0;
} finally {
release();
}
}
return new Date(Date.now() + timeOffset);
}

View File

@ -16,6 +16,7 @@ const https = require('https');
const toRelative = require('relative');
const timers = require('timers');
const zlib = require('zlib');
const dgram = require('dgram');
function fileExists(filePath) {
try {
@ -93,6 +94,10 @@ function shimInit(options = null) {
return shim.fsDriver_;
};
shim.dgram = () => {
return dgram;
};
if (options.React) {
shim.react = () => {
return options.React;

View File

@ -333,6 +333,10 @@ const shim = {
return react_;
},
dgram: () => {
throw new Error('Not implemented');
},
platformSupportsKeyChain: () => {
// keytar throws an error when system keychain is not present; even
// when keytar itself is installed. try/catch to ensure system

View File

@ -44,8 +44,8 @@ const Buffer = require('buffer').Buffer;
if (!exports.dgram) throw new Error('dgram package has not been set!!');
var client = exports.dgram.createSocket("udp4"),
ntpData = new Buffer(48);
var client = exports.dgram.createSocket("udp4");
var ntpData = Buffer.alloc(48); // new Buffer(48);
// RFC 2030 -> LI = 0 (no warning, 2 bits), VN = 3 (IPv4 only, 3 bits), Mode = 3 (Client Mode, 3 bits) -> 1 byte
// -> rtol(LI, 6) ^ rotl(VN, 3) ^ rotl(Mode, 0)

View File

@ -8,6 +8,7 @@ import config, { initConfig, runningInDocker } from './config';
import { migrateLatest, waitForConnection, sqliteDefaultDir, latestMigration } from './db';
import { AppContext, Env, KoaNext } from './utils/types';
import FsDriverNode from '@joplin/lib/fs-driver-node';
import { getDeviceTimeDrift } from '@joplin/lib/ntp';
import routeHandler from './middleware/routeHandler';
import notificationHandler from './middleware/notificationHandler';
import ownerHandler from './middleware/ownerHandler';
@ -249,6 +250,13 @@ async function main() {
runCommandAndExitApp = false;
appLogger().info(`Starting server v${config().appVersion} (${env}) on port ${config().port} and PID ${process.pid}...`);
const timeDrift = await getDeviceTimeDrift();
if (Math.abs(timeDrift) > config().maxTimeDrift) {
throw new Error(`The device time drift is ${timeDrift}ms (Max allowed: ${config().maxTimeDrift}ms) - cannot continue as it could cause data loss and conflicts on the sync clients. You may increase env var MAX_TIME_DRIFT to pass the check.`);
}
appLogger().info(`NTP time offset: ${timeDrift}ms`);
appLogger().info('Running in Docker:', runningInDocker());
appLogger().info('Public base URL:', config().baseUrl);
appLogger().info('API base URL:', config().apiBaseUrl);

View File

@ -134,6 +134,7 @@ export async function initConfig(envType: Env, env: EnvVariables, overrides: any
storageDriver: parseStorageDriverConnectionString(env.STORAGE_DRIVER),
storageDriverFallback: parseStorageDriverConnectionString(env.STORAGE_DRIVER_FALLBACK),
itemSizeHardLimit: 250000000, // Beyond this the Postgres driver will crash the app
maxTimeDrift: env.MAX_TIME_DRIFT,
...overrides,
};
}

View File

@ -16,6 +16,7 @@ const defaultEnvValues: EnvVariables = {
ERROR_STACK_TRACES: false,
COOKIES_SECURE: false,
RUNNING_IN_DOCKER: false,
MAX_TIME_DRIFT: 10,
// ==================================================
// URL config
@ -85,6 +86,7 @@ export interface EnvVariables {
ERROR_STACK_TRACES: boolean;
COOKIES_SECURE: boolean;
RUNNING_IN_DOCKER: boolean;
MAX_TIME_DRIFT: number;
APP_BASE_URL: string;
USER_CONTENT_BASE_URL: string;

View File

@ -160,6 +160,7 @@ export interface Config {
storageDriver: StorageDriverConfig;
storageDriverFallback: StorageDriverConfig;
itemSizeHardLimit: number;
maxTimeDrift: number;
}
export enum HttpMethod {