GSOC 2017: Implementing RFC 8229 "TCP Encapsulation of IKE and IPsec Packets" for Libreswan: Difference between revisions

From Libreswan
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
My GSOC 2017 project aimed at implementing the RFC 8229 - TCP Encapsulation of IKE and IPsec Packets. According to this, we add support for TCP encapsulation of packets in Pluto to route out of stringent networks allowing only TCP traffic.
My GSOC 2017 project aimed at implementing the RFC 8229 - TCP Encapsulation of IKE and IPsec Packets. According to this, we add support for TCP encapsulation of packets in Pluto(Libreswan IKE daemon) to route out of stringent networks allowing only TCP traffic.  
 
Introduction:
 
The RFC was in the draft stage when I started working on the project, and as of now, has been accepted as RFC 8229. There weren't any major changes between the draft I started to work on and the final RFC. The problem addressed is that when UDP is blocked on networks behind strict NATs, we fall back to using TCP encapsulation and route out. The RFC specifies various standards for implementing this. I have ensured to implement the MUSTs in the RFC, so that Libreswan could be used as a client or a server with other standard implementations. Important thing to keep in mind is that kernel changes are yet to be made, so as of now we end up just negotiating the IPsec SA details, but install a ESP/ESPinUDP SA. Three new ipsec.conf paramenters were introduced and they direct the implementation these changes:-
 
# listen-tcp: The TCP port to listen on. TCP will keep listening on the port number specified in this option. The default value is 0, indicating not to open any TCP ports for listening. Acceptable values are 0-65536, where anything other that 0 means listen on that port number.
 
These are per conn options to be defined in a connection on the client side when required:
 
# tcp-remoteport : The remote port number to which we want to initiate TCP connection. This option is set to 0 by default indicating that TCP encap is not enabled and we shouldn't fall back to TCP. Acceptable values are 0-65536, where anything other that 0 means initiate a TCP connection to that port number.
 
# tcponly : Use TCP encapsulation immediately when connection goes up. This makes sure Pluto doesn't even try over UDP. The default value is no. Acceptable values are no and yes.
 
Implementation:
 
These features have been implemented during the project duration:-
 
* Add the above options in ipsec.conf for enabling TCP encapsulation
* On the client, Fall back to trying TCP if UDP fails or directly try TCP if tcponly option is there
* On the server, create a TCP socket and keep listening on it for connections
* Send a stream prefix at the start of a new connection
* Add a TCP packet length field in the header
 
All the listening, receiving and sending logic for TCP was implemented using callbacks in libevent's evconnlistener and bufferevent structures. Some core structures were updated to have new members.
A side task I did was adding of IPsec policy holes for IKE packets. Pluto initially used socket bypass options for these, but now we have fixed bypass policies for the IKE ports and also for ICMPv6 neighbor discovery and solicitation type packets.
 
Some extras in the RFC that weren't implemented due to time and difficulty constraints:-
 
* Kernel support. This will be done by the kernel people and when this happens, We will have to make a few changes to Libreswan for this.
* Listening on multiple TCP ports. This isn't a very difficult implementation but I wanted to implement the basics properly, so can be tackled easily later
* TLS support. Again time constraints were there for this to be implemented.
* Probing over UDP even after TCP is established, so that we can ditch the TCP connection and go back to UDP
 
The above are in the list of future tasks that could be implemented to have a robust support for TCP.
 
 
 


This page documents the project.


These are some points from the RFC that we wanted to implement:
These are some points from the RFC that we wanted to implement:

Revision as of 16:00, 28 August 2017

My GSOC 2017 project aimed at implementing the RFC 8229 - TCP Encapsulation of IKE and IPsec Packets. According to this, we add support for TCP encapsulation of packets in Pluto(Libreswan IKE daemon) to route out of stringent networks allowing only TCP traffic.

Introduction:

The RFC was in the draft stage when I started working on the project, and as of now, has been accepted as RFC 8229. There weren't any major changes between the draft I started to work on and the final RFC. The problem addressed is that when UDP is blocked on networks behind strict NATs, we fall back to using TCP encapsulation and route out. The RFC specifies various standards for implementing this. I have ensured to implement the MUSTs in the RFC, so that Libreswan could be used as a client or a server with other standard implementations. Important thing to keep in mind is that kernel changes are yet to be made, so as of now we end up just negotiating the IPsec SA details, but install a ESP/ESPinUDP SA. Three new ipsec.conf paramenters were introduced and they direct the implementation these changes:-

  1. listen-tcp: The TCP port to listen on. TCP will keep listening on the port number specified in this option. The default value is 0, indicating not to open any TCP ports for listening. Acceptable values are 0-65536, where anything other that 0 means listen on that port number.

These are per conn options to be defined in a connection on the client side when required:

  1. tcp-remoteport : The remote port number to which we want to initiate TCP connection. This option is set to 0 by default indicating that TCP encap is not enabled and we shouldn't fall back to TCP. Acceptable values are 0-65536, where anything other that 0 means initiate a TCP connection to that port number.
  1. tcponly : Use TCP encapsulation immediately when connection goes up. This makes sure Pluto doesn't even try over UDP. The default value is no. Acceptable values are no and yes.

Implementation:

These features have been implemented during the project duration:-

  • Add the above options in ipsec.conf for enabling TCP encapsulation
  • On the client, Fall back to trying TCP if UDP fails or directly try TCP if tcponly option is there
  • On the server, create a TCP socket and keep listening on it for connections
  • Send a stream prefix at the start of a new connection
  • Add a TCP packet length field in the header

All the listening, receiving and sending logic for TCP was implemented using callbacks in libevent's evconnlistener and bufferevent structures. Some core structures were updated to have new members. A side task I did was adding of IPsec policy holes for IKE packets. Pluto initially used socket bypass options for these, but now we have fixed bypass policies for the IKE ports and also for ICMPv6 neighbor discovery and solicitation type packets.

Some extras in the RFC that weren't implemented due to time and difficulty constraints:-

  • Kernel support. This will be done by the kernel people and when this happens, We will have to make a few changes to Libreswan for this.
  • Listening on multiple TCP ports. This isn't a very difficult implementation but I wanted to implement the basics properly, so can be tackled easily later
  • TLS support. Again time constraints were there for this to be implemented.
  • Probing over UDP even after TCP is established, so that we can ditch the TCP connection and go back to UDP

The above are in the list of future tasks that could be implemented to have a robust support for TCP.



These are some points from the RFC that we wanted to implement:

  • The SPI field in ESP header must be non-zero.
  • Before sending any of the IKE or ESP packet streams, a peer must send a fixed sequence of six bytes "IKETCP" so that IKE/ESP traffic is distinguishable. This is only required at the start of the TCP connection. The responder must wait to receive all six bytes before parsing other packets.
  • A responder peer must always keep listening on the configured TCP port in case a session is initiated. And if TCP encapsulation is used then the subsequent IKE SA and Child SAs must be sent over this TCP connection.
  • When the TCP connection is torn down for any reason, the TCP originator should create a new connection sending the "IKETCP" bytes at the start. The responder must receive traffic for old SAs on this new TCP connection even if there's a change of ports.
  • A partially received message due to a broken connection must always be discarded. In case a peer can't recognize a stream, it must tear down the tcp connection but should only tear down the IKE SA if the issue is with the IKE packet syntax.
  • If MOBIKE is also being used, pluto must support dynamically changing between UDP and TCP when interfaces change.
  • A peer must silently drop NAT keep-alives when using TCP encapsulation. TCP/TLS keep alives may be used by peers but it must not be an indicator of IKE liveliness, for that IKE informational packets should be used.
  • Multiple IKE SAs must not share a single TCP connection, unless one is a rekey of an existing SA.

As of the completion of the project duration, I have been able to implement the following functionalities into Pluto (The Libreswan IKE daemon):

  • Listen on a TCP port for connections using the config parameter listen-tcp = portno. As of now, this should always be 4500.
  • Tell Pluto to connect to a server over TCP directly with the conn parameter tcponly = yes and specify tcp server port with tcp-remoteport = portno.
  • If we want to try over UDP first and then fallback to TCP, then set tcponly = no (default).
  • Currently rekey does work, but it tears down the old TCP connection and creates a new one.
  • In all cases where the TCP connection is torn down, right now we create a new IKE connection.

These are the things that are yet to be incorporated from the RFC into pluto:

  • Listen on multiple ports apart from 4500.
  • Add TLS support and maybe a configuration option to initiate TLS directly.
  • Any kind of support from the kernel. We are waiting for the kernel guys to implement this, and once that is in place, I'll make changes for it as well.
  • Mobike support

Implementation:

We have to make sure that TCP only inititates when UDP has completely failed. So, as of now, 1 keying attempt by the initiator with all the retransmits failing is enough to fall back to trying over TCP. After UDP fails, the next consequent attempts are made over TCP. But if the connection is loaded again or pluto restarts, it will try over UDP first. If a client wants to inititate over TCP from the start, it can set tcponly option as yes, in which case we will directly start with TCP encap. In both of these cases, just when the inititate function is called, I create a new TCP interface and inititate a connection to the server. And the state now stores the information about this interface in the subsequent exchanges.

On the server side, I create a TCP socket and put it in listen mode using libevent's evconnlistener to receive connections. When a new connection is received, a bufferevent is created and then all communication on that connection is handled by bufferevent callbacks. This includes handling the initial stream prefix of "IKETCP" and accept or reject the connection based on that. The read callback for bufferevent handles the received packet in the same function as UDP would handle it, just does receiving through bufferevent buffer.