Linux IPsec overview
IPsec is a group of protocols used on top of IP for the purpose of authentication, encryption and secure exchange of encryption keys. These three activities correspond to three different protocols. The Authentication Header protocol, or AH for short, confirms the identity of the sender. Packet encryption is done by Encapsulating Security Payload or ESP. Finally, Internet Key Exchange or IKE is a mechanism for exchanging secret encryption keys used by ESP.
There is also an additional protocol, Payload Compression Protocol (IPComp). As it is next to impossible to achieve efficient compression of encrypted data, IPComp accommodates this shortcoming by compressing packets prior to ESP encryption. So while IPComp isn’t essential for actual security, it is essential for efficient net usage.
With the exception of IKE, all protocols are implemented somewhere in the depths of the Linux kernel. IKE is usually run as user-space daemon. There is an abundance of IKE implementations and there is always an option of putting keys manually.
As a part of the IPsec stack within the Linux kernel there are two databases. They are named Security Policy Database (SPD) and Security Association Database (SAD). Strictly speaking, those two databases look more like two tables than anything like relational databases, but nevertheless they do store data which is vital for IPsec functionality.
SAD stores Security Associations (SA). SA is used as a description of encryption protocols used on IPsec packets. The best analogy to describe security association would be a recipe. Just like a recipe is a list of directions and a set of ingredients for making something, SA is a mixture of directions and ingredients for the encryption of IPsec packets. With SA one can indicate the encryption of the authentication header and of the payload, and the encryption algorithm to be used for both of these tasks.
Like an unused recipe, unused SA doesn’t mean much. The purpose of both becomes apparent with usage. Security Policy (SP) is a mechanism which will put SA to the desired use. SP describes IPsec security procedure for a connection. SP can designate a host or port connection. Once a policy is set for a connection, the kernel will determine which security associations to apply to that connection. Security policies are stored in SPD database.
XFRM Programming Details
This simple overview of IPsec functionality raises a new question: how to use all those mechanisms within Linux? The end user will probably be satisfied with running an IKE daemon and a front-end through which one can insert all the associations and policies in the kernel. A more advanced user can achieve a similar effect through command-line utilities like setkey or iproute.
As this article is aimed at the most advanced users—the programmers, the population for which “how to use” is an abbreviation for “how to use it from code” things get more complicated. That question is by an order of magnitude harder. To answer this, additional insight into the kernel is needed, to the point where we must introduce a part of the kernel known as XFRM.
Both SAD and SPD are handled by the kernel module known as XFRM. Upon request XFRM will add, get, update or delete entries from SAD or SPD databases. Communicating with XFRM the programmer handles the SAD and SPD databases. Communication is done by passing messages to XFRM. It is also bidirectional. XFRM passes similar messages to the programmer. This happens when some triggered event fires, or more usually as a response to a programmer’s action.
XFRM is located deep within the kernel and it isn’t directly visible to the programmer. It is also an undocumented part of the kernel. The standard way of the communication with XFRM is done trough the Netlink Sockets API which is the standard IPC mechanism for various parts of the kernel. More specifically, the part of Netlink of our interest is NETLINK_XFRM. Unfortunately, that part is complex and also undocumented. An additional API does exist. It is called rtnetlink and is a wrapper API for netlink. It is somewhat easier to use and it is somewhat documented. It is a part of iproute2 project.
Inserting simple Security Policy
The simplest way to add an SA or SP entry is trough command-line utilities like iproute or setkey. We’ll be using iproute. For the list of all functions possible on policies use:
$ip xfrm policy help
Iproute is capable of adding, listing, flushing, deleting or updating policies in the database. The simplest way to add one policy would be:
$ip xfrm policy add dir in
This will add one general SP to SPD. The analog way to do so in C would be this:
int xfrm_policy_add_simple() { struct rtnl_handle rth; struct { struct nlmsghdr n; struct xfrm_userpolicy_info xpinfo; char buf[ 2048 ]; } req; memset(&req, 0, sizeof(req)); // nlmsghdr initialization req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xpinfo)); req.n.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_EXCL; req.n.nlmsg_type = XFRM_MSG_NEWPOLICY; // xfrm_userpolicy_info initialization req.xpinfo.sel.family = AF_INET; req.xpinfo.lft.soft_byte_limit = XFRM_INF; req.xpinfo.lft.hard_byte_limit = XFRM_INF; req.xpinfo.lft.soft_packet_limit = XFRM_INF; req.xpinfo.lft.hard_packet_limit = XFRM_INF; req.xpinfo.dir = XFRM_POLICY_IN; if (rtnl_open_byproto(&rth, 0, NETLINK_XFRM) < 0) exit(1); if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) exit(2); rtnl_close(&rth); return 0; }
The code is very simple. A quick explanation follows. In the first line we created an rtnetlink handler. Rtnetlink will take the burden of handling netlink sockets and this handler is used as a marker to those internal structures. That’s all there is to it. A more important part is the creation of the request. It is a structure composed out of three distinct features:
- a Netlink message header
- an XFRM message
- a field of attributes (that 2k char buffer)
All netlink messages consist of the netlink message header (nlmsghdr) and data which it encapsulates. The netlink message header carries some general information about the message like its type or size. As our goal is to add policy to SPD, the message we’ll use is of type XFRM_MSG_NEWPOLICY, which is defined in linuxxfrm.h.
This type of xfrm message is associated with the xfrm_userpolicy_info structure which carries all important data about a policy. It is a quite complex structure because the policy carries quite a lot of information. But for now, as our goal is simple (adding the most general policy), we’ll ignore all those things. Just remember that when you need to add the port, the destination or source address, the way of tunneling, the transport protocol and many other things you’ll add it to xfrm_userpolicy_info. The things we have set here are the expiration times of the policy (which we set to infinity), IP4 as the transport protocol in use (AF_INET), and the direction of the policy to “in” (XFRM_POLICY_IN) as this is default. All these constants are defined in linuxxfrm.h.
The field of attributes is not used in this particular example. However, it is used in more complex tasks like sending a provisional number of policy templates etc. The field of attributes is a standard part of the netlink philosophy, and meant to be handled by RTA macros within netlink which take the burden of proper memory alignment and can’t really be described as the most intuitive thing in the world. After all, this is low-level system programming
The last step is opening a socket (NETLINK_XFRM socket), sending the request, and closing the socket. Upon failure the code will stop. That’s all there is to inserting a new policy into the kernel.
Success of this function can be seen by the use of:
$ip xfrm policy list
The flushing of the policy is done with a very intuitive command:
$ip xfrm policy flush
In later articles we will demonstrate somewhat more complicated handling of security associations and polices, and some general lines of xfrm internals like a general walkthrough of all types of messages and associated structures.