src: add client side https proxy support
This commit is contained in:
parent
3116eca5b8
commit
142f8f5d77
@ -45,6 +45,8 @@ module.exports = function init({ isMinimal, isOverwrite, isDryRun = false }) {
|
|||||||
'mux': false,
|
'mux': false,
|
||||||
'mux_concurrency': 10,
|
'mux_concurrency': 10,
|
||||||
},
|
},
|
||||||
|
'https_key': 'https_key.pem',
|
||||||
|
'https_cert': 'https_cert.pem',
|
||||||
'dns': [],
|
'dns': [],
|
||||||
'dns_expire': 3600,
|
'dns_expire': 3600,
|
||||||
'timeout': timeout,
|
'timeout': timeout,
|
||||||
|
@ -30,6 +30,9 @@ export class Config {
|
|||||||
is_client = null;
|
is_client = null;
|
||||||
is_server = null;
|
is_server = null;
|
||||||
|
|
||||||
|
https_key = null;
|
||||||
|
https_cert = null;
|
||||||
|
|
||||||
timeout = null;
|
timeout = null;
|
||||||
redirect = null;
|
redirect = null;
|
||||||
|
|
||||||
@ -66,6 +69,7 @@ export class Config {
|
|||||||
stores = [];
|
stores = [];
|
||||||
|
|
||||||
constructor(json) {
|
constructor(json) {
|
||||||
|
// service
|
||||||
const { protocol, hostname, port, pathname, searchParams, username, password } = new URL(json.service);
|
const { protocol, hostname, port, pathname, searchParams, username, password } = new URL(json.service);
|
||||||
this.local_protocol = protocol.slice(0, -1);
|
this.local_protocol = protocol.slice(0, -1);
|
||||||
this.local_username = username;
|
this.local_username = username;
|
||||||
@ -75,6 +79,7 @@ export class Config {
|
|||||||
this.local_port = +port;
|
this.local_port = +port;
|
||||||
this.local_pathname = pathname;
|
this.local_pathname = pathname;
|
||||||
|
|
||||||
|
// server
|
||||||
let server;
|
let server;
|
||||||
// TODO(remove in next version): make backwards compatibility to "json.servers"
|
// TODO(remove in next version): make backwards compatibility to "json.servers"
|
||||||
if (json.servers !== undefined) {
|
if (json.servers !== undefined) {
|
||||||
@ -105,8 +110,15 @@ export class Config {
|
|||||||
this._initServer(server);
|
this._initServer(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
// common
|
// https_cert, https_key
|
||||||
|
if (this.is_client && this.local_protocol === 'https') {
|
||||||
|
logger.info(`[config] loading ${json.https_cert}`);
|
||||||
|
this.https_cert = loadFileSync(json.https_cert);
|
||||||
|
logger.info(`[config] loading ${json.https_key}`);
|
||||||
|
this.https_key = loadFileSync(json.https_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// common
|
||||||
this.timeout = (json.timeout !== undefined) ? json.timeout * 1e3 : 600 * 1e3;
|
this.timeout = (json.timeout !== undefined) ? json.timeout * 1e3 : 600 * 1e3;
|
||||||
this.dns_expire = (json.dns_expire !== undefined) ? json.dns_expire * 1e3 : DNS_DEFAULT_EXPIRE;
|
this.dns_expire = (json.dns_expire !== undefined) ? json.dns_expire * 1e3 : DNS_DEFAULT_EXPIRE;
|
||||||
|
|
||||||
@ -242,18 +254,18 @@ export class Config {
|
|||||||
throw Error('"service" must be provided as "<protocol>://<host>:<port>[?params]"');
|
throw Error('"service" must be provided as "<protocol>://<host>:<port>[?params]"');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { protocol: _protocol, hostname, port, searchParams } = new URL(json.service);
|
const { protocol, hostname, port, searchParams } = new URL(json.service);
|
||||||
|
|
||||||
// service.protocol
|
// service.protocol
|
||||||
if (typeof _protocol !== 'string') {
|
if (typeof protocol !== 'string') {
|
||||||
throw Error('service.protocol is invalid');
|
throw Error('service.protocol is invalid');
|
||||||
}
|
}
|
||||||
|
|
||||||
const protocol = _protocol.slice(0, -1);
|
const proto = protocol.slice(0, -1);
|
||||||
const available_client_protocols = [
|
const available_client_protocols = [
|
||||||
'tcp', 'http', 'socks', 'socks5', 'socks4', 'socks4a',
|
'tcp', 'http', 'https', 'socks', 'socks5', 'socks4', 'socks4a',
|
||||||
];
|
];
|
||||||
if (!available_client_protocols.includes(protocol)) {
|
if (!available_client_protocols.includes(proto)) {
|
||||||
throw Error(`service.protocol must be: ${available_client_protocols.join(', ')}`);
|
throw Error(`service.protocol must be: ${available_client_protocols.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +280,7 @@ export class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// service.query
|
// service.query
|
||||||
if (protocol === 'tcp') {
|
if (proto === 'tcp') {
|
||||||
const forward = searchParams.get('forward');
|
const forward = searchParams.get('forward');
|
||||||
|
|
||||||
// ?forward
|
// ?forward
|
||||||
@ -285,6 +297,16 @@ export class Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https_cert, https_key
|
||||||
|
if (proto === 'https') {
|
||||||
|
if (typeof json.https_cert !== 'string' || json.https_cert === '') {
|
||||||
|
throw Error('"https_cert" must be provided');
|
||||||
|
}
|
||||||
|
if (typeof json.https_key !== 'string' || json.https_key === '') {
|
||||||
|
throw Error('"https_key" must be provided');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// server
|
// server
|
||||||
let server;
|
let server;
|
||||||
// TODO(remove in next version): make backwards compatibility to "json.servers"
|
// TODO(remove in next version): make backwards compatibility to "json.servers"
|
||||||
|
@ -150,6 +150,7 @@ export class Hub {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const { local_protocol, local_search_params, local_host, local_port } = this._config;
|
const { local_protocol, local_search_params, local_host, local_port } = this._config;
|
||||||
const { local_username: username, local_password: password } = this._config;
|
const { local_username: username, local_password: password } = this._config;
|
||||||
|
const { https_key, https_cert } = this._config;
|
||||||
let server = null;
|
let server = null;
|
||||||
switch (local_protocol) {
|
switch (local_protocol) {
|
||||||
case 'tcp': {
|
case 'tcp': {
|
||||||
@ -164,10 +165,22 @@ export class Hub {
|
|||||||
case 'socks5':
|
case 'socks5':
|
||||||
case 'socks4':
|
case 'socks4':
|
||||||
case 'socks4a':
|
case 'socks4a':
|
||||||
server = socks.createServer({ bindAddress: local_host, bindPort: local_port, username, password });
|
server = socks.createServer({
|
||||||
|
bindAddress: local_host,
|
||||||
|
bindPort: local_port,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case 'http':
|
case 'http':
|
||||||
server = httpProxy.createServer({ username, password });
|
case 'https':
|
||||||
|
server = httpProxy.createServer({
|
||||||
|
secure: local_protocol === 'https',
|
||||||
|
https_key,
|
||||||
|
https_cert,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return reject(Error(`unsupported protocol: "${local_protocol}"`));
|
return reject(Error(`unsupported protocol: "${local_protocol}"`));
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
|
import https from 'https';
|
||||||
import { logger, isValidPort } from '../utils';
|
import { logger, isValidPort } from '../utils';
|
||||||
|
|
||||||
function checkBasicAuthorization(credentials, { username, password }) {
|
function checkBasicAuthorization(credentials, { username, password }) {
|
||||||
@ -14,8 +15,15 @@ function checkBasicAuthorization(credentials, { username, password }) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createServer({ username, password }) {
|
export function createServer({ secure, https_key, https_cert, username, password }) {
|
||||||
const server = http.createServer();
|
let name = secure ? 'https' : 'http';
|
||||||
|
let server = null;
|
||||||
|
if (secure) {
|
||||||
|
server = https.createServer({ key: https_key, cert: https_cert });
|
||||||
|
} else {
|
||||||
|
server = http.createServer();
|
||||||
|
}
|
||||||
|
|
||||||
const isAuthRequired = username !== '' && password !== '';
|
const isAuthRequired = username !== '' && password !== '';
|
||||||
|
|
||||||
// Simple HTTP Proxy
|
// Simple HTTP Proxy
|
||||||
@ -28,7 +36,7 @@ export function createServer({ username, password }) {
|
|||||||
|
|
||||||
if (hostname === null || !isValidPort(_port)) {
|
if (hostname === null || !isValidPort(_port)) {
|
||||||
const remote = `${socket.remoteAddress}:${socket.remotePort}`;
|
const remote = `${socket.remoteAddress}:${socket.remotePort}`;
|
||||||
logger.warn(`[http] drop invalid http request sent from ${remote}`);
|
logger.warn(`[${name}] drop invalid http request sent from ${remote}`);
|
||||||
return res.end();
|
return res.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +45,7 @@ export function createServer({ username, password }) {
|
|||||||
const proxyAuth = headers['proxy-authorization'] || '';
|
const proxyAuth = headers['proxy-authorization'] || '';
|
||||||
const [type, credentials] = proxyAuth.split(' ');
|
const [type, credentials] = proxyAuth.split(' ');
|
||||||
if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) {
|
if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) {
|
||||||
logger.error(`[http] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`);
|
logger.error(`[${name}] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`);
|
||||||
return res.end('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
return res.end('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,7 +72,7 @@ export function createServer({ username, password }) {
|
|||||||
|
|
||||||
// free to recv from application now
|
// free to recv from application now
|
||||||
socket.resume();
|
socket.resume();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -77,7 +85,7 @@ export function createServer({ username, password }) {
|
|||||||
|
|
||||||
if (hostname === null || !isValidPort(port)) {
|
if (hostname === null || !isValidPort(port)) {
|
||||||
const remote = `${socket.remoteAddress}:${socket.remotePort}`;
|
const remote = `${socket.remoteAddress}:${socket.remotePort}`;
|
||||||
logger.warn(`[http] [${appAddress}] drop invalid http CONNECT request sent from ${remote}`);
|
logger.warn(`[${name}] [${appAddress}] drop invalid http CONNECT request sent from ${remote}`);
|
||||||
return socket.destroy();
|
return socket.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +94,7 @@ export function createServer({ username, password }) {
|
|||||||
const proxyAuth = req.headers['proxy-authorization'] || '';
|
const proxyAuth = req.headers['proxy-authorization'] || '';
|
||||||
const [type, credentials] = proxyAuth.split(' ');
|
const [type, credentials] = proxyAuth.split(' ');
|
||||||
if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) {
|
if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) {
|
||||||
logger.error(`[http] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`);
|
logger.error(`[${name}] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`);
|
||||||
return socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
return socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,14 +104,14 @@ export function createServer({ username, password }) {
|
|||||||
port: port,
|
port: port,
|
||||||
onConnected: () => {
|
onConnected: () => {
|
||||||
socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
|
socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// errors
|
// errors
|
||||||
server.on('clientError', (err, socket) => {
|
server.on('clientError', (err, socket) => {
|
||||||
const appAddress = `${socket.remoteAddress}:${socket.remotePort}`;
|
const appAddress = `${socket.remoteAddress || ''}:${socket.remotePort || ''}`;
|
||||||
logger.error(`[http] [${appAddress}] invalid http request: ${err.message}`);
|
logger.error(`[${name}] [${appAddress}] invalid http request: ${err.message}`);
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user