ActionDispatch::RemoteIp::GetIp (original) (raw)
The GetIp class exists as a way to defer processing of the request data into an actual IP address. If the ActionDispatch::Request#remote_ip method is called, this class will calculate the value and then memoize it.
Methods
C
F
I
N
T
Class Public methods
new(req, check_ip, proxies)Link
def initialize(req, check_ip, proxies) @req = req @check_ip = check_ip @proxies = proxies end
Instance Public methods
calculate_ip()Link
Sort through the various IP address headers, looking for the IP most likely to be the address of the actual remote client making this request.
REMOTE_ADDR will be correct if the request is made directly against the Ruby process, on e.g. Heroku. When the request is proxied by another server like HAProxy or NGINX, the IP address that made the original request will be put in an X-Forwarded-For
header. If there are multiple proxies, that header may contain a list of IPs. Other proxy services set the Client-Ip
header instead, so we check that too.
As discussed in this post about Rails IP Spoofing, while the first IP in the list is likely to be the “originating” IP, it could also have been set by the client maliciously.
In order to find the first address that is (probably) accurate, we take the list of IPs, remove known and trusted proxies, and then take the last address left, which was presumably set by one of those proxies.
def calculate_ip
remote_addr = ips_from(@req.remote_addr).last
client_ips = ips_from(@req.client_ip).reverse! forwarded_ips = ips_from(@req.x_forwarded_for).reverse!
should_check_ip = @check_ip && client_ips.last && forwarded_ips.last if should_check_ip && !forwarded_ips.include?(client_ips.last)
raise IpSpoofAttackError, "IP spoofing attack?! " \
"HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
"HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
end
ips = forwarded_ips + client_ips ips.compact!
filter_proxies(ips + [remote_addr]).first || ips.last || remote_addr end
Instance Private methods
filter_proxies(ips)Link
def filter_proxies(ips) ips.reject do |ip| @proxies.any? { |proxy| proxy === ip } end end
ips_from(header)Link
def ips_from(header) return [] unless header
ips = header.strip.split(/[,\s]+/) ips.select! do |ip|
range = IPAddr.new(ip).to_range
range.begin == range.end
rescue ArgumentError nil end ips end