Skip to content

Commit

Permalink
feat: implement the family flag for restricting tcp family (#35)
Browse files Browse the repository at this point in the history
this allows requests to be forced to either ipv4 or ipv6
  • Loading branch information
nlf authored May 9, 2023
1 parent ac89410 commit 471f70b
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 6 deletions.
1 change: 1 addition & 0 deletions lib/proxy/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class HttpProxy {
delete opts.path
// we also delete the timeout since we control it ourselves
delete opts.timeout
opts.family = this.agent.options.family
opts.lookup = this.lookup

if (this.url.protocol === 'https:') {
Expand Down
4 changes: 2 additions & 2 deletions lib/proxy/null.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ class NullProxy {

createConnection (options, callback) {
const socket = this.secure
? tls.connect({ ...options, lookup: this.lookup })
: net.connect({ ...options, lookup: this.lookup })
? tls.connect({ ...options, family: this.agent.options.family, lookup: this.lookup })
: net.connect({ ...options, family: this.agent.options.family, lookup: this.lookup })

socket.setKeepAlive(this.agent.keepAlive, this.agent.keepAliveMsecs)
socket.setNoDelay(this.agent.keepAlive)
Expand Down
1 change: 1 addition & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const normalizeOptions = (_options) => {
delete options.timeout
}

options.family = !isNaN(+options.family) ? +options.family : 0
options.dns = {
ttl: 5 * 60 * 1000,
lookup: dns.lookup,
Expand Down
16 changes: 14 additions & 2 deletions test/fixtures/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ const _onConnect = Symbol('Proxy._onConnect')
const _onConnection = Symbol('Proxy._onConnection')

class Proxy extends EventEmitter {
constructor ({ auth, tls: _tls, failConnect } = {}) {
constructor ({ auth, tls: _tls, family, failConnect } = {}) {
super()
this.family = typeof family === 'number' ? family : 0
this.failConnect = !!failConnect
this.auth = !!auth
if (this.auth) {
Expand All @@ -42,7 +43,18 @@ class Proxy extends EventEmitter {
}

async start () {
this.server.listen(0, 'localhost')
let host
if (this.family === 0) {
host = 'localhost'
} else if (this.family === 4) {
host = '127.0.0.1'
} else if (this.family === 6) {
host = '::1'
}
this.server.listen({
port: 0,
host,
})
await once(this.server, 'listening')

const address = this.server.address()
Expand Down
16 changes: 14 additions & 2 deletions test/fixtures/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ const _onConnection = Symbol('Server._onConnection')
const _onRequest = Symbol('Server._onRequest')

class Server extends EventEmitter {
constructor ({ auth, tls, responseDelay, idleDelay, transferDelay } = {}) {
constructor ({ auth, tls, family, responseDelay, idleDelay, transferDelay } = {}) {
super()
this.family = typeof family === 'number' ? family : 0
this.responseDelay = responseDelay || 0
this.idleDelay = idleDelay || 0
this.transferDelay = transferDelay || 0
Expand Down Expand Up @@ -40,7 +41,18 @@ class Server extends EventEmitter {
}

async start () {
this.server.listen(0, 'localhost')
let host
if (this.family === 0) {
host = 'localhost'
} else if (this.family === 4) {
host = '127.0.0.1'
} else if (this.family === 6) {
host = '::1'
}
this.server.listen({
port: 0,
host,
})
await once(this.server, 'listening')

const address = this.server.address()
Expand Down
96 changes: 96 additions & 0 deletions test/http-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,54 @@ t.test('http destination', (t) => {
t.equal(res.result, 'OK!')
})

t.test('single request ipv4 only', async (t) => {
const server = new Server()
await server.start()

const proxy = new Proxy({ family: 4 })
await proxy.start()

t.teardown(async () => {
await server.stop()
await proxy.stop()
})

const agent = new HttpAgent({ family: 4, proxy: proxy.address })
const client = new Client(agent, server.address)

const res = await client.get('/')
t.equal(res.status, 200)
t.equal(res.result, 'OK!')

const mismatchAgent = new HttpAgent({ family: 6, proxy: proxy.address })
const mismatchClient = new Client(mismatchAgent, server.address)
await t.rejects(mismatchClient.get('/'), { code: 'ECONNREFUSED' })
})

t.test('single request ipv6 only', async (t) => {
const server = new Server()
await server.start()

const proxy = new Proxy({ family: 6 })
await proxy.start()

t.teardown(async () => {
await server.stop()
await proxy.stop()
})

const agent = new HttpAgent({ family: 6, proxy: proxy.address })
const client = new Client(agent, server.address)

const res = await client.get('/')
t.equal(res.status, 200)
t.equal(res.result, 'OK!')

const mismatchAgent = new HttpAgent({ family: 4, proxy: proxy.address })
const mismatchClient = new Client(mismatchAgent, server.address)
await t.rejects(mismatchClient.get('/'), { code: 'ECONNREFUSED' })
})

t.test('can disable keep-alive', async (t) => {
const server = new Server()
await server.start()
Expand Down Expand Up @@ -305,6 +353,54 @@ t.test('https destination', (t) => {
t.equal(res.result, 'OK!')
})

t.test('single request ipv4 only', async (t) => {
const server = new Server({ tls: true })
await server.start()

const proxy = new Proxy({ family: 4 })
await proxy.start()

t.teardown(async () => {
await server.stop()
await proxy.stop()
})

const agent = new HttpsAgent({ family: 4, proxy: proxy.address })
const client = new Client(agent, server.address)

const res = await client.get('/')
t.equal(res.status, 200)
t.equal(res.result, 'OK!')

const mismatchAgent = new HttpsAgent({ family: 6, proxy: proxy.address })
const mismatchClient = new Client(mismatchAgent, server.address)
await t.rejects(mismatchClient.get('/'), { code: 'ECONNREFUSED' })
})

t.test('single request ipv6 only', async (t) => {
const server = new Server({ tls: true })
await server.start()

const proxy = new Proxy({ family: 6 })
await proxy.start()

t.teardown(async () => {
await server.stop()
await proxy.stop()
})

const agent = new HttpsAgent({ family: 6, proxy: proxy.address })
const client = new Client(agent, server.address)

const res = await client.get('/')
t.equal(res.status, 200)
t.equal(res.result, 'OK!')

const mismatchAgent = new HttpsAgent({ family: 4, proxy: proxy.address })
const mismatchClient = new Client(mismatchAgent, server.address)
await t.rejects(mismatchClient.get('/'), { code: 'ECONNREFUSED' })
})

t.test('can disable keep-alive', async (t) => {
const server = new Server({ tls: true })
await server.start()
Expand Down
96 changes: 96 additions & 0 deletions test/https-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,54 @@ t.test('http destination', (t) => {
t.equal(res.result, 'OK!')
})

t.test('single request ipv4 only', async (t) => {
const server = new Server()
await server.start()

const proxy = new Proxy({ family: 4, tls: true })
await proxy.start()

t.teardown(async () => {
await server.stop()
await proxy.stop()
})

const agent = new HttpAgent({ family: 4, proxy: proxy.address })
const client = new Client(agent, server.address)

const res = await client.get('/')
t.equal(res.status, 200)
t.equal(res.result, 'OK!')

const mismatchAgent = new HttpAgent({ family: 6, proxy: proxy.address })
const mismatchClient = new Client(mismatchAgent, server.address)
await t.rejects(mismatchClient.get('/'), { code: 'ECONNREFUSED' })
})

t.test('single request ipv6 only', async (t) => {
const server = new Server()
await server.start()

const proxy = new Proxy({ family: 6, tls: true })
await proxy.start()

t.teardown(async () => {
await server.stop()
await proxy.stop()
})

const agent = new HttpAgent({ family: 6, proxy: proxy.address })
const client = new Client(agent, server.address)

const res = await client.get('/')
t.equal(res.status, 200)
t.equal(res.result, 'OK!')

const mismatchAgent = new HttpAgent({ family: 4, proxy: proxy.address })
const mismatchClient = new Client(mismatchAgent, server.address)
await t.rejects(mismatchClient.get('/'), { code: 'ECONNREFUSED' })
})

t.test('can disable keep-alive', async (t) => {
const server = new Server()
await server.start()
Expand Down Expand Up @@ -325,6 +373,54 @@ t.test('https destination', (t) => {
t.equal(res.result, 'OK!')
})

t.test('single request ipv4 only', async (t) => {
const server = new Server({ tls: true })
await server.start()

const proxy = new Proxy({ family: 4, tls: true })
await proxy.start()

t.teardown(async () => {
await server.stop()
await proxy.stop()
})

const agent = new HttpsAgent({ family: 4, proxy: proxy.address })
const client = new Client(agent, server.address)

const res = await client.get('/')
t.equal(res.status, 200)
t.equal(res.result, 'OK!')

const mismatchAgent = new HttpsAgent({ family: 6, proxy: proxy.address })
const mismatchClient = new Client(mismatchAgent, server.address)
await t.rejects(mismatchClient.get('/'), { code: 'ECONNREFUSED' })
})

t.test('single request ipv6 only', async (t) => {
const server = new Server({ tls: true })
await server.start()

const proxy = new Proxy({ family: 6, tls: true })
await proxy.start()

t.teardown(async () => {
await server.stop()
await proxy.stop()
})

const agent = new HttpsAgent({ family: 6, proxy: proxy.address })
const client = new Client(agent, server.address)

const res = await client.get('/')
t.equal(res.status, 200)
t.equal(res.result, 'OK!')

const mismatchAgent = new HttpsAgent({ family: 4, proxy: proxy.address })
const mismatchClient = new Client(mismatchAgent, server.address)
await t.rejects(mismatchClient.get('/'), { code: 'ECONNREFUSED' })
})

t.test('can disable keep-alive', async (t) => {
const server = new Server({ tls: true })
await server.start()
Expand Down
Loading

0 comments on commit 471f70b

Please sign in to comment.