feat(core): Add support for tagging iptables rules with comments and cleaning them up on process exit
This commit is contained in:
parent
aa756bd698
commit
a1051f78e8
@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-02-25 - 3.13.0 - feat(core)
|
||||
Add support for tagging iptables rules with comments and cleaning them up on process exit
|
||||
|
||||
- Extended IPTablesProxy class to include tagging rules with unique comments.
|
||||
- Added feature to clean up iptables rules via comments during process exit.
|
||||
|
||||
## 2025-02-24 - 3.12.0 - feat(IPTablesProxy)
|
||||
Introduce IPTablesProxy class for managing iptables NAT rules
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "push.rocks",
|
||||
"gitrepo": "smartproxy",
|
||||
"description": "a proxy for handling high workloads of proxying",
|
||||
"description": "A robust and versatile proxy package designed to handle high workloads, offering features like SSL redirection, port proxying, WebSocket support, and customizable routing and authentication.",
|
||||
"npmPackagename": "@push.rocks/smartproxy",
|
||||
"license": "MIT",
|
||||
"projectDomain": "push.rocks",
|
||||
@ -20,7 +20,11 @@
|
||||
"ssl redirect",
|
||||
"port mapping",
|
||||
"reverse proxy",
|
||||
"authentication"
|
||||
"authentication",
|
||||
"dynamic routing",
|
||||
"sni",
|
||||
"port forwarding",
|
||||
"real-time applications"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
10
package.json
10
package.json
@ -2,7 +2,7 @@
|
||||
"name": "@push.rocks/smartproxy",
|
||||
"version": "3.12.0",
|
||||
"private": false,
|
||||
"description": "a proxy for handling high workloads of proxying",
|
||||
"description": "A robust and versatile proxy package designed to handle high workloads, offering features like SSL redirection, port proxying, WebSocket support, and customizable routing and authentication.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"type": "module",
|
||||
@ -62,7 +62,11 @@
|
||||
"ssl redirect",
|
||||
"port mapping",
|
||||
"reverse proxy",
|
||||
"authentication"
|
||||
"authentication",
|
||||
"dynamic routing",
|
||||
"sni",
|
||||
"port forwarding",
|
||||
"real-time applications"
|
||||
],
|
||||
"homepage": "https://code.foss.global/push.rocks/smartproxy#readme",
|
||||
"repository": {
|
||||
@ -75,4 +79,4 @@
|
||||
"pnpm": {
|
||||
"overrides": {}
|
||||
}
|
||||
}
|
||||
}
|
189
readme.md
189
readme.md
@ -14,11 +14,11 @@ This will add `@push.rocks/smartproxy` to your project's dependencies.
|
||||
|
||||
## Usage
|
||||
|
||||
`@push.rocks/smartproxy` is a versatile package for setting up and handling proxies with various capabilities such as SSL redirection, port proxying, and creating network proxies with complex routing rules. Below is a comprehensive guide on using its features.
|
||||
`@push.rocks/smartproxy` is a comprehensive and versatile package designed to handle complex and high-volume proxying tasks efficiently. It includes features such as SSL redirection, port proxying, WebSocket support, and customizable routing and authentication mechanisms. This guide will provide a detailed walkthrough of how to harness these capabilities effectively.
|
||||
|
||||
### Setting Up a Network Proxy
|
||||
### Initial Setup
|
||||
|
||||
Create a network proxy to route incoming HTTPS requests to different local servers based on the hostname.
|
||||
Before diving into specific features, let's start by configuring and setting up our basic proxy server:
|
||||
|
||||
```typescript
|
||||
import { NetworkProxy } from '@push.rocks/smartproxy';
|
||||
@ -26,7 +26,7 @@ import { NetworkProxy } from '@push.rocks/smartproxy';
|
||||
// Instantiate the NetworkProxy with desired options
|
||||
const myNetworkProxy = new NetworkProxy({ port: 443 });
|
||||
|
||||
// Define your reverse proxy configurations
|
||||
// Define reverse proxy configurations
|
||||
const proxyConfigs = [
|
||||
{
|
||||
destinationIp: '127.0.0.1',
|
||||
@ -39,70 +39,187 @@ PRIVATE_KEY_CONTENT
|
||||
CERTIFICATE_CONTENT
|
||||
-----END CERTIFICATE-----`,
|
||||
},
|
||||
// Add more reverse proxy configurations here
|
||||
// More configurations can be added here
|
||||
];
|
||||
|
||||
// Start the network proxy
|
||||
await myNetworkProxy.start();
|
||||
|
||||
// Update proxy configurations dynamically
|
||||
// Apply proxy configurations
|
||||
await myNetworkProxy.updateProxyConfigs(proxyConfigs);
|
||||
|
||||
// Optionally, add default headers to all responses
|
||||
// Optionally add default headers to all responses
|
||||
await myNetworkProxy.addDefaultHeaders({
|
||||
'X-Powered-By': 'smartproxy',
|
||||
});
|
||||
```
|
||||
|
||||
### Port Proxying
|
||||
### Configuring SSL Redirection
|
||||
|
||||
You can also set up a port proxy to forward traffic from one port to another, which is useful for dynamic port forwarding scenarios.
|
||||
|
||||
```typescript
|
||||
import { PortProxy } from '@push.rocks/smartproxy';
|
||||
|
||||
// Create a PortProxy to forward traffic from port 5000 to port 3000
|
||||
const myPortProxy = new PortProxy(5000, 3000);
|
||||
|
||||
// Start the port proxy
|
||||
await myPortProxy.start();
|
||||
|
||||
// To stop the port proxy, simply call
|
||||
await myPortProxy.stop();
|
||||
```
|
||||
|
||||
### Enabling SSL Redirection
|
||||
|
||||
Easily redirect HTTP traffic to HTTPS using the `SslRedirect` class. This is particularly useful when ensuring all traffic uses encryption.
|
||||
One essential capability of a robust proxy server is ensuring that all HTTP traffic is redirected to secure HTTPS endpoints. This can be effortlessly accomplished using the `SslRedirect` class within `smartproxy`. This class listens on port 80 (HTTP) and redirects all incoming requests to HTTPS:
|
||||
|
||||
```typescript
|
||||
import { SslRedirect } from '@push.rocks/smartproxy';
|
||||
|
||||
// Instantiate the SslRedirect on port 80 (HTTP)
|
||||
// Instantiate the SslRedirect for listening on port 80
|
||||
const mySslRedirect = new SslRedirect(80);
|
||||
|
||||
// Start listening and redirecting to HTTPS
|
||||
// Start listening and redirect HTTP traffic to HTTPS
|
||||
await mySslRedirect.start();
|
||||
|
||||
// To stop the redirection, use
|
||||
// To stop redirection, you can use the following command:
|
||||
await mySslRedirect.stop();
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
### Handling Complex Networking with Port Proxy
|
||||
|
||||
The package integrates seamlessly with TypeScript, allowing for advanced use cases, such as implementing custom routing logic, authentication mechanisms, and handling WebSocket connections through the network proxy.
|
||||
Port proxying allows redirection of traffic from one port to another. This capability is crucial when dealing with services that need dynamic port forwarding, or when adapting to infrastructure changes without downtime. Smartproxy's `PortProxy` class handles this efficiently:
|
||||
|
||||
For a more advanced setup involving WebSocket proxying and dynamic configuration reloading, refer to the network proxy example provided above. The WebSocket support demonstrates how seamless it is to work with real-time applications.
|
||||
```typescript
|
||||
import { PortProxy } from '@push.rocks/smartproxy';
|
||||
|
||||
Remember, when dealing with certificates and private keys for HTTPS configurations, always secure your keys and store them appropriately.
|
||||
// Create a PortProxy to directly forward traffic from port 5000 to 3000
|
||||
const myPortProxy = new PortProxy(5000, 3000);
|
||||
|
||||
`@push.rocks/smartproxy` provides a solid foundation for handling high workloads and complex proxying requirements with ease, whether you're implementing SSL redirections, port forwarding, or extensive routing and WebSocket support in your network.
|
||||
// Initiate the port proxy
|
||||
await myPortProxy.start();
|
||||
|
||||
For more information on how to use the features, refer to the in-depth documentation available in the package's repository or the npm package description.
|
||||
// To stop the port proxy mechanism:
|
||||
await myPortProxy.stop();
|
||||
```
|
||||
|
||||
Additionally, smartproxy's port proxying can support intricate scenarios where different forwarding rules are configured based on domain names or allowed IPs:
|
||||
|
||||
```typescript
|
||||
import { PortProxy } from '@push.rocks/smartproxy';
|
||||
|
||||
const myComplexPortProxy = new PortProxy({
|
||||
fromPort: 6000,
|
||||
toPort: 3000,
|
||||
domains: [
|
||||
{
|
||||
domain: 'api.example.com',
|
||||
allowedIPs: ['192.168.0.*', '127.0.0.1'],
|
||||
targetIP: '192.168.1.100'
|
||||
}
|
||||
// Define more domain-specific rules if needed
|
||||
],
|
||||
sniEnabled: true, // if SNI (Server Name Indication) is desired
|
||||
defaultAllowedIPs: ['*']);
|
||||
});
|
||||
|
||||
// Start listening for complex routing requests
|
||||
await myComplexPortProxy.start();
|
||||
```
|
||||
|
||||
### WebSocket Support and Load Handling
|
||||
|
||||
With the advent of real-time applications, efficient WebSocket handling in proxies is crucial. Smartproxy integrates WebSocket support seamlessly, enabling it to proxy WebSocket traffic while maintaining security and performance:
|
||||
|
||||
```typescript
|
||||
import { NetworkProxy } from '@push.rocks/smartproxy';
|
||||
|
||||
const wsProxy = new NetworkProxy({ port: 443 });
|
||||
|
||||
// Assume reverse proxy configurations with WebSocket intentions
|
||||
const wsProxyConfigs = [
|
||||
{
|
||||
destinationIp: '127.0.0.1',
|
||||
destinationPort: '8080',
|
||||
hostName: 'socket.example.com',
|
||||
// Add further options such as keys for SSL if needed
|
||||
}
|
||||
];
|
||||
|
||||
// Start the network proxy with WebSocket capabilities
|
||||
await wsProxy.start();
|
||||
await wsProxy.updateProxyConfigs(wsProxyConfigs);
|
||||
|
||||
// Ensure WebSocket connections remain alive
|
||||
wsProxy.heartbeatInterval = setInterval(() => {
|
||||
// logic for keeping connections alive and healthy
|
||||
}, 60000); // Every 60 seconds
|
||||
|
||||
// Gracefully handle server or connection errors to maintain uptime
|
||||
wsProxy.httpsServer.on('error', (error) => console.log('Server Error:', error));
|
||||
```
|
||||
|
||||
### Comprehensive Routing and Advanced Features
|
||||
|
||||
Smartproxy supports dynamic and customizable request routing based on the incoming request's destination. This feature enables extensive use-case scenarios, from simple API endpoint redirection to elaborate B2B service integrations:
|
||||
|
||||
```typescript
|
||||
import { NetworkProxy } from '@push.rocks/smartproxy';
|
||||
|
||||
const dynamicRoutingProxy = new NetworkProxy({ port: 8443 });
|
||||
dynamicRoutingProxy.router.setNewProxyConfigs([
|
||||
{
|
||||
destinationIp: '192.168.1.150',
|
||||
destinationPort: '80',
|
||||
hostName: 'dynamic.example.com',
|
||||
authentication: {
|
||||
type: 'Basic',
|
||||
user: 'admin',
|
||||
pass: 'password123'
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
await dynamicRoutingProxy.start();
|
||||
```
|
||||
|
||||
For those dealing with high volume or regulatory needs, the integration of tools like `iptables` allows broad control over network traffic:
|
||||
|
||||
```typescript
|
||||
import { IPTablesProxy } from '@push.rocks/smartproxy';
|
||||
|
||||
// Setting up iptables for advanced network management
|
||||
const ipTablesProxy = new IPTablesProxy({
|
||||
fromPort: 8081,
|
||||
toPort: 8080,
|
||||
deleteOnExit: true // clean rules upon server shutdown
|
||||
});
|
||||
|
||||
// Begin routing with IPTables
|
||||
await ipTablesProxy.start();
|
||||
```
|
||||
|
||||
### Combining with HTTP and HTTPS Credentials
|
||||
|
||||
When undertaking proxy configurations, handling sensitive data like SSL certificates and keys securely is imperative:
|
||||
|
||||
```typescript
|
||||
import { loadDefaultCertificates } from '@push.rocks/smartproxy';
|
||||
|
||||
try {
|
||||
const { privateKey, publicKey } = loadDefaultCertificates(); // adjust path as needed
|
||||
console.log('Certificates loaded.');
|
||||
// Use these certificates in your SSL-based configurations
|
||||
} catch (error) {
|
||||
console.error('Cannot load certificates:', error);
|
||||
}
|
||||
```
|
||||
|
||||
### Testing and Validation
|
||||
|
||||
Given these powerful capabilities, rigorous testing of configurations and functionality using frameworks like `tap` can ensure high-quality and reliable proxy configurations. Smartproxy integrates with Typescript test setups:
|
||||
|
||||
```typescript
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { NetworkProxy } from '@push.rocks/smartproxy';
|
||||
|
||||
tap.test('proxied request should return status 200', async () => {
|
||||
// Your test logic here
|
||||
});
|
||||
|
||||
tap.start();
|
||||
```
|
||||
|
||||
In summary, `@push.rocks/smartproxy` offers a plethora of solutions tailored to both common and sophisticated proxying needs. Whether you're seeking straightforward port forwarding, secure SSL redirection, WebSocket management, or robust network routing controls, smartproxy provides the right tools for efficient and effective proxy operations. Through its integration simplicity and versatile configurations, developers can ensure high performance and secure proxying across various environments and applications.
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartproxy',
|
||||
version: '3.12.0',
|
||||
description: 'a proxy for handling high workloads of proxying'
|
||||
version: '3.13.0',
|
||||
description: 'A robust and versatile proxy package designed to handle high workloads, offering features like SSL redirection, port proxying, WebSocket support, and customizable routing and authentication.'
|
||||
}
|
||||
|
@ -1,35 +1,65 @@
|
||||
import { exec } from 'child_process';
|
||||
import { exec, execSync } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
/**
|
||||
* Settings for IPTablesProxy.
|
||||
*/
|
||||
export interface IIpTableProxySettings {
|
||||
fromPort: number;
|
||||
toPort: number;
|
||||
toHost?: string; // Target host for proxying; defaults to 'localhost'
|
||||
preserveSourceIP?: boolean; // If true, the original source IP is preserved.
|
||||
deleteOnExit?: boolean; // If true, clean up marked iptables rules before process exit.
|
||||
}
|
||||
|
||||
/**
|
||||
* IPTablesProxy sets up iptables NAT rules to forward TCP traffic.
|
||||
* It only supports basic port forwarding.
|
||||
* It only supports basic port forwarding and uses iptables comments to tag rules.
|
||||
*/
|
||||
export class IPTablesProxy {
|
||||
public settings: IIpTableProxySettings;
|
||||
private rulesInstalled: boolean = false;
|
||||
private ruleTag: string;
|
||||
|
||||
constructor(settings: IIpTableProxySettings) {
|
||||
this.settings = {
|
||||
...settings,
|
||||
toHost: settings.toHost || 'localhost',
|
||||
};
|
||||
// Generate a unique identifier for the rules added by this instance.
|
||||
this.ruleTag = `IPTablesProxy:${Date.now()}:${Math.random().toString(36).substr(2, 5)}`;
|
||||
|
||||
// If deleteOnExit is true, register cleanup handlers.
|
||||
if (this.settings.deleteOnExit) {
|
||||
const cleanup = () => {
|
||||
try {
|
||||
IPTablesProxy.cleanSlateSync();
|
||||
} catch (err) {
|
||||
console.error('Error cleaning iptables rules on exit:', err);
|
||||
}
|
||||
};
|
||||
process.on('exit', cleanup);
|
||||
process.on('SIGINT', () => {
|
||||
cleanup();
|
||||
process.exit();
|
||||
});
|
||||
process.on('SIGTERM', () => {
|
||||
cleanup();
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up iptables rules for port forwarding.
|
||||
* The rules are tagged with a unique comment so that they can be identified later.
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
const dnatCmd = `iptables -t nat -A PREROUTING -p tcp --dport ${this.settings.fromPort} -j DNAT --to-destination ${this.settings.toHost}:${this.settings.toPort}`;
|
||||
const dnatCmd = `iptables -t nat -A PREROUTING -p tcp --dport ${this.settings.fromPort} ` +
|
||||
`-j DNAT --to-destination ${this.settings.toHost}:${this.settings.toPort} ` +
|
||||
`-m comment --comment "${this.ruleTag}:DNAT"`;
|
||||
try {
|
||||
await execAsync(dnatCmd);
|
||||
console.log(`Added iptables rule: ${dnatCmd}`);
|
||||
@ -39,8 +69,11 @@ export class IPTablesProxy {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// If preserveSourceIP is false, add a MASQUERADE rule.
|
||||
if (!this.settings.preserveSourceIP) {
|
||||
const masqueradeCmd = `iptables -t nat -A POSTROUTING -p tcp -d ${this.settings.toHost} --dport ${this.settings.toPort} -j MASQUERADE`;
|
||||
const masqueradeCmd = `iptables -t nat -A POSTROUTING -p tcp -d ${this.settings.toHost} ` +
|
||||
`--dport ${this.settings.toPort} -j MASQUERADE ` +
|
||||
`-m comment --comment "${this.ruleTag}:MASQ"`;
|
||||
try {
|
||||
await execAsync(masqueradeCmd);
|
||||
console.log(`Added iptables rule: ${masqueradeCmd}`);
|
||||
@ -48,7 +81,9 @@ export class IPTablesProxy {
|
||||
console.error(`Failed to add iptables MASQUERADE rule: ${err}`);
|
||||
// Roll back the DNAT rule if MASQUERADE fails.
|
||||
try {
|
||||
const rollbackCmd = `iptables -t nat -D PREROUTING -p tcp --dport ${this.settings.fromPort} -j DNAT --to-destination ${this.settings.toHost}:${this.settings.toPort}`;
|
||||
const rollbackCmd = `iptables -t nat -D PREROUTING -p tcp --dport ${this.settings.fromPort} ` +
|
||||
`-j DNAT --to-destination ${this.settings.toHost}:${this.settings.toPort} ` +
|
||||
`-m comment --comment "${this.ruleTag}:DNAT"`;
|
||||
await execAsync(rollbackCmd);
|
||||
this.rulesInstalled = false;
|
||||
} catch (rollbackErr) {
|
||||
@ -60,12 +95,14 @@ export class IPTablesProxy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the iptables rules that were added in start().
|
||||
* Removes the iptables rules that were added in start(), by matching the unique comment.
|
||||
*/
|
||||
public async stop(): Promise<void> {
|
||||
if (!this.rulesInstalled) return;
|
||||
|
||||
const dnatDelCmd = `iptables -t nat -D PREROUTING -p tcp --dport ${this.settings.fromPort} -j DNAT --to-destination ${this.settings.toHost}:${this.settings.toPort}`;
|
||||
const dnatDelCmd = `iptables -t nat -D PREROUTING -p tcp --dport ${this.settings.fromPort} ` +
|
||||
`-j DNAT --to-destination ${this.settings.toHost}:${this.settings.toPort} ` +
|
||||
`-m comment --comment "${this.ruleTag}:DNAT"`;
|
||||
try {
|
||||
await execAsync(dnatDelCmd);
|
||||
console.log(`Removed iptables rule: ${dnatDelCmd}`);
|
||||
@ -74,7 +111,9 @@ export class IPTablesProxy {
|
||||
}
|
||||
|
||||
if (!this.settings.preserveSourceIP) {
|
||||
const masqueradeDelCmd = `iptables -t nat -D POSTROUTING -p tcp -d ${this.settings.toHost} --dport ${this.settings.toPort} -j MASQUERADE`;
|
||||
const masqueradeDelCmd = `iptables -t nat -D POSTROUTING -p tcp -d ${this.settings.toHost} ` +
|
||||
`--dport ${this.settings.toPort} -j MASQUERADE ` +
|
||||
`-m comment --comment "${this.ruleTag}:MASQ"`;
|
||||
try {
|
||||
await execAsync(masqueradeDelCmd);
|
||||
console.log(`Removed iptables rule: ${masqueradeDelCmd}`);
|
||||
@ -85,4 +124,60 @@ export class IPTablesProxy {
|
||||
|
||||
this.rulesInstalled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously cleans up any iptables rules in the nat table that were added by this module.
|
||||
* It looks for rules with comments containing "IPTablesProxy:".
|
||||
*/
|
||||
public static async cleanSlate(): Promise<void> {
|
||||
try {
|
||||
const { stdout } = await execAsync('iptables-save -t nat');
|
||||
const lines = stdout.split('\n');
|
||||
const proxyLines = lines.filter(line => line.includes('IPTablesProxy:'));
|
||||
for (const line of proxyLines) {
|
||||
const trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith('-A')) {
|
||||
// Replace the "-A" with "-D" to form a deletion command.
|
||||
const deleteRule = trimmedLine.replace('-A', '-D');
|
||||
const cmd = `iptables -t nat ${deleteRule}`;
|
||||
try {
|
||||
await execAsync(cmd);
|
||||
console.log(`Cleaned up iptables rule: ${cmd}`);
|
||||
} catch (err) {
|
||||
console.error(`Failed to remove iptables rule: ${cmd}`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to run iptables-save: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously cleans up any iptables rules in the nat table that were added by this module.
|
||||
* It looks for rules with comments containing "IPTablesProxy:".
|
||||
* This method is intended for use in process exit handlers.
|
||||
*/
|
||||
public static cleanSlateSync(): void {
|
||||
try {
|
||||
const stdout = execSync('iptables-save -t nat').toString();
|
||||
const lines = stdout.split('\n');
|
||||
const proxyLines = lines.filter(line => line.includes('IPTablesProxy:'));
|
||||
for (const line of proxyLines) {
|
||||
const trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith('-A')) {
|
||||
const deleteRule = trimmedLine.replace('-A', '-D');
|
||||
const cmd = `iptables -t nat ${deleteRule}`;
|
||||
try {
|
||||
execSync(cmd);
|
||||
console.log(`Cleaned up iptables rule: ${cmd}`);
|
||||
} catch (err) {
|
||||
console.error(`Failed to remove iptables rule: ${cmd}`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to run iptables-save: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user