Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions lib/datadog/core/utils/network.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module Network
true-client-ip
x-client-ip
x-forwarded
forwarded
forwarded-for
x-cluster-client-ip
fastly-client-ip
Expand Down Expand Up @@ -73,6 +74,8 @@ def ip_header(headers, ip_headers_to_check)
next unless value

ips = value.split(',')
ips = process_forwarded_header_values(ips) if name == 'forwarded'

ips.each do |ip|
parsed_ip = ip_to_ipaddr(ip.strip)

Expand All @@ -83,6 +86,22 @@ def ip_header(headers, ip_headers_to_check)
nil
end

def process_forwarded_header_values(values)
values.each_with_object([]) do |value, acc|
value.downcase!

value.split(';').each do |tuple_str|
tuple_str.strip!
next unless tuple_str.start_with?('for=')

tuple_str.delete_prefix!('for=')
tuple_str.delete!('"')

acc << tuple_str
end
end
end

# Returns whether the given value is more likely to be an IPv4 than an IPv6 address.
#
# This is done by checking if a dot (`'.'`) character appears before a colon (`':'`) in the value.
Expand Down
1 change: 1 addition & 0 deletions sig/datadog/core/utils/network.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Datadog
private

def self.ip_header: (Datadog::Core::HeaderCollection headers, ::Array[::String] ip_headers_to_check) -> untyped?
def self.process_forwarded_header_values: (::Array[::String] values) -> ::Array[::String]
def self.strip_zone_specifier: (::String ipv6) -> ::String
def self.strip_ipv6_port: (::String ip) -> ::String
def self.strip_ipv4_port: (::String ip) -> ::String
Expand Down
39 changes: 39 additions & 0 deletions spec/datadog/core/utils/network_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,45 @@
end
end

context 'with Forwaded header' do
it 'correctly parses a single for IP' do
headers = Datadog::Core::HeaderCollection.from_hash({'Forwarded' => 'for=43.43.43.43;proto=http;by=203.0.113.43'})

result = described_class.stripped_ip_from_request_headers(headers)
expect(result).to eq('43.43.43.43')
end

it 'is case-insencitive to keys in the header' do
headers = Datadog::Core::HeaderCollection.from_hash({'Forwarded' => 'For=43.43.43.43; Proto=http; By=203.0.113.43'})

result = described_class.stripped_ip_from_request_headers(headers)
expect(result).to eq('43.43.43.43')
end

it 'correctly parses multiple for IPs' do
headers = Datadog::Core::HeaderCollection.from_hash(
{'Forwarded' => 'for=127.0.0.1;host="example.host";by=2.2.2.2;proto=http,for="1.1.1.1:6543"'}
)

result = described_class.stripped_ip_from_request_headers(headers)
expect(result).to eq('1.1.1.1')
end

it 'correctly parses IPv6' do
headers = Datadog::Core::HeaderCollection.from_hash({'Forwarded' => 'for="[2001:db8:cafe::17]:4711"'})

result = described_class.stripped_ip_from_request_headers(headers)
expect(result).to eq('2001:db8:cafe::17')
end

it 'returns nil for invalid values' do
headers = Datadog::Core::HeaderCollection.from_hash({'Forwarded' => 'foobar'})

result = described_class.stripped_ip_from_request_headers(headers)
expect(result).to be_nil
end
end

context 'with custom header value' do
it 'returns the IP value if valid public address' do
headers = Datadog::Core::HeaderCollection.from_hash(
Expand Down