Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 48fd822

Browse files
authored
Support Forwarded header for IP extraction (#6248)
1 parent 024b9aa commit 48fd822

File tree

2 files changed

+246
-3
lines changed

2 files changed

+246
-3
lines changed

packages/dd-trace/src/plugins/util/ip_extractor.js

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
const { BlockList } = require('net')
44
const net = require('net')
55

6+
const FORWARED_HEADER_NAME = 'forwarded'
7+
68
const ipHeaderList = [
79
'x-forwarded-for',
810
'x-real-ip',
911
'true-client-ip',
1012
'x-client-ip',
13+
FORWARED_HEADER_NAME,
1114
'forwarded-for',
1215
'x-cluster-client-ip',
1316
'fastly-client-ip',
@@ -49,7 +52,8 @@ function extractIp (config, req) {
4952
let firstPrivateIp
5053
if (headers) {
5154
for (const ipHeaderName of ipHeaderList) {
52-
const firstIp = findFirstIp(headers[ipHeaderName])
55+
const header = headers[ipHeaderName]
56+
const firstIp = ipHeaderName === FORWARED_HEADER_NAME ? findFirstIpForwardedFormat(header) : findFirstIp(header)
5357

5458
if (firstIp.public) {
5559
return firstIp.public
@@ -62,6 +66,10 @@ function extractIp (config, req) {
6266
return firstPrivateIp || req.socket?.remoteAddress
6367
}
6468

69+
function isPublicIp (ip, type) {
70+
return !privateIPMatcher.check(ip, type === 6 ? 'ipv6' : 'ipv4')
71+
}
72+
6573
function findFirstIp (str) {
6674
const result = {}
6775
if (!str) return result
@@ -76,10 +84,10 @@ function findFirstIp (str) {
7684
const type = net.isIP(chunk)
7785
if (!type) continue
7886

79-
if (!privateIPMatcher.check(chunk, type === 6 ? 'ipv6' : 'ipv4')) {
87+
if (isPublicIp(chunk, type)) {
8088
// it's public, return it immediately
8189
result.public = chunk
82-
break
90+
return result
8391
}
8492

8593
// it's private, only save the first one found
@@ -89,6 +97,39 @@ function findFirstIp (str) {
8997
return result
9098
}
9199

100+
const forwardedForRegexp = /for="?\[?(([0-9]+\.)+[0-9]+|[0-9a-f:]*:[0-9a-f]*)/i
101+
const forwardedByRegexp = /by="?\[?(([0-9]+\.)+[0-9]+|[0-9a-f:]*:[0-9a-f]*)/i
102+
const forwardedRegexps = [forwardedForRegexp, forwardedByRegexp]
103+
104+
function findFirstIpForwardedFormat (str) {
105+
const result = {}
106+
if (!str) return result
107+
108+
const splitted = str.split(',')
109+
110+
for (const part of splitted) {
111+
const chunk = part.trim()
112+
113+
for (const regex of forwardedRegexps) {
114+
const ip = regex.exec(chunk)?.[1]
115+
116+
const type = net.isIP(ip)
117+
if (!type) continue
118+
119+
if (isPublicIp(ip, type)) {
120+
// it's public, return it immediately
121+
result.public = ip
122+
return result
123+
}
124+
125+
// it's private, only save the first one found
126+
if (!result.private) result.private = ip
127+
}
128+
}
129+
130+
return result
131+
}
132+
92133
module.exports = {
93134
extractIp,
94135
ipHeaderList

packages/dd-trace/test/plugins/util/ip_extractor.spec.js

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,206 @@ describe('ip extractor', () => {
290290
}
291291
}).catch(done)
292292
})
293+
294+
describe('Forwarded header', () => {
295+
it('should detect ip in header \'Forwarded\' in for', (done) => {
296+
const expectedIp = '1.2.3.4'
297+
controller = function (req) {
298+
const ip = extractIp({}, req)
299+
try {
300+
expect(ip).to.be.equal(expectedIp)
301+
done()
302+
} catch (e) {
303+
done(e)
304+
}
305+
}
306+
axios.get(`http://localhost:${port}/`, {
307+
headers: {
308+
Forwarded: `for=${expectedIp}`
309+
}
310+
}).catch(done)
311+
})
312+
313+
it('should detect ip in header \'Forwarded\' in by', (done) => {
314+
const expectedIp = '1.2.3.4'
315+
controller = function (req) {
316+
const ip = extractIp({}, req)
317+
try {
318+
expect(ip).to.be.equal(expectedIp)
319+
done()
320+
} catch (e) {
321+
done(e)
322+
}
323+
}
324+
axios.get(`http://localhost:${port}/`, {
325+
headers: {
326+
Forwarded: `by=${expectedIp}`
327+
}
328+
}).catch(done)
329+
})
330+
331+
it('should detect ipv6 in header \'Forwarded\' in for', (done) => {
332+
const expectedIp = '5a54:f844:006c:b8f1:0e96:9e54:54ac:4a2d'
333+
controller = function (req) {
334+
const ip = extractIp({}, req)
335+
try {
336+
expect(ip).to.be.equal(expectedIp)
337+
done()
338+
} catch (e) {
339+
done(e)
340+
}
341+
}
342+
axios.get(`http://localhost:${port}/`, {
343+
headers: {
344+
Forwarded: `for="[${expectedIp}]"`
345+
}
346+
}).catch(done)
347+
})
348+
349+
it('should detect ipv6 in header \'Forwarded\' in by', (done) => {
350+
const expectedIp = '5a54:f844:006c:b8f1:0e96:9e54:54ac:4a2d'
351+
controller = function (req) {
352+
const ip = extractIp({}, req)
353+
try {
354+
expect(ip).to.be.equal(expectedIp)
355+
done()
356+
} catch (e) {
357+
done(e)
358+
}
359+
}
360+
axios.get(`http://localhost:${port}/`, {
361+
headers: {
362+
Forwarded: `by="[${expectedIp}]"`
363+
}
364+
}).catch(done)
365+
})
366+
367+
it('should detect ip in header \'Forwarded\' in by when for is private', (done) => {
368+
const expectedIp = '1.2.3.4'
369+
controller = function (req) {
370+
const ip = extractIp({}, req)
371+
try {
372+
expect(ip).to.be.equal(expectedIp)
373+
done()
374+
} catch (e) {
375+
done(e)
376+
}
377+
}
378+
axios.get(`http://localhost:${port}/`, {
379+
headers: {
380+
Forwarded: `for=192.168.0.1;by=${expectedIp}`
381+
}
382+
}).catch(done)
383+
})
384+
385+
it('should detect ip in header \'Forwarded\' in for when for and by are public', (done) => {
386+
const expectedIp = '1.2.3.4'
387+
controller = function (req) {
388+
const ip = extractIp({}, req)
389+
try {
390+
expect(ip).to.be.equal(expectedIp)
391+
done()
392+
} catch (e) {
393+
done(e)
394+
}
395+
}
396+
axios.get(`http://localhost:${port}/`, {
397+
headers: {
398+
Forwarded: `by=5.6.7.8;for=${expectedIp}`
399+
}
400+
}).catch(done)
401+
})
402+
403+
it('should detect ip in header \'x-client-ip\' when \'Forwarded\' is also set', (done) => {
404+
const expectedIp = '1.2.3.4'
405+
controller = function (req) {
406+
const ip = extractIp({}, req)
407+
try {
408+
expect(ip).to.be.equal(expectedIp)
409+
done()
410+
} catch (e) {
411+
done(e)
412+
}
413+
}
414+
axios.get(`http://localhost:${port}/`, {
415+
headers: {
416+
'x-client-ip': expectedIp,
417+
Forwarded: 'for=5.6.7.8'
418+
}
419+
}).catch(done)
420+
})
421+
422+
it('should detect ip in header \'Forwarded\' when \'forwarded-for\' is also set', (done) => {
423+
const expectedIp = '1.2.3.4'
424+
controller = function (req) {
425+
const ip = extractIp({}, req)
426+
try {
427+
expect(ip).to.be.equal(expectedIp)
428+
done()
429+
} catch (e) {
430+
done(e)
431+
}
432+
}
433+
axios.get(`http://localhost:${port}/`, {
434+
headers: {
435+
'forwarded-for': '5.6.7.8',
436+
Forwarded: `for=${expectedIp}`
437+
}
438+
}).catch(done)
439+
})
440+
441+
it('should detect ip in header \'Forwarded\' when \'host\' and \'proto\' are also set', (done) => {
442+
const expectedIp = '1.2.3.4'
443+
controller = function (req) {
444+
const ip = extractIp({}, req)
445+
try {
446+
expect(ip).to.be.equal(expectedIp)
447+
done()
448+
} catch (e) {
449+
done(e)
450+
}
451+
}
452+
axios.get(`http://localhost:${port}/`, {
453+
headers: {
454+
Forwarded: `for=${expectedIp};proto=http;host=testhost`
455+
}
456+
}).catch(done)
457+
})
458+
459+
it('should detect ip in header \'Forwarded\' when port is included in the IP', (done) => {
460+
const expectedIp = '1.2.3.4'
461+
controller = function (req) {
462+
const ip = extractIp({}, req)
463+
try {
464+
expect(ip).to.be.equal(expectedIp)
465+
done()
466+
} catch (e) {
467+
done(e)
468+
}
469+
}
470+
axios.get(`http://localhost:${port}/`, {
471+
headers: {
472+
Forwarded: `for=${expectedIp}:8080`
473+
}
474+
}).catch(done)
475+
})
476+
477+
it('should detect ip in header \'Forwarded\' when port is included in the IPv6', (done) => {
478+
const expectedIp = '5a54:f844:006c:b8f1:0e96:9e54:54ac:4a2d'
479+
controller = function (req) {
480+
const ip = extractIp({}, req)
481+
try {
482+
expect(ip).to.be.equal(expectedIp)
483+
done()
484+
} catch (e) {
485+
done(e)
486+
}
487+
}
488+
axios.get(`http://localhost:${port}/`, {
489+
headers: {
490+
Forwarded: `for=[${expectedIp}]:8080`
491+
}
492+
}).catch(done)
493+
})
494+
})
293495
})

0 commit comments

Comments
 (0)