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_concurrency': 10,
|
||||
},
|
||||
'https_key': 'https_key.pem',
|
||||
'https_cert': 'https_cert.pem',
|
||||
'dns': [],
|
||||
'dns_expire': 3600,
|
||||
'timeout': timeout,
|
||||
|
@ -30,6 +30,9 @@ export class Config {
|
||||
is_client = null;
|
||||
is_server = null;
|
||||
|
||||
https_key = null;
|
||||
https_cert = null;
|
||||
|
||||
timeout = null;
|
||||
redirect = null;
|
||||
|
||||
@ -66,6 +69,7 @@ export class Config {
|
||||
stores = [];
|
||||
|
||||
constructor(json) {
|
||||
// service
|
||||
const { protocol, hostname, port, pathname, searchParams, username, password } = new URL(json.service);
|
||||
this.local_protocol = protocol.slice(0, -1);
|
||||
this.local_username = username;
|
||||
@ -75,6 +79,7 @@ export class Config {
|
||||
this.local_port = +port;
|
||||
this.local_pathname = pathname;
|
||||
|
||||
// server
|
||||
let server;
|
||||
// TODO(remove in next version): make backwards compatibility to "json.servers"
|
||||
if (json.servers !== undefined) {
|
||||
@ -105,8 +110,15 @@ export class Config {
|
||||
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.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]"');
|
||||
}
|
||||
|
||||
const { protocol: _protocol, hostname, port, searchParams } = new URL(json.service);
|
||||
const { protocol, hostname, port, searchParams } = new URL(json.service);
|
||||
|
||||
// service.protocol
|
||||
if (typeof _protocol !== 'string') {
|
||||
if (typeof protocol !== 'string') {
|
||||
throw Error('service.protocol is invalid');
|
||||
}
|
||||
|
||||
const protocol = _protocol.slice(0, -1);
|
||||
const proto = protocol.slice(0, -1);
|
||||
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(', ')}`);
|
||||
}
|
||||
|
||||
@ -268,7 +280,7 @@ export class Config {
|
||||
}
|
||||
|
||||
// service.query
|
||||
if (protocol === 'tcp') {
|
||||
if (proto === 'tcp') {
|
||||
const forward = searchParams.get('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
|
||||
let server;
|
||||
// TODO(remove in next version): make backwards compatibility to "json.servers"
|
||||
|
@ -150,6 +150,7 @@ export class Hub {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { local_protocol, local_search_params, local_host, local_port } = this._config;
|
||||
const { local_username: username, local_password: password } = this._config;
|
||||
const { https_key, https_cert } = this._config;
|
||||
let server = null;
|
||||
switch (local_protocol) {
|
||||
case 'tcp': {
|
||||
@ -164,10 +165,22 @@ export class Hub {
|
||||
case 'socks5':
|
||||
case 'socks4':
|
||||
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;
|
||||
case 'http':
|
||||
server = httpProxy.createServer({ username, password });
|
||||
case 'https':
|
||||
server = httpProxy.createServer({
|
||||
secure: local_protocol === 'https',
|
||||
https_key,
|
||||
https_cert,
|
||||
username,
|
||||
password,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
return reject(Error(`unsupported protocol: "${local_protocol}"`));
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { URL } from 'url';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import { logger, isValidPort } from '../utils';
|
||||
|
||||
function checkBasicAuthorization(credentials, { username, password }) {
|
||||
@ -14,8 +15,15 @@ function checkBasicAuthorization(credentials, { username, password }) {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function createServer({ username, password }) {
|
||||
const server = http.createServer();
|
||||
export function createServer({ secure, https_key, https_cert, username, password }) {
|
||||
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 !== '';
|
||||
|
||||
// Simple HTTP Proxy
|
||||
@ -28,7 +36,7 @@ export function createServer({ username, password }) {
|
||||
|
||||
if (hostname === null || !isValidPort(_port)) {
|
||||
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();
|
||||
}
|
||||
|
||||
@ -37,7 +45,7 @@ export function createServer({ username, password }) {
|
||||
const proxyAuth = headers['proxy-authorization'] || '';
|
||||
const [type, credentials] = proxyAuth.split(' ');
|
||||
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');
|
||||
}
|
||||
}
|
||||
@ -64,7 +72,7 @@ export function createServer({ username, password }) {
|
||||
|
||||
// free to recv from application now
|
||||
socket.resume();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@ -77,7 +85,7 @@ export function createServer({ username, password }) {
|
||||
|
||||
if (hostname === null || !isValidPort(port)) {
|
||||
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();
|
||||
}
|
||||
|
||||
@ -86,7 +94,7 @@ export function createServer({ username, password }) {
|
||||
const proxyAuth = req.headers['proxy-authorization'] || '';
|
||||
const [type, credentials] = proxyAuth.split(' ');
|
||||
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');
|
||||
}
|
||||
}
|
||||
@ -96,14 +104,14 @@ export function createServer({ username, password }) {
|
||||
port: port,
|
||||
onConnected: () => {
|
||||
socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// errors
|
||||
server.on('clientError', (err, socket) => {
|
||||
const appAddress = `${socket.remoteAddress}:${socket.remotePort}`;
|
||||
logger.error(`[http] [${appAddress}] invalid http request: ${err.message}`);
|
||||
const appAddress = `${socket.remoteAddress || ''}:${socket.remotePort || ''}`;
|
||||
logger.error(`[${name}] [${appAddress}] invalid http request: ${err.message}`);
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user