Module blind_llama.request_adapters

This module implements a set of requests TransportAdapter, PoolManager, ConnectionPool and HTTPSConnection with one goal only:

  • to use a specific IP address when connecting via SSL to a web service without running into SNI trouble.

The usual technique to force an IP address on an HTTP connection with Requests is (assuming I want https://www.mithrilsecurity.io/contact on IP 1.2.3.4):

requests.get("http://1.2.3.4/contact", headers={'Host': 'mithrilsecurity.io'})

this is useful if I want to specifically test how 1.2.3.4 is responding; for instance, if example.com is DNS round-robined to several IP addresses and I want to hit one of them specifically.

This also works for https requests if using Python <2.7.9 because older versions don't do SNI and thus don't pass the requested hostname as part of the SSL handshake.

However, Python >=2.7.9 and >=3.4.x conveniently added SNI support, breaking this way of connecting to the IP, because the IP address embedded in the URL is passed as part of the SSL handshake, causing errors (mainly, the server returns a 400 Bad Request because the SNI host 1.2.3.4 doesn't match the one in the HTTP headers example.com).

The "easiest" way to achieve this is to force the IP address at the lowest possible level, namely when we do socket.create_connection. The rest of the "stack" is given the actual hostname. So the sequence is:

1- Open a socket to 1.2.3.4 2- SSL wrap this socket using the hostname. 3- Do the rest of the HTTPS traffic, headers and all over this socket.

Unfortunately Requests hides the socket.create_connection call in the deep recesses of urllib3, so the specified chain of classes is needed to propagate the given dest_ip value all the way down the stack.

Because this applies to a very limited set of circumstances, the overridden code is very simplistic and eschews many of the nice checks Requests does for you.

Specifically:

  • It ONLY handles HTTPS.
  • It does NO certificate verification (which would be pointless)
  • Only tested with Requests 2.2.1 and 2.9.1.
  • Does NOT work with the ancient urllib3 (1.7.1) shipped with Ubuntu 14.04. Should not be an issue because Ubunt 14.04 has older Python which doesn't do SNI.

How to use it

It's like any other transport adapter. Just pass the IP address that connections to the given URL prefix should use.

session = requests.Session() session.mount("https://example.com", ForcedIPHTTPSAdapter(dest_ip='1.2.3.4')) response = session.get( '/some/path', headers={'Host': 'example.com'}, verify=False)

Classes

class ForcedIPHTTPSAdapter (*args, **kwargs)

The built-in HTTP Adapter for urllib3.

Provides a general-case interface for Requests sessions to contact HTTP and HTTPS urls by implementing the Transport Adapter interface. This class will usually be created by the :class:Session <Session> class under the covers.

:param pool_connections: The number of urllib3 connection pools to cache. :param pool_maxsize: The maximum number of connections to save in the pool. :param max_retries: The maximum number of retries each connection should attempt. Note, this applies only to failed DNS lookups, socket connections and connection timeouts, never to requests where data has made it to the server. By default, Requests does not retry failed connections. If you need granular control over the conditions under which we retry a request, import urllib3's Retry class and pass that instead. :param pool_block: Whether the connection pool should block for connections.

Usage::

import requests s = requests.Session() a = requests.adapters.HTTPAdapter(max_retries=3) s.mount('http://', a)

Ancestors

  • requests.adapters.HTTPAdapter
  • requests.adapters.BaseAdapter

Methods

def init_poolmanager(self, *args, **pool_kwargs)

Initializes a urllib3 PoolManager.

This method should not be called from user code, and is only exposed for use when subclassing the :class:HTTPAdapter <requests.adapters.HTTPAdapter>.

:param connections: The number of urllib3 connection pools to cache. :param maxsize: The maximum number of connections to save in the pool. :param block: Block when no free connections are available. :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager.

class ForcedIPHTTPSConnection (*args, **kwargs)

Many of the parameters to this constructor are passed to the underlying SSL socket by means of :py:func:urllib3.util.ssl_wrap_socket.

Ancestors

  • urllib3.connection.HTTPSConnection
  • urllib3.connection.HTTPConnection
  • http.client.HTTPConnection
class ForcedIPHTTPSConnectionPool (*args, **kwargs)

Same as :class:.HTTPConnectionPool, but HTTPS.

:class:.HTTPSConnection uses one of assert_fingerprint, assert_hostname and host in this order to verify connections. If assert_hostname is False, no verification is done.

The key_file, cert_file, cert_reqs, ca_certs, ca_cert_dir, ssl_version, key_password are only used if :mod:ssl is available and are fed into :meth:urllib3.util.ssl_wrap_socket to upgrade the connection socket into an SSL socket.

Ancestors

  • urllib3.connectionpool.HTTPSConnectionPool
  • urllib3.connectionpool.HTTPConnectionPool
  • urllib3.connectionpool.ConnectionPool
  • urllib3.request.RequestMethods
class ForcedIPHTTPSPoolManager (*args, **kwargs)

Allows for arbitrary requests while transparently keeping track of necessary connection pools for you.

:param num_pools: Number of connection pools to cache before discarding the least recently used pool.

:param headers: Headers to include with all requests, unless other headers are given explicitly.

:param **connection_pool_kw: Additional parameters are used to create fresh :class:urllib3.connectionpool.ConnectionPool instances.

Example::

>>> manager = PoolManager(num_pools=2)
>>> r = manager.request('GET', 'http://google.com/')
>>> r = manager.request('GET', 'http://google.com/mail')
>>> r = manager.request('GET', 'http://yahoo.com/')
>>> len(manager.pools)
2

Ancestors

  • urllib3.poolmanager.PoolManager
  • urllib3.request.RequestMethods