feat(core): Add support for tagging iptables rules with comments and cleaning them up on process exit

This commit is contained in:
Philipp Kunz 2025-02-25 00:56:01 +00:00
parent aa756bd698
commit a1051f78e8
6 changed files with 277 additions and 51 deletions

View File

@ -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

View File

@ -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"
]
}
},

View File

@ -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
View File

@ -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.

View 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.'
}

View File

@ -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}`);
}
}
}