diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index e959bf9..abd5999 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -1781,7 +1781,7 @@ function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG
       'uid'         => $user_uid,
       'request_uri' => $base_root . request_uri(),
       'referer'     => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '',
-      'ip'          => ip_address(),
+      'ip'          => drupal_container()->get('request')->getClientIp(),
       // Request time isn't accurate for long processes, use time() instead.
       'timestamp'   => time(),
     );
@@ -2055,7 +2055,7 @@ function drupal_get_hash_salt() {
 function drupal_anonymous_user() {
   $user = new stdClass();
   $user->uid = 0;
-  $user->hostname = ip_address();
+  $user->hostname = drupal_container()->get('request')->getClientIp();
   $user->roles = array();
   $user->roles[DRUPAL_ANONYMOUS_RID] = DRUPAL_ANONYMOUS_RID;
   return $user;
@@ -3103,52 +3103,6 @@ function arg($index = NULL, $path = NULL) {
 }
 
 /**
- * Returns the IP address of the client machine.
- *
- * If Drupal is behind a reverse proxy, we use the X-Forwarded-For header
- * instead of $_SERVER['REMOTE_ADDR'], which would be the IP address of
- * the proxy server, and not the client's. The actual header name can be
- * configured by the reverse_proxy_header variable.
- *
- * @return
- *   IP address of client machine, adjusted for reverse proxy and/or cluster
- *   environments.
- */
-function ip_address() {
-  $ip_address = &drupal_static(__FUNCTION__);
-
-  if (!isset($ip_address)) {
-    $ip_address = $_SERVER['REMOTE_ADDR'];
-
-    if (settings()->get('reverse_proxy', 0)) {
-      $reverse_proxy_header = settings()->get('reverse_proxy_header', 'HTTP_X_FORWARDED_FOR');
-      if (!empty($_SERVER[$reverse_proxy_header])) {
-        // If an array of known reverse proxy IPs is provided, then trust
-        // the XFF header if request really comes from one of them.
-        $reverse_proxy_addresses = settings()->get('reverse_proxy_addresses', array());
-
-        // Turn XFF header into an array.
-        $forwarded = explode(',', $_SERVER[$reverse_proxy_header]);
-
-        // Trim the forwarded IPs; they may have been delimited by commas and spaces.
-        $forwarded = array_map('trim', $forwarded);
-
-        // Tack direct client IP onto end of forwarded array.
-        $forwarded[] = $ip_address;
-
-        // Eliminate all trusted IPs.
-        $untrusted = array_diff($forwarded, $reverse_proxy_addresses);
-
-        // The right-most IP is the most specific we can trust.
-        $ip_address = array_pop($untrusted);
-      }
-    }
-  }
-
-  return $ip_address;
-}
-
-/**
  * Initializes and returns the class loader.
  *
  * The class loader is responsible for lazy-loading all PSR-0 compatible
diff --git a/core/includes/session.inc b/core/includes/session.inc
index 31e67a6..a82d252 100644
--- a/core/includes/session.inc
+++ b/core/includes/session.inc
@@ -173,7 +173,7 @@ function _drupal_session_write($sid, $value) {
       // Either ssid or sid or both will be added from $key below.
       $fields = array(
         'uid' => $user->uid,
-        'hostname' => ip_address(),
+        'hostname' => drupal_container()->get('request')->getClientIp(),
         'session' => $value,
         'timestamp' => REQUEST_TIME,
       );
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 16faeef..394dba1 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -207,6 +207,9 @@ public function build(ContainerBuilder $container) {
     $container->register('mime_type_matcher', 'Drupal\Core\Routing\MimeTypeMatcher')
       ->addTag('route_filter');
 
+    $container->register('reverse_proxy_subscriber', 'Drupal\Core\EventSubscriber\ReverseProxySubscriber')
+      ->addArgument(new Reference('settings'))
+      ->addTag('event_subscriber');
     $container->register('router_processor_subscriber', 'Drupal\Core\EventSubscriber\RouteProcessorSubscriber')
       ->addArgument(new Reference('content_negotiation'))
       ->addTag('event_subscriber');
diff --git a/core/lib/Drupal/Core/EventSubscriber/ReverseProxySubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ReverseProxySubscriber.php
new file mode 100644
index 0000000..b7b45d6
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/ReverseProxySubscriber.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\EventSubscriber\ReverseProxySubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Drupal\Component\Utility\Settings;
+
+/**
+ * Reverse proxy subscriber for controller requests.
+ */
+class ReverseProxySubscriber implements EventSubscriberInterface {
+
+  /**
+   * A settings object.
+   *
+   * @var \Drupal\Component\Utility\Settings
+   */
+  protected $settings;
+
+  public function __construct(Settings $settings) {
+    $this->settings = $settings;
+  }
+
+  /**
+   * Passes reverse proxy settings to current request.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onKernelRequestReverseProxyCheck(GetResponseEvent $event) {
+    $request = $event->getRequest();
+    if ($this->settings->get('reverse_proxy', 0)) {
+      $reverse_proxy_header = $this->settings->get('reverse_proxy_header', 'HTTP_X_FORWARDED_FOR');
+      $request->setTrustedHeaderName($request::HEADER_CLIENT_IP, $reverse_proxy_header);
+      $reverse_proxy_addresses = $this->settings->get('reverse_proxy_addresses', array());
+      $request->setTrustedProxies($reverse_proxy_addresses);
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::REQUEST][] = array('onKernelRequestReverseProxyCheck', 10);
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/Flood/DatabaseBackend.php b/core/lib/Drupal/Core/Flood/DatabaseBackend.php
index a7b05f4..0bc30d1 100644
--- a/core/lib/Drupal/Core/Flood/DatabaseBackend.php
+++ b/core/lib/Drupal/Core/Flood/DatabaseBackend.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Flood;
 
+use Symfony\Component\HttpFoundation\Request;
 use Drupal\Core\Database\Connection;
 
 /**
@@ -22,14 +23,24 @@ class DatabaseBackend implements FloodInterface {
   protected $connection;
 
   /**
+   * A request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
    * Construct the DatabaseBackend.
    *
    * @param \Drupal\Core\Database\Connection $connection
    *   The database connection which will be used to store the flood event
    *   information.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The HttpRequest object representing the current request.
    */
-  public function __construct(Connection $connection) {
+  public function __construct(Connection $connection, Request $request) {
     $this->connection = $connection;
+    $this->request = $request;
   }
 
   /**
@@ -37,7 +48,7 @@ public function __construct(Connection $connection) {
    */
   public function register($name, $window = 3600, $identifier = NULL) {
     if (!isset($identifier)) {
-      $identifier = ip_address();
+      $identifier = $this->request->getClientIp();
     }
     $this->connection->insert('flood')
       ->fields(array(
@@ -54,7 +65,7 @@ public function register($name, $window = 3600, $identifier = NULL) {
    */
   public function clear($name, $identifier = NULL) {
     if (!isset($identifier)) {
-      $identifier = ip_address();
+      $identifier = $this->request->getClientIp();
     }
     $this->connection->delete('flood')
       ->condition('event', $name)
@@ -67,7 +78,7 @@ public function clear($name, $identifier = NULL) {
    */
   public function isAllowed($name, $threshold, $window = 3600, $identifier = NULL) {
     if (!isset($identifier)) {
-      $identifier = ip_address();
+      $identifier = $this->request->getClientIp();
     }
     $number = $this->connection->select('flood', 'f')
       ->condition('event', $name)
diff --git a/core/lib/Drupal/Core/Flood/MemoryBackend.php b/core/lib/Drupal/Core/Flood/MemoryBackend.php
index 63b19da..1436e43 100644
--- a/core/lib/Drupal/Core/Flood/MemoryBackend.php
+++ b/core/lib/Drupal/Core/Flood/MemoryBackend.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Flood;
 
+use Symfony\Component\HttpFoundation\Request;
+
 /**
  * Defines the memory flood backend. This is used for testing.
  */
@@ -18,11 +20,28 @@ class MemoryBackend implements FloodInterface {
   protected $events = array();
 
   /**
+   * A request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * Construct the MemoryBackend.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The HttpRequest object representing the current request.
+   */
+  public function __construct(Request $request) {
+    $this->request = $request;
+  }
+
+  /**
    * Implements Drupal\Core\Flood\FloodInterface::register().
    */
   public function register($name, $window = 3600, $identifier = NULL) {
     if (!isset($identifier)) {
-      $identifier = ip_address();
+      $identifier = $this->request->getClientIp();
     }
     // We can't use REQUEST_TIME here, because that would not guarantee
     // uniqueness.
@@ -35,7 +54,7 @@ public function register($name, $window = 3600, $identifier = NULL) {
    */
   public function clear($name, $identifier = NULL) {
     if (!isset($identifier)) {
-      $identifier = ip_address();
+      $identifier = $this->request->getClientIp();
     }
     unset($this->events[$name][$identifier]);
   }
@@ -45,7 +64,7 @@ public function clear($name, $identifier = NULL) {
    */
   public function isAllowed($name, $threshold, $window = 3600, $identifier = NULL) {
     if (!isset($identifier)) {
-      $identifier = ip_address();
+      $identifier = $this->request->getClientIp();
     }
     $limit = microtime(true) - $window;
     $number = count(array_filter($this->events[$name][$identifier], function ($timestamp) use ($limit) {
diff --git a/core/modules/action/tests/action_loop_test/action_loop_test.module b/core/modules/action/tests/action_loop_test/action_loop_test.module
index 8c3b98d..c953ef4 100644
--- a/core/modules/action/tests/action_loop_test/action_loop_test.module
+++ b/core/modules/action/tests/action_loop_test/action_loop_test.module
@@ -69,7 +69,7 @@ function watchdog_skip_semaphore($type, $message, $variables = array(), $severit
     'uid'         => isset($user->uid) ? $user->uid : 0,
     'request_uri' => $base_root . request_uri(),
     'referer'     => $_SERVER['HTTP_REFERER'],
-    'ip'          => ip_address(),
+    'ip'          => drupal_container()->get('request')->getClientIp(),
     'timestamp'   => REQUEST_TIME,
   );
 
diff --git a/core/modules/ban/ban.admin.inc b/core/modules/ban/ban.admin.inc
index 0ea23fc..f5e699c 100644
--- a/core/modules/ban/ban.admin.inc
+++ b/core/modules/ban/ban.admin.inc
@@ -85,7 +85,7 @@ function ban_ip_form_validate($form, &$form_state) {
   if (db_query("SELECT * FROM {ban_ip} WHERE ip = :ip", array(':ip' => $ip))->fetchField()) {
     form_set_error('ip', t('This IP address is already banned.'));
   }
-  elseif ($ip == ip_address()) {
+  elseif ($ip == drupal_container()->get('request')->getClientIp()) {
     form_set_error('ip', t('You may not ban your own IP address.'));
   }
   elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE) == FALSE) {
diff --git a/core/modules/ban/lib/Drupal/ban/EventSubscriber/BanSubscriber.php b/core/modules/ban/lib/Drupal/ban/EventSubscriber/BanSubscriber.php
index 7dc141c..8d9f2ad 100644
--- a/core/modules/ban/lib/Drupal/ban/EventSubscriber/BanSubscriber.php
+++ b/core/modules/ban/lib/Drupal/ban/EventSubscriber/BanSubscriber.php
@@ -43,8 +43,7 @@ public function __construct(BanIpManager $manager) {
    *   The Event to process.
    */
   public function onKernelRequestBannedIpCheck(GetResponseEvent $event) {
-    // @todo convert this to Request::getClientIP().
-    $ip = ip_address();
+    $ip = $event->getRequest()->getClientIp();
     if ($this->manager->isDenied($ip)) {
       $response = new Response('Sorry, ' . check_plain($ip) . ' has been banned.', 403);
       $event->setResponse($response);
diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
index 6851be0..99dda2d 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
@@ -153,7 +153,7 @@ protected function preSave(EntityInterface $comment) {
       }
       // Add the values which aren't passed into the function.
       $comment->thread->value = $thread;
-      $comment->hostname->value = ip_address();
+      $comment->hostname->value = drupal_container()->get('request')->getClientIp();
     }
   }
 
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php
index 91cab48..f1f7f82 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentLinksTest.php
@@ -134,7 +134,7 @@ function setEnvironment(array $info) {
           'uid' => 0,
           'status' => COMMENT_PUBLISHED,
           'subject' => $this->randomName(),
-          'hostname' => ip_address(),
+          'hostname' => drupal_container()->get('request')->getClientIp(),
           'langcode' => LANGUAGE_NOT_SPECIFIED,
           'comment_body' => array(LANGUAGE_NOT_SPECIFIED => array($this->randomName())),
         ));
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentNewIndicatorTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentNewIndicatorTest.php
index 324012e..d94674c 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentNewIndicatorTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentNewIndicatorTest.php
@@ -43,7 +43,7 @@ public function testCommentNewCommentsIndicator() {
       'uid' => $this->loggedInUser->uid,
       'status' => COMMENT_PUBLISHED,
       'subject' => $this->randomName(),
-      'hostname' => ip_address(),
+      'hostname' => drupal_container()->get('request')->getClientIp(),
       'langcode' => LANGUAGE_NOT_SPECIFIED,
       'comment_body' => array(LANGUAGE_NOT_SPECIFIED => array($this->randomName())),
     ));
diff --git a/core/modules/dblog/lib/Drupal/dblog/Tests/DBLogTest.php b/core/modules/dblog/lib/Drupal/dblog/Tests/DBLogTest.php
index 1769605..c8ec39b 100644
--- a/core/modules/dblog/lib/Drupal/dblog/Tests/DBLogTest.php
+++ b/core/modules/dblog/lib/Drupal/dblog/Tests/DBLogTest.php
@@ -137,7 +137,7 @@ private function generateLogEntries($count, $type = 'custom', $severity = WATCHD
       'uid'         => isset($this->big_user->uid) ? $this->big_user->uid : 0,
       'request_uri' => $base_root . request_uri(),
       'referer'     => $_SERVER['HTTP_REFERER'],
-      'ip'          => ip_address(),
+      'ip'          => drupal_container()->get('request')->getClientIp(),
       'timestamp'   => REQUEST_TIME,
       );
     $message = 'Log entry added to test the dblog row limit. Entry #';
@@ -424,7 +424,7 @@ protected function testDBLogAddAndClear() {
       'uid'         => isset($this->big_user->uid) ? $this->big_user->uid : 0,
       'request_uri' => $base_root . request_uri(),
       'referer'     => $_SERVER['HTTP_REFERER'],
-      'ip'          => ip_address(),
+      'ip'          => drupal_container()->get('request')->getClientIp(),
       'timestamp'   => REQUEST_TIME,
     );
     // Add a watchdog entry.
diff --git a/core/modules/openid/openid.module b/core/modules/openid/openid.module
index 6d31124..3c0366e 100644
--- a/core/modules/openid/openid.module
+++ b/core/modules/openid/openid.module
@@ -1066,7 +1066,7 @@ function openid_verify_assertion_nonce($service, $response) {
     return TRUE;
   }
   else {
-    watchdog('openid', 'Nonce replay attempt blocked from @ip, nonce: @nonce.', array('@ip' => ip_address(), '@nonce' => $response['openid.response_nonce']), WATCHDOG_CRITICAL);
+    watchdog('openid', 'Nonce replay attempt blocked from @ip, nonce: @nonce.', array('@ip' => drupal_container()->get('request')->getClientIp(), '@nonce' => $response['openid.response_nonce']), WATCHDOG_CRITICAL);
     return FALSE;
   }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/IpAddressTest.php b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/IpAddressTest.php
deleted file mode 100644
index c062aa6..0000000
--- a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/IpAddressTest.php
+++ /dev/null
@@ -1,111 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\system\Tests\Bootstrap\IpAddressTest.
- */
-
-namespace Drupal\system\Tests\Bootstrap;
-
-use Drupal\simpletest\WebTestBase;
-
-/**
- * Tests getting IP addresses and hostname validation.
- */
-class IpAddressTest extends WebTestBase {
-
-  public static function getInfo() {
-    return array(
-      'name' => 'IP address and HTTP_HOST test',
-      'description' => 'Get the IP address from the current visitor from the server variables, check hostname validation.',
-      'group' => 'Bootstrap'
-    );
-  }
-
-  function setUp() {
-    $this->oldserver = $_SERVER;
-
-    $this->remote_ip = '127.0.0.1';
-    $this->proxy_ip = '127.0.0.2';
-    $this->proxy2_ip = '127.0.0.3';
-    $this->forwarded_ip = '127.0.0.4';
-    $this->cluster_ip = '127.0.0.5';
-    $this->untrusted_ip = '0.0.0.0';
-
-    drupal_static_reset('ip_address');
-
-    $_SERVER['REMOTE_ADDR'] = $this->remote_ip;
-    unset($_SERVER['HTTP_X_FORWARDED_FOR']);
-    unset($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']);
-
-    parent::setUp();
-  }
-
-  function tearDown() {
-    $_SERVER = $this->oldserver;
-    drupal_static_reset('ip_address');
-    parent::tearDown();
-  }
-
-  /**
-   * Tests IP address and hostname.
-   */
-  function testIPAddressHost() {
-    // Test the normal IP address.
-    $this->assertTrue(
-      ip_address() == $this->remote_ip,
-      'Got remote IP address.'
-    );
-
-    // Proxy forwarding on but no proxy addresses defined.
-    $this->settingsSet('reverse_proxy',  1);
-    $this->assertTrue(
-      ip_address() == $this->remote_ip,
-      'Proxy forwarding without trusted proxies got remote IP address.'
-    );
-
-    // Proxy forwarding on and proxy address not trusted.
-    $this->settingsSet('reverse_proxy_addresses', array($this->proxy_ip, $this->proxy2_ip));
-    drupal_static_reset('ip_address');
-    $_SERVER['REMOTE_ADDR'] = $this->untrusted_ip;
-    $this->assertTrue(
-      ip_address() == $this->untrusted_ip,
-      'Proxy forwarding with untrusted proxy got remote IP address.'
-    );
-
-    // Proxy forwarding on and proxy address trusted.
-    $_SERVER['REMOTE_ADDR'] = $this->proxy_ip;
-    $_SERVER['HTTP_X_FORWARDED_FOR'] = $this->forwarded_ip;
-    drupal_static_reset('ip_address');
-    $this->assertTrue(
-      ip_address() == $this->forwarded_ip,
-      'Proxy forwarding with trusted proxy got forwarded IP address.'
-    );
-
-    // Multi-tier architecture with comma separated values in header.
-    $_SERVER['REMOTE_ADDR'] = $this->proxy_ip;
-    $_SERVER['HTTP_X_FORWARDED_FOR'] = implode(', ', array($this->untrusted_ip, $this->forwarded_ip, $this->proxy2_ip));
-    drupal_static_reset('ip_address');
-    $this->assertTrue(
-      ip_address() == $this->forwarded_ip,
-      'Proxy forwarding with trusted 2-tier proxy got forwarded IP address.'
-    );
-
-    // Custom client-IP header.
-    $this->settingsSet('reverse_proxy_header', 'HTTP_X_CLUSTER_CLIENT_IP');
-    $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'] = $this->cluster_ip;
-    drupal_static_reset('ip_address');
-    $this->assertTrue(
-      ip_address() == $this->cluster_ip,
-      'Cluster environment got cluster client IP.'
-    );
-
-    // Verifies that drupal_valid_http_host() prevents invalid characters.
-    $this->assertFalse(drupal_valid_http_host('security/.drupal.org:80'), 'HTTP_HOST with / is invalid');
-    $this->assertFalse(drupal_valid_http_host('security\\.drupal.org:80'), 'HTTP_HOST with \\ is invalid');
-    $this->assertFalse(drupal_valid_http_host('security<.drupal.org:80'), 'HTTP_HOST with &lt; is invalid');
-    $this->assertFalse(drupal_valid_http_host('security..drupal.org:80'), 'HTTP_HOST with .. is invalid');
-    // IPv6 loopback address
-    $this->assertTrue(drupal_valid_http_host('[::1]:80'), 'HTTP_HOST containing IPv6 loopback is valid');
-  }
-}
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 6a9fb78..91527b6 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -1343,7 +1343,7 @@ function user_login_authenticate_validate($form, &$form_state) {
         // The default identifier is a combination of uid and IP address. This
         // is less secure but more resistant to denial-of-service attacks that
         // could lock out all users with public user names.
-        $identifier = $account->uid . '-' . ip_address();
+        $identifier = $account->uid . '-' . drupal_container()->get('request')->getClientIp();
       }
       $form_state['flood_control_user_identifier'] = $identifier;
 
