跳至内容 跳至搜索

GetIp 类用于延迟将请求数据处理为实际 IP 地址。如果调用 ActionDispatch::Request#remote_ip 方法,此类将计算该值然后进行缓存。

方法
C
F
I
N
S
T

类公共方法

new(req, check_ip, proxies)

# File actionpack/lib/action_dispatch/middleware/remote_ip.rb, line 105
def initialize(req, check_ip, proxies)
  @req      = req
  @check_ip = check_ip
  @proxies  = proxies
end

实例公共方法

calculate_ip()

遍历各种 IP 地址标头,寻找最可能是此请求的实际远程客户端地址的 IP。

如果请求直接针对 Ruby 进程(例如在 Heroku 上),REMOTE_ADDR 将是正确的。当请求被 HAProxy 或 NGINX 等其他服务器代理时,发出原始请求的 IP 地址将放入 X-Forwarded-For 标头。如果存在多个代理,该标头可能包含 IP 列表。其他代理服务则设置 Client-Ip 标头,因此我们也对此进行检查。

正如 这篇关于 Rails IP 欺骗的文章 所讨论的,虽然列表中的第一个 IP 可能是“原始”IP,但也可能被客户端恶意设置。

为了找到第一个(可能)准确的地址,我们获取 IP 列表,删除已知且受信任的代理,然后取剩余的最后一个地址,这很可能是由其中一个代理设置的。

# File actionpack/lib/action_dispatch/middleware/remote_ip.rb, line 129
def calculate_ip
  # Set by the Rack web server, this is a single value.
  remote_addr = sanitize_ips(ips_from(@req.remote_addr)).last

  # Could be a CSV list and/or repeated headers that were concatenated.
  client_ips    = sanitize_ips(ips_from(@req.client_ip)).reverse!
  forwarded_ips = sanitize_ips(@req.forwarded_for || []).reverse!

  # `Client-Ip` and `X-Forwarded-For` should not, generally, both be set. If they
  # are both set, it means that either:
  #
  # 1) This request passed through two proxies with incompatible IP header
  #     conventions.
  #
  # 2) The client passed one of `Client-Ip` or `X-Forwarded-For`
  #     (whichever the proxy servers weren't using) themselves.
  #
  # Either way, there is no way for us to determine which header is the right one
  # after the fact. Since we have no idea, if we are concerned about IP spoofing
  # we need to give up and explode. (If you're not concerned about IP spoofing you
  # can turn the `ip_spoofing_check` option off.)
  should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
  if should_check_ip && !forwarded_ips.include?(client_ips.last)
    # We don't know which came from the proxy, and which from the user
    raise IpSpoofAttackError, "IP spoofing attack?! " \
      "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
      "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
  end

  # We assume these things about the IP headers:
  #
  #     - X-Forwarded-For will be a list of IPs, one per proxy, or blank
  #     - Client-Ip is propagated from the outermost proxy, or is blank
  #     - REMOTE_ADDR will be the IP that made the request to Rack
  ips = forwarded_ips + client_ips
  ips.compact!

  # If every single IP option is in the trusted list, return the IP that's
  # furthest away
  filter_proxies(ips + [remote_addr]).first || ips.last || remote_addr
end

to_s()

缓存 calculate_ip 返回的值,并将其提供给 ActionDispatch::Request 使用。

# File actionpack/lib/action_dispatch/middleware/remote_ip.rb, line 173
def to_s
  @ip ||= calculate_ip
end

实例私有方法

filter_proxies(ips)

# File actionpack/lib/action_dispatch/middleware/remote_ip.rb, line 196
def filter_proxies(ips) # :doc:
  ips.reject do |ip|
    @proxies.any? { |proxy| proxy === ip }
  end
end

ips_from(header)

# File actionpack/lib/action_dispatch/middleware/remote_ip.rb, line 178
def ips_from(header) # :doc:
  return [] unless header
  # Split the comma-separated list into an array of strings.
  header.strip.split(/[,\s]+/)
end

sanitize_ips(ips)

# File actionpack/lib/action_dispatch/middleware/remote_ip.rb, line 184
def sanitize_ips(ips) # :doc:
  ips.select! do |ip|
    # Only return IPs that are valid according to the IPAddr#new method.
    range = IPAddr.new(ip).to_range
    # We want to make sure nobody is sneaking a netmask in.
    range.begin == range.end
  rescue ArgumentError
    nil
  end
  ips
end