From e5f24ea2f19d0f93d571539cda4b10aa71fd0451 Mon Sep 17 00:00:00 2001 From: dumbmoron Date: Sun, 12 May 2024 11:54:44 +0000 Subject: [PATCH 1/4] add createSocket method for manually specifying IP --- dispatcher.js | 40 ++++++++++++++++++++++++++++++++++++++-- ip.js | 6 ++---- socket.js | 36 ++++++++++++++++++++++++------------ 3 files changed, 64 insertions(+), 18 deletions(-) diff --git a/dispatcher.js b/dispatcher.js index b99779c..33de654 100644 --- a/dispatcher.js +++ b/dispatcher.js @@ -1,4 +1,5 @@ -import { createRandomSocket } from './socket.js' +import { generateRandomIP } from './ip.js'; +import { createRandomSocket, createSocketFromHostname } from './socket.js' import { Agent, buildConnector } from 'undici' // taken from `fetch-socks`, many thanks @@ -6,6 +7,30 @@ function resolvePort(protocol, port) { return port ? Number.parseInt(port) : protocol === "http:" ? 80 : 443 } +export function createConnectorFromIP(ip, tlsOpts = {}, sockOpts = {}) { + const undiciConnect = buildConnector(tlsOpts) + + return async (options, callback) => { + let { protocol, hostname, port } = options; + + return undiciConnect({ + ...options, + httpSocket: await createSocketFromHostname( + hostname, + resolvePort(protocol, port), + ip, + sockOpts + ) + }, callback); + }; +} + +export function createRandomStickyConnector(cidr, tlsOpts, options) { + const { bits, ...sockOpts } = options; + const ip = generateRandomIP(cidr, bits); + return createConnectorFromIP(ip, tlsOpts, sockOpts); +} + export function createRandomConnector(cidr, tlsOpts = {}, sockOpts = {}) { const undiciConnect = buildConnector(tlsOpts) @@ -24,7 +49,18 @@ export function createRandomConnector(cidr, tlsOpts = {}, sockOpts = {}) { }; } +export function dispatcherFromIP(ip, options = {}) { + const { connect, ...rest } = options + return new Agent({ ...rest, connect: createConnectorFromIP(ip, connect, rest) }) +} + +export function randomStickyDispatcher(cidr, options = {}) { + const { connect, ...rest } = options + return new Agent({ ...rest, connect: createRandomStickyConnector(cidr, connect, rest) }) +} + export function randomDispatcher(cidr, options = {}) { const { connect, ...rest } = options return new Agent({ ...rest, connect: createRandomConnector(cidr, connect, rest) }) -} \ No newline at end of file +} + diff --git a/ip.js b/ip.js index 9b62394..87e6f21 100644 --- a/ip.js +++ b/ip.js @@ -23,9 +23,7 @@ function generateRandomIPArray({ bytes, available }, count = available) { } export function generateRandomIP(cidr, count) { - const addr = ip.fromByteArray( + return ip.fromByteArray( generateRandomIPArray(parseCIDR(cidr), count) - ); - - return { addr: addr.toString(), kind: addr.kind() }; + ).toString(); } \ No newline at end of file diff --git a/socket.js b/socket.js index 65c47b1..b14262e 100644 --- a/socket.js +++ b/socket.js @@ -1,5 +1,5 @@ import sys from 'syscall-napi' -import ipaddr from 'ipaddr.js' +import ip from 'ipaddr.js' import { Socket } from 'node:net' import dns from 'node:dns/promises' import { strict as assert } from 'node:assert' @@ -39,9 +39,10 @@ async function enableFreebind(fd) { // todo: createDgram export async function createSocket(host, port, localAddress, connectOptions) { - const addrFamily = ipaddr.parse(host).kind() - if (connectOptions.strict !== false) - assert(addrFamily == ipaddr.parse(localAddress).kind()) + const addrFamily = ip.parse(host).kind() + if (connectOptions.strict !== false) { + assert(addrFamily == ip.parse(localAddress).kind()) + } const fd = await initSocket(addrFamily, 'tcp'); await enableFreebind(fd); @@ -71,17 +72,28 @@ async function lookup(hostname, family) { return host; } -// `bits` defines how many bits to fill from the MSB to the LSB -// needs to be <= length of the prefix -export async function createRandomSocket(hostname, port, localCIDR, options) { - const { bits, strict, ...connectOptions } = options; - const { addr, kind } = generateRandomIP(localCIDR, bits); +export async function createSocketFromHostname(hostname, port, sourceIP, options) { + const { strict, ...connectOptions } = options; + + const kind = ip.parse(sourceIP).kind(); const family = ({ 'ipv4': 4, 'ipv6': 6 })[ kind ]; const host = await lookup(hostname, family); const familyMatching = family === host.family; - if (!familyMatching && strict !== false) - throw 'family mismatch for addr ' + host.address + if (!familyMatching && strict !== false) { + throw 'family mismatch for addr ' + host.address; + } - return await createSocket(host.address, port, addr, { ...connectOptions, strict, familyMatching }) + return createSocket( + host.address, port, sourceIP, + { ...connectOptions, strict, familyMatching } + ); +} + +// `bits` defines how many bits to fill from the MSB to the LSB +// needs to be <= length of the prefix +export function createRandomSocket(hostname, port, localCIDR, options) { + const { bits, ...rest } = options; + const addr = generateRandomIP(localCIDR, bits); + return createSocketFromHostname(hostname, port, addr, rest); } \ No newline at end of file From c876d23daac2d9083968a3f4235baa8fc132b048 Mon Sep 17 00:00:00 2001 From: dumbmoron Date: Sun, 12 May 2024 11:59:20 +0000 Subject: [PATCH 2/4] workflows: add workflow for package publish --- .github/workflows/package.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/package.yml diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 0000000..86c1224 --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,18 @@ +name: Publish Package to npmjs +on: + release: + types: [published] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Setup .npmrc file to publish to npm + - uses: actions/setup-node@v4 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + - run: npm ci + - run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file From e63e4ae0cc1cd3c59cfa0046b287795ef2918087 Mon Sep 17 00:00:00 2001 From: dumbmoron Date: Sun, 12 May 2024 12:36:49 +0000 Subject: [PATCH 3/4] index: expose generateRandomIP as part of api --- index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.js b/index.js index 06acd0d..568b91f 100644 --- a/index.js +++ b/index.js @@ -1,2 +1,5 @@ +import { generateRandomIP } from './ip.js' + export * as tcp from './socket.js' export * from './dispatcher.js' +export const ip = { random: generateRandomIP }; \ No newline at end of file From 13b4c18cd742404f0e4940ab842e7243f48b6ac1 Mon Sep 17 00:00:00 2001 From: dumbmoron Date: Sun, 12 May 2024 12:37:02 +0000 Subject: [PATCH 4/4] package.json: bump version to 0.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b5b7e16..2be780e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freebind", - "version": "0.1.2", + "version": "0.2.0", "description": "bind sockets to random IP addresses from specified prefixes", "keywords": ["anyip", "rate-limit", "socket", "ipv6", "ratelimiting"], "type": "module",