Skip to content

Commit

Permalink
feat: add gun synchronization
Browse files Browse the repository at this point in the history
  • Loading branch information
johackim committed May 5, 2024
1 parent 44f3d0b commit 9d23320
Show file tree
Hide file tree
Showing 16 changed files with 141 additions and 91 deletions.
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
GUN_SERVER=http://localhost:8765/gun
CONFIG_PATH=/tmp/ulysse/config.json
RESOLV_CONF_PATH=/tmp/resolv.conf
SOCKET_PATH=/tmp/ulysse.sock
13 changes: 8 additions & 5 deletions __tests__/block.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { config, readConfig } from '../src/config';
import { config, editConfig, readConfig } from '../src/config';
import { DEFAULT_CONFIG } from '../src/constants';
import { disableShieldMode } from '../src/shield';
import {
getBlockedApps,
blockDistraction,
Expand All @@ -9,10 +11,11 @@ import {
getRunningBlockedApps,
} from '../src/block';

beforeEach(() => {
config.blocklist = [];
config.whitelist = [];
config.shield = false;
beforeEach(async () => {
await disableShieldMode('ulysse');
await editConfig(DEFAULT_CONFIG);
Object.assign(config, DEFAULT_CONFIG);
jest.spyOn(console, 'log').mockImplementation(() => {});
});

test('Should check a distraction', async () => {
Expand Down
16 changes: 10 additions & 6 deletions __tests__/commands.spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { config } from '../src/config';
import { config, editConfig } from '../src/config';
import { DEFAULT_CONFIG } from '../src/constants';
import { disableShieldMode } from '../src/shield';
import { helpCmd, versionCmd, blockCmd, whitelistCmd, unblockCmd, shieldCmd } from '../src/commands';

beforeEach(() => {
beforeEach(async () => {
process.argv = [];
config.blocklist = [];
config.whitelist = [];
config.shield = false;
await disableShieldMode('ulysse');
await editConfig(DEFAULT_CONFIG);
Object.assign(config, DEFAULT_CONFIG);
jest.spyOn(console, 'log').mockImplementation(() => {});
});

Expand Down Expand Up @@ -106,7 +108,9 @@ test('Should whitelist a domain with a wildcard', async () => {
});

test('Should enable shield mode', async () => {
shieldCmd();
process.argv = ['ulysse', '-s', 'on', '-p', 'ulysse'];

shieldCmd('on');

expect(console.log).toHaveBeenCalledWith('Shield mode enabled.');
});
Expand Down
14 changes: 8 additions & 6 deletions __tests__/daemon.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import fs from 'fs';
import { config, readConfig } from '../src/config';
import { config, editConfig, readConfig } from '../src/config';
import { getRunningApps } from '../src/utils';
import { blockDistraction } from '../src/block';
import { DEFAULT_CONFIG } from '../src/constants';
import { disableShieldMode } from '../src/shield';
import { handleAppBlocking, handleTimeout, updateResolvConf } from '../src/daemon';

jest.mock('../src/utils', () => ({
Expand All @@ -14,10 +16,10 @@ jest.mock('child_process', () => ({
exec: jest.fn().mockImplementation(() => false),
}));

beforeEach(() => {
config.blocklist = [];
config.whitelist = [];
config.shield = false;
beforeEach(async () => {
await disableShieldMode('ulysse');
await editConfig(DEFAULT_CONFIG);
Object.assign(config, DEFAULT_CONFIG);
jest.spyOn(console, 'log').mockImplementation(() => {});
});

Expand Down Expand Up @@ -45,7 +47,7 @@ test('Should edit /etc/resolv.conf', async () => {
test('Should remove a distraction from blocklist if timeout is reached', async () => {
config.blocklist = [{ name: 'chromium' }, { name: 'example.com', timeout: 1708617136 }];

handleTimeout();
await handleTimeout();

expect(readConfig().blocklist).toEqual([{ name: 'chromium' }]);
});
8 changes: 6 additions & 2 deletions __tests__/shield.spec.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { readConfig } from '../src/config';
import { config, readConfig, editConfig } from '../src/config';
import { DEFAULT_CONFIG } from '../src/constants';
import { whitelistDistraction } from '../src/whitelist';
import { enableShieldMode, disableShieldMode } from '../src/shield';
import { blockDistraction, unblockDistraction } from '../src/block';

beforeEach(() => {
beforeEach(async () => {
await disableShieldMode('ulysse');
await editConfig(DEFAULT_CONFIG);
Object.assign(config, DEFAULT_CONFIG);
jest.spyOn(console, 'log').mockImplementation(() => {});
});

Expand Down
29 changes: 14 additions & 15 deletions __tests__/whitelist.spec.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
import { config } from '../src/config';
import { config, readConfig, editConfig } from '../src/config';
import { DEFAULT_CONFIG } from '../src/constants';
import { disableShieldMode } from '../src/shield';
import { blockDistraction, isDistractionBlocked } from '../src/block';
import { whitelistDistraction } from '../src/whitelist';

jest.mock('child_process', () => ({
execSync: jest.fn().mockImplementation(() => false),
}));

beforeEach(() => {
config.blocklist = [];
config.whitelist = [];
config.shield = false;
beforeEach(async () => {
await disableShieldMode('ulysse');
await editConfig(DEFAULT_CONFIG);
Object.assign(config, DEFAULT_CONFIG);
jest.spyOn(console, 'log').mockImplementation(() => {});
});

test('Should whitelist a distraction', async () => {
const distraction = { name: 'example.com' };

whitelistDistraction(distraction);
await whitelistDistraction(distraction);

expect(config.whitelist).toEqual([distraction]);
expect(readConfig().whitelist).toEqual([distraction]);
});

test('Should not block a domain if it is in the whitelist', async () => {
blockDistraction({ name: '*.*' });
whitelistDistraction({ name: 'www.example.com' });
await blockDistraction({ name: 'www.example.com' });
await whitelistDistraction({ name: 'www.example.com' });

expect(isDistractionBlocked('www.example.com')).toBe(false);
});

test('Should not block a domain if it is in the whitelist with a wildcard', async () => {
blockDistraction({ name: '*.*' });
whitelistDistraction({ name: '*.example.com' });
await blockDistraction({ name: '*.*' });
await whitelistDistraction({ name: '*.example.com' });

expect(isDistractionBlocked('www.example.com')).toBe(false);
});
3 changes: 2 additions & 1 deletion jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
require('dotenv/config');
const fs = require('fs');
const { CONFIG_PATH } = require('./src/constants');
const { socket } = require('./src/socket');

module.exports = () => {
if (fs.existsSync(CONFIG_PATH)) {
fs.unlinkSync(CONFIG_PATH);
}

import('./src/socket');
socket();
};
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"productivity"
],
"dependencies": {
"dns-packet": "^5.6.1"
"dns-packet": "^5.6.1",
"gun": "^0.2020.1240"
},
"devDependencies": {
"@babel/cli": "^7.23.9",
Expand All @@ -42,6 +43,7 @@
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4",
"dotenv": "^16.4.5",
"eslint": "^8.57.0",
Expand Down
2 changes: 2 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import json from '@rollup/plugin-json';
import babel from '@rollup/plugin-babel';
import terser from '@rollup/plugin-terser';
import commonjs from '@rollup/plugin-commonjs';
import { nodeResolve } from '@rollup/plugin-node-resolve';

export default {
input: 'src/index.js',
Expand All @@ -12,6 +13,7 @@ export default {
json(),
terser(),
commonjs(),
nodeResolve(),
babel({ babelHelpers: 'bundled' }),
],
};
3 changes: 2 additions & 1 deletion src/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { HELP_MESSAGE } from './constants';
import { whitelistDistraction } from './whitelist';
import { isValidPassword, enableShieldMode, disableShieldMode } from './shield';
import { isValidDistraction, isValidDomain, blockDistraction, unblockDistraction } from './block';
import { daemon } from './daemon';

export const helpCmd = () => {
console.log(HELP_MESSAGE);
Expand All @@ -16,7 +17,7 @@ export const versionCmd = () => {
};

export const daemonCmd = () => {
import('./daemon');
daemon();
};

export const blockCmd = (name) => {
Expand Down
6 changes: 1 addition & 5 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,7 @@ export const readConfig = () => {
};

export const editConfig = async (newConfig) => {
if (isSudo()) {
fs.writeFileSync(CONFIG_PATH, JSON.stringify(newConfig, null, 4), 'utf8');
} else {
await sendDataToSocket(newConfig);
}
await sendDataToSocket(newConfig);
};

export const config = (tryCatch(() => {
Expand Down
3 changes: 3 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ export const CONFIG_PATH = process.env.CONFIG_PATH || '/etc/ulysse/config.json';

export const SOCKET_PATH = process.env.SOCKET_PATH || '/var/run/ulysse.sock';

export const GUN_SERVER = process.env.GUN_SERVER || 'http://localhost:8765/gun';

export const DNS_SERVER = process.env.DNS_SERVER || '9.9.9.9';

export const DNS_PORT = process.env.DNS_PORT || 53;

export const DOMAIN_REGEX = /^([\w-]+\.)+[\w-]+$/;

export const SYSTEM_WHITELIST = [
new URL(GUN_SERVER).hostname,
'/sbin/init',
'/sbin/agetty',
'/usr/bin/xinit',
Expand Down
32 changes: 18 additions & 14 deletions src/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { config, editConfig } from './config';
import { getRunningBlockedApps } from './block';
import { isSudo, sendNotification } from './utils';
import { DNS_SERVER, RESOLV_CONF_PATH } from './constants';
import { synchronize } from './synchronize';
import { socket } from './socket';
import { dns } from './dns';

export const updateResolvConf = (dnsServer = DNS_SERVER) => {
execSync(`chattr -i ${RESOLV_CONF_PATH}`);
Expand All @@ -25,24 +28,26 @@ export const handleAppBlocking = () => {
}
};

export const handleTimeout = () => {
config.blocklist = config.blocklist.filter(({ timeout }) => !timeout || timeout >= Math.floor(Date.now() / 1000));
config.whitelist = config.whitelist.filter(({ timeout }) => !timeout || timeout >= Math.floor(Date.now() / 1000));
export const handleTimeout = async () => {
const blocklist = config.blocklist.filter(({ timeout }) => !timeout || timeout >= Math.floor(Date.now() / 1000));
const whitelist = config.whitelist.filter(({ timeout }) => !timeout || timeout >= Math.floor(Date.now() / 1000));

editConfig(config);
if (blocklist.length !== config.blocklist.length || whitelist.length !== config.whitelist.length) {
await editConfig({ ...config, blocklist, whitelist });
}
};

export const cleanUpAndExit = () => {
updateResolvConf();
process.exit(0);
};

if (!isSudo()) {
console.error('You must run this command with sudo.');
process.exit(1);
}
export const daemon = () => {
if (!isSudo()) {
console.error('You must run this command with sudo.');
process.exit(1);
}

if (process.env.NODE_ENV !== 'test') {
setInterval(() => {
handleAppBlocking();
}, 1000);
Expand All @@ -53,12 +58,11 @@ if (process.env.NODE_ENV !== 'test') {

console.log('Starting daemon...');
updateResolvConf('127.0.0.1');
handleTimeout();
handleAppBlocking();

process.on('SIGINT', cleanUpAndExit);
process.on('SIGTERM', cleanUpAndExit);

import('./socket');
import('./dns');
}
synchronize();
socket();
dns();
};
50 changes: 26 additions & 24 deletions src/dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,39 @@ import packet from 'dns-packet';
import { isDistractionBlocked } from './block';
import { DNS_SERVER, DNS_PORT } from './constants';

const server = dgram.createSocket('udp4');
export const dns = () => {
const server = dgram.createSocket('udp4');

server.on('message', async (msg, rinfo) => {
const proxy = dgram.createSocket('udp4');
server.on('message', async (msg, rinfo) => {
const proxy = dgram.createSocket('udp4');

proxy.on('message', (response) => {
const responsePacket = packet.decode(response);
const domain = responsePacket.questions?.[0]?.name;
proxy.on('message', (response) => {
const responsePacket = packet.decode(response);
const domain = responsePacket.questions?.[0]?.name;

if (!isDistractionBlocked(domain) || responsePacket.answers.length === 0) {
server.send(response, rinfo.port, rinfo.address);
proxy.close();
return;
}

responsePacket.answers = responsePacket.answers.map((answer) => {
if (answer.type === 'A' || answer.type === 'AAAA') {
return { ...answer, data: answer.type === 'A' ? '127.0.0.1' : '::1' };
if (!isDistractionBlocked(domain) || responsePacket.answers.length === 0) {
server.send(response, rinfo.port, rinfo.address);
proxy.close();
return;
}

return answer;
responsePacket.answers = responsePacket.answers.map((answer) => {
if (answer.type === 'A' || answer.type === 'AAAA') {
return { ...answer, data: answer.type === 'A' ? '127.0.0.1' : '::1' };
}

return answer;
});

const newPacket = packet.encode(responsePacket);
server.send(newPacket, rinfo.port, rinfo.address);
proxy.close();
});

const newPacket = packet.encode(responsePacket);
server.send(newPacket, rinfo.port, rinfo.address);
proxy.close();
proxy.send(msg, 0, msg.length, 53, DNS_SERVER);
});

proxy.send(msg, 0, msg.length, 53, DNS_SERVER);
});

server.bind(DNS_PORT);
server.bind(DNS_PORT);

console.log(`Starting DNS server on port ${DNS_PORT}...`);
console.log(`Starting DNS server on port ${DNS_PORT}...`);
};
Loading

0 comments on commit 9d23320

Please sign in to comment.