-
Notifications
You must be signed in to change notification settings - Fork 381
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update the client IP extraction resolution base on the latest RFC.
- Update the list of HTTP headers to look for IP - Add support to parsing HTTP IP Headers with multiple IP addresses - Update specs - Change Tracing::ClientIp module methods visibility
- Loading branch information
1 parent
989b271
commit 85ee26e
Showing
10 changed files
with
451 additions
and
318 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ lib/datadog/tracing/contrib/utils/quantization/http.rb,https://github.com/ruby/u | |
ext/ddtrace_profiling_native_extension/private_vm_api_access,https://github.com/ruby/ruby,BSD-2-Clause,"Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved." | ||
msgpack,https://rubygems.org/gems/msgpack,Apache-2.0,"Copyright (c) 2008-2015 Sadayuki Furuhashi" | ||
debase-ruby_core_source,https://rubygems.org/gems/debase-ruby_core_source,MIT for gem and BSD-2-Clause for Ruby sources,"Copyright (c) 2012 Gabriel Horner. Files from Ruby sources are Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved." | ||
lib/datadog/core/vendor/ipaddr,https://github.com/ruby/ipaddr/blob/master/lib/ipaddr.rb,BSD 2-Clause "Simplified" License,"Copyright (c) 2002 Hajimu UMEMOTO <[email protected]> Copyright (c) 2007-2017 Akinori MUSHA <[email protected]>" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'ipaddr' | ||
require_relative '../vendor/ipaddr' | ||
|
||
module Datadog | ||
module Core | ||
module Utils | ||
# Common Network utility functions. | ||
module Network | ||
DEFAULT_IP_HEADERS_NAMES = %w[ | ||
x-forwarded-for | ||
x-real-ip | ||
true-client-ip | ||
x-client-ip | ||
x-forwarded | ||
forwarded-for | ||
x-cluster-client-ip | ||
fastly-client-ip | ||
cf-connecting-ip | ||
cf-connecting-ipv6 | ||
].freeze | ||
|
||
class << self | ||
# Returns a client IP associated with the request if it was | ||
# retrieved successfully. | ||
# | ||
# | ||
# @param [Datadog::Core::HeaderCollection, #get, nil] headers The request headers | ||
# @param [Array<String>] list of headers to check. | ||
# @return [String] IP value without the port and the zone indentifier. | ||
# @return [nil] when no valid IP value found. | ||
def stripped_ip_from_request_headers(headers, ip_headers_to_check: DEFAULT_IP_HEADERS_NAMES) | ||
ip = ip_header(headers, ip_headers_to_check) | ||
|
||
ip ? ip.to_s : nil | ||
end | ||
|
||
# @param [String] IP value. | ||
# @return [String] IP value without the port and the zone indentifier. | ||
# @return [nil] when no valid IP value found. | ||
def stripped_ip(ip) | ||
ip = ip_to_ipaddr(ip) | ||
ip ? ip.to_s : nil | ||
end | ||
|
||
private | ||
|
||
# @param [String] IP value. | ||
# @return [IPaddr] | ||
# @return [nil] when no valid IP value found. | ||
def ip_to_ipaddr(ip) | ||
return unless ip | ||
|
||
clean_ip = if likely_ipv4?(ip) | ||
strip_ipv4_port(ip) | ||
else | ||
strip_zone_specifier(strip_ipv6_port(ip)) | ||
end | ||
|
||
begin | ||
IPAddr.new(clean_ip) | ||
rescue IPAddr::Error | ||
nil | ||
end | ||
end | ||
|
||
def ip_header(headers, ip_headers_to_check) | ||
return unless headers | ||
|
||
ip_headers_to_check.each do |name| | ||
value = headers.get(name) | ||
|
||
next unless value | ||
|
||
ips = value.split(',') | ||
ips.each do |ip| | ||
parsed_ip = ip_to_ipaddr(ip.strip) | ||
|
||
return parsed_ip if global_ip?(parsed_ip) | ||
end | ||
end | ||
|
||
nil | ||
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. | ||
# The rationale is that in valid IPv6 addresses, colons will always preced dots, | ||
# and in valid IPv4 addresses dots will always preced colons. | ||
def likely_ipv4?(value) | ||
dot_index = value.index('.') || value.size | ||
colon_index = value.index(':') || value.size | ||
|
||
dot_index < colon_index | ||
end | ||
|
||
def strip_zone_specifier(ipv6) | ||
ipv6.gsub(/%.*/, '') | ||
end | ||
|
||
def strip_ipv6_port(ip) | ||
if /\[(.*)\](?::\d+)?/ =~ ip | ||
Regexp.last_match(1) | ||
else | ||
ip | ||
end | ||
end | ||
|
||
def strip_ipv4_port(ip) | ||
ip.gsub(/:\d+\z/, '') | ||
end | ||
|
||
def global_ip?(parsed_ip) | ||
parsed_ip && !private?(parsed_ip) && !loopback?(parsed_ip) && !link_local?(parsed_ip) | ||
end | ||
|
||
# TODO: remove once we drop support for ruby 2.1, 2.2, 2.3, 2.4 | ||
# replace with ip.private? | ||
def private?(ip) | ||
Datadog::Core::Vendor::IPAddr.private?(ip) | ||
end | ||
|
||
# TODO: remove once we drop support for ruby 2.1, 2.2, 2.3, 2.4 | ||
# replace with ip.link_local? | ||
def link_local?(ip) | ||
Datadog::Core::Vendor::IPAddr.link_local?(ip) | ||
end | ||
|
||
# TODO: remove once we drop support for ruby 2.1, 2.2, 2.3, 2.4 | ||
# replace with ip.loopback | ||
def loopback?(ip) | ||
Datadog::Core::Vendor::IPAddr.loopback?(ip) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# Copyright (c) 2002 Hajimu UMEMOTO <[email protected]> | ||
# Copyright (c) 2007-2017 Akinori MUSHA <[email protected]> | ||
|
||
# Redistribution and use in source and binary forms, with or without | ||
# modification, are permitted provided that the following conditions | ||
# are met: | ||
# 1. Redistributions of source code must retain the above copyright | ||
# notice, this list of conditions and the following disclaimer. | ||
# 2. Redistributions in binary form must reproduce the above copyright | ||
# notice, this list of conditions and the following disclaimer in the | ||
# documentation and/or other materials provided with the distribution. | ||
|
||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | ||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | ||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | ||
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | ||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | ||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||
# SUCH DAMAGE. | ||
|
||
module Datadog | ||
module Core | ||
module Vendor | ||
# vendor code from https://github.com/ruby/ipaddr/blob/master/lib/ipaddr.rb | ||
# Ruby version below 2.5 does not have the IpAddr#private? method | ||
# We have to vendor the code because ruby versions below 2.5 did not extract ipaddr as a gem | ||
# So we can not specify a specific version for ipaddr for ruby versions: 2.1, 2.2, 2.3, 2.4 | ||
module IPAddr | ||
class << self | ||
def private?(ip) | ||
addr = ip.instance_variable_get(:@addr) | ||
|
||
case ip.family | ||
when Socket::AF_INET | ||
addr & 0xff000000 == 0x0a000000 || # 10.0.0.0/8 | ||
addr & 0xfff00000 == 0xac100000 || # 172.16.0.0/12 | ||
addr & 0xffff0000 == 0xc0a80000 # 192.168.0.0/16 | ||
when Socket::AF_INET6 | ||
addr & 0xfe00_0000_0000_0000_0000_0000_0000_0000 == 0xfc00_0000_0000_0000_0000_0000_0000_0000 | ||
else | ||
raise IPAddr::AddressFamilyError, 'unsupported address family' | ||
end | ||
end | ||
|
||
def link_local?(ip) | ||
addr = ip.instance_variable_get(:@addr) | ||
|
||
case ip.family | ||
when Socket::AF_INET | ||
addr & 0xffff0000 == 0xa9fe0000 # 169.254.0.0/16 | ||
when Socket::AF_INET6 | ||
addr & 0xffc0_0000_0000_0000_0000_0000_0000_0000 == 0xfe80_0000_0000_0000_0000_0000_0000_0000 | ||
else | ||
raise IPAddr::AddressFamilyError, 'unsupported address family' | ||
end | ||
end | ||
|
||
def loopback?(ip) | ||
addr = ip.instance_variable_get(:@addr) | ||
|
||
case ip.family | ||
when Socket::AF_INET | ||
addr & 0xff000000 == 0x7f000000 | ||
when Socket::AF_INET6 | ||
addr == 1 | ||
else | ||
raise IPAddr::AddressFamilyError, 'unsupported address family' | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.