From: Ben Pfaff Date: Thu, 13 Mar 2008 00:13:31 +0000 (-0700) Subject: Support SSL in secchan and controller. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cf6207b610f15e73984e94c6c84ee07730ec746b;p=openvswitch Support SSL in secchan and controller. --- diff --git a/INSTALL b/INSTALL index e10aec69..db567b71 100644 --- a/INSTALL +++ b/INSTALL @@ -1,62 +1,100 @@ - Installation Instructions for OpenFlow Reference Release v0.1.5 + Installation Instructions for OpenFlow Reference Release -This document describes how to build, install, and execute the v0.1.5 +This document describes how to build, install, and execute the reference implementation of OpenFlow. Please send any comments to: -Setting up the Kernel Build Environment ---------------------------------------- +Prerequisites +------------- + +To compile the userspace programs in the OpenFlow reference +distribution, you will need the following software: + + - A make program, e.g. GNU make + (http://www.gnu.org/software/make/). BSD make should also work. + + - The GNU C compiler (http://gcc.gnu.org/). We generally test + with version 4.1 or 4.2. + + - libssl, from OpenSSL (http://www.openssl.org/), is optional but + recommended. libssl is required to establish confidentiality + and authenticity in the connections among OpenFlow switches and + controllers. + +To compile the datapath kernel module, you will additionally need: + + - A supported Linux kernel version. Please refer to README for a + list of supported versions. + + The OpenFlow datapath requires bridging support (CONFIG_BRIDGE) + to be built as a kernel module. (This is common in kernels + provided by Linux distributions.) The bridge module must not be + loaded or in use. If the bridge module is running (check with + "lsmod | grep bridge"), you must remove it ("rmmod bridge") + before starting the datapath. -The datapath kernel module must be compiled against a kernel build -directory for the Linux version the module is to run on. The datapath -module has been mainly tested on Linux 2.6.23. Support for Linux 2.4 -is also in place, although it has only been lightly tested under 2.4.35. + - The correct version of GCC for the kernel that you are building + the module against: -For example, if compiling on Debian or Ubuntu, the Linux headers -and image packages must be installed (apt-get install -linux-headers- linux-image-). + * To build a kernel module for a Linux 2.6 kernel, you need + the same version of GCC that was used to build that kernel + (usually version 4.0 or later). -Note: the OpenFlow datapath requires that bridging support has been -configured in the kernel, but not enabled or in use. If the bridge -module is running (check with "lsmod | grep bridge"), you must remove -it ("rmmod bridge") before starting the datapath. + * To build a kernel module for a Linux 2.4 kernel, you need an + earlier version of GCC, typically GCC 2.95, 3.3, or 3.4. + + - A kernel build directory corresponding to the Linux kernel image + the module is to run on. Under Debian and Ubuntu, for example, + each linux-image package containing a kernel binary has a + corresponding linux-headers package with the required build + infrastructure. Building the Code ----------------- -1. In the top source directory, configure the package, passing the - location of the kernel build directory as an argument. Use - --with-l26 for Linux 2.6, --with-l24 for Linux 2.4: +1. In the top source directory, configure the package by running the + configure script. To compile without building a kernel module, you + can usually invoke configure without any arguments: + % ./configure - For example, if compiling for a running instance of Linux 2.6: + To build a kernel module as well as the rest of the distribution, + pass the location of the kernel build directory as an argument. + Use --with-l26 for Linux 2.6, --with-l24 for Linux 2.4. For + example, to build for a running instance of Linux 2.6: % ./configure --with-l26=/lib/modules/`uname -r`/build - Or if compiling for a running instance of Linux 2.4: + To build for a running instance of Linux 2.4: % ./configure --with-l24=/lib/modules/`uname -r`/build To use a specific C compiler for compiling OpenFlow user programs, also specify it on the configure command line, like so: % ./configure CC=gcc-4.2 + The configure script accepts a number of other options and honors a + additional environment variables. For a full list, invoke + configure with the --help option. + 2. Run make in the top source directory: + % make - % make + The following binaries will be built: - The following binaries will be built: + Datapath kernel module: + datapath/linux-2.6/openflow_mod.ko (if --with-l26 was specified) + datapath/linux-2.4/openflow_mod.o (if --with-l24 was specified) - Datapath kernel module: - ./datapath/linux-2.6/openflow_mod.ko (If compiling for Linux 2.6) - ./datapath/linux-2.4/openflow_mod.o (If compiling for Linux 2.4) + Secure channel executable: + secchan/secchan - Secure channel executable: - ./secchan/secchan + Controller executable: + controller/controller - Controller executable: - ./controller/controller + Datapath administration utility: + utilities/dpctl - Datapath administration utility: - ./utilities/dpctl + Runtime logging configuration utility: + utilities/vlogconf 3. (Optional) Run "make install" to install the executables and manpages into the running system, by default under /usr/local. @@ -66,12 +104,12 @@ Installing the datapath To run the module, simply insmod it: - (Linux 2.6) - % insmod datapath/linux-2.6/openflow_mod.ko + (Linux 2.6) + % insmod datapath/linux-2.6/openflow_mod.ko - (Linux 2.4) - % insmod datapath/linux-2.4/compat24_mod.o - % insmod datapath/linux-2.4/openflow_mod.o + (Linux 2.4) + % insmod datapath/linux-2.4/compat24_mod.o + % insmod datapath/linux-2.4/openflow_mod.o Testing the datapath @@ -83,8 +121,7 @@ the dpctl command line utility. 1. Create a datapath instance. The command below creates a datapath with ID 0 (see dpctl(8) for more detailed usage information). - - % dpctl adddp 0 + % dpctl adddp 0 (note, while in principle openflow_mod supports multiple datapaths within the same host, this is rarely useful in practice) @@ -93,14 +130,12 @@ the dpctl command line utility. machine. Say, for example, you want to create a trivial 2-port switch using interfaces eth1 and eth2, you would issue the following commands: - - % dpctl addif 0 eth1 - % dpctl addif 0 eth2 + % dpctl addif 0 eth1 + % dpctl addif 0 eth2 You can verify that the interfaces were successfully added by asking dpctl to print the current status of datapath 0: - - % dpctl show 0 + % dpctl show 0 3. (Optional) You can manually add flows to the datapath to test using dpctl add-flows and view them using dpctl dump-flows. See dpctl(8) @@ -108,9 +143,8 @@ the dpctl command line utility. 4. The simplest way to test the datapath is to run the provided sample controller on the host machine to manage the datapath directly using - netlink. - - % controller -v nl:0 + netlink: + % controller -v nl:0 Once the controller is running, the datapath should operate like a learning Ethernet switch. You may monitor the flows in the datapath @@ -131,8 +165,7 @@ Running the datapath with a remote controller 2. Run the controller in passive tcp mode on the host which will act as the controller. In the example below, the controller will bind to port 975 (the default) awaiting connections from secure channels. - - % controller -v ptcp: + % controller -v ptcp: (See controller(8) for more details) @@ -146,8 +179,97 @@ Running the datapath with a remote controller If the controller is running on host 192.168.1.2 port 975 (the default port) and the datapath ID is 0, the secchan invocation would look like: - - % secchan -v nl:0 tcp:192.168.1.2 + % secchan -v nl:0 tcp:192.168.1.2 + +Secure operation over SSL +------------------------- + +The instructions above set up OpenFlow for operation over a plaintext +TCP connection. Production use of OpenFlow should use SSL to ensure +confidentiality and authenticity of traffic among switches and +controllers. + +To use SSL with OpenFlow, you must set up a public-key infrastructure +(PKI) including a pair of certificate authorities (CAs), one for +controllers and one for switches. If you have an established PKI, +OpenFlow can use it directly. Otherwise, refer to "Establishing a +Public Key Infrastructure" below. + +To configure the controller to listen for SSL connections on the +default port, invoke it as follows: + % controller -v pssl: --private-key=PRIVKEY --certificate=CERT \ + --ca-cert=CACERT +where PRIVKEY is a file containing the controller's private key, CERT +is a file containing the controller CA's certificate for the +controller's public key, and CACERT is a file containing the root +certificate for the switch CA. If, for example, your PKI was created +with the instructions below, then the invocation would look like: + % controller -v pssl: --private-key=ctl-privkey.pem \ + --certificate=ctl-cert.pem --ca-cert=pki/switchca/cacert.pem + +To configure a switch to connect to a controller running on the +default port on host 192.168.1.2 over SSL, invoke it as follows: + % secchan -v nl:0 ssl:192.168.1.2 --private-key=PRIVKEY \ + --certificate=CERT --ca-cert=CACERT +where PRIVKEY is a file containing the switch's private key, CERT is a +file containing the switch CA's certificate for the switch's public +key, and CACERT is a file containing the root certificate for the +controller CA. If, for example, your PKI was created with the +instructions below, then the invocation would look like: + % secchan -v nl:0 ssl:192.168.1.2 --private-key=sc-privkey.pem \ + --certificate=sc-cert.pem --ca-cert=pki/controllerca/cacert.pem + +Establishing a Public Key Infrastructure +---------------------------------------- + +If you do not have a PKI, the ofp-pki script included with OpenFlow +can help. To create an initial PKI structure, invoke it as: + % ofp-pki new-pki +which will create and populate a new directory named "pki" under the +current directory. + +The pki directory contains two important subdirectories. The +controllerca subdirectory contains controller certificate authority +related files, including the following: + + - cacert.pem: Root certificate for the controller certificate + authority. This file must be provided to the secchan + program with the --ca-cert option to enable it to + authenticate valid controllers. + + - private/cakey.pem: Private signing key for the controller + certificate authority. This file must be kept secret. There is + no need for switches or controllers to have a copy of it. + +The switchca subdirectory contains switch certificate authority +related files, analogous to those in the controllerca subdirectory: + + - cacert.pem: Root certificate for the switch certificate + authority. This file must be provided to the controller program + with the --ca-cert option to enable it to authenticate valid + switches. + + - private/cakey.pem: Private signing key for the switch + certificate authority. This file must be kept secret. There is + no need for switches or controllers to have a copy of it. + +After you create the initial structure, you can create keys and +certificates for switches and controllers with ofp-pki. To create a +controller private key and certificate in files named ctl-privkey.pem +and ctl-cert.pem, for example, you could run: + % ofp-pki req+sign ctl controller +ctl-privkey.pem and ctl-cert.pem would need to be copied to the +controller for its use at runtime (they could then be deleted from +their original locations). The --private-key and --certificate +options of controller, respectively, would point to these files. + +Analogously, to create a switch private key and certificate in files +named sc-privkey.pem and sc-cert.pem, for example, you could run: + % ofp-pki req+sign sc switch +sc-privkey.pem and sc-cert.pem would need to be copied to the switch +for its use at runtime (they could then be deleted from their original +locations). The --private-key and --certificate options of secchan, +respectively, would point to these files. Bug Reporting ------------- diff --git a/Make.vars b/Make.vars index 9b8b342f..538cde32 100644 --- a/Make.vars +++ b/Make.vars @@ -1,8 +1,7 @@ # -*- makefile -*- -if HAVE_NETLINK -AM_CPPFLAGS = -DHAVE_NETLINK=1 -endif +AM_CPPFLAGS = $(SSL_CFLAGS) +LIBS = $(SSL_LIBS) COMMON_FLAGS = -DVERSION=\"$(VERSION)\" if NDEBUG diff --git a/README b/README index b5268917..2cab2634 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ - OpenFlow Reference Release v0.1.5 + OpenFlow Reference Release What is OpenFlow? ----------------- @@ -8,13 +8,13 @@ researchers to run experiments in live networks. OpenFlow is based on a simple Ethernet flow switch that exposes a standardized interface for adding and removing flow entries. -An OpenFlow Switch consists of three parts: (1) A Flow Table in which -each flow entry is associated with an action telling the switch how to -process the flow, (2) A Secure Channel connecting the switch to a remote -process (a controller), allowing commands and packets to be sent between -the controller and the switch, and (3) An OpenFlow Protocol -implementation, providing an open and standard way for a controller to -talk to the switch. +An OpenFlow Switch consists of three parts: (1) A "flow table" in +which each flow entry is associated with an action telling the switch +how to process the flow, (2) a "secure channel" connecting the switch +to a remote process (a controller), allowing commands and packets to +be sent between the controller and the switch, and (3) an OpenFlow +protocol implementation, providing an open and standard way for a +controller to talk to the switch. An OpenFlow Switch can thus serve as a simple datapath element that forwards packets between ports according to flow actions defined by @@ -30,156 +30,66 @@ Specification [2]. What's here? ------------ -This software is a reference implementation of an OpenFlow Switch kernel -module for the Linux operating system, a secure channel implementation, -and an example controller that performs switching with MAC learning. +This distribution includes a Linux-specific reference implementation +of an OpenFlow switch, comprising: -The rest of this file contains the following sections: + - A Linux kernel module that implements the flow table and + OpenFlow protocol. - - Description of the directory hierarchy - - Platform support - - Quickstart build and install instructions - - Shortcomings - - References + - secchan, a program that implements the secure channel + component of the reference switch. -Directory Hierarchy -------------------- + - dpctl, a tool for configuring the kernel module. - Source: +This distribution includes some additional software as well: - datapath/ Linux kernel module implementing an OpenFlow Flow Table - that incoming packets are checked against. The - kernel module uses netlink (a socket protocol for - user-kernel communication, described in RFC 3549) to - pass OpenFlow messages with the secure channel to be - relayed to the controller. + - controller, a simple program connects to any number of + OpenFlow switches, commanding them to act as regular MAC + learning switches. - secchan/ A Secure Channel that connects to a kernel datapath - via netlink and a remote controller via TCP, - relaying OpenFlow packets received on one end to the - other. (The current implementation does not - support SSL, but this will be added in future releases.) + - vlogconf, a utility that can adjust the logging levels of a + running secchan or controller. - controller/ A simple controller that connects to a datapath via - a Secure Channel, commanding the datapath to act as - a regular MAC learning switch. + - ofp-pki, a utility for creating and managing the public-key + infrastructure for OpenFlow switches. - utilities/ Contains the sorce for "dpctl", a command-line utility - for controlling the OpenFlow datapath kernel module. - With it, you can add physical ports to the datapath, - add flows, monitor received packets, and query the - datapath state. + - A patch to tcpdump that enables it to parse OpenFlow + messages. - include/ Header files common to the datapath, secure channel, - and controller components. - - lib/ Implementation files common to the datapath, secure - channel, and controller components. - - third-party/ Contains third-party software that may be useful for - debugging. Currently, it only contains a patch to - allow tcpdump to parse OpenFlow messages. - - Documentation: - - README Text file describing this OpenFlow implementation, - aka this document. - - INSTALLATION Detailed configure, build, and installation - instructions - - man/ Man pages describing how to administer datapath, - secure channel, and controller. +For installation instructions, read INSTALL. This distribution also +includes manpages for each of its userspace programs, in the man/ +directory. Platform support ---------------- - The datapath kernel module supports Linux 2.6.15 and above, however, - testing has focused on Linux 2.6.23. Support for Linux 2.4.20 and - above is also in place, although testing has focused on Linux 2.6. - - Components have been built and tested on Debian and Ubuntu. - - If you are able to build/run the code on platforms not mentioned - here, or have problems with supported system, please report your - experiences to: - - - - GCC is required for compilation. - - -Building and Installing (Quick Start) -------------------------------------- - - Building the datapath module requires that the source for the - currently running Linux kernel be installed on the machine and - been configured. - - The following instructions assume the Linux 2.6 source is located in - /usr/src/linux-2.6.23 and Linux 2.4 in /usr/src/linux-2.4.35 - - 1. ./configure the package, passing the location of one or more - kernel source trees on the command line: - - For example, if compiling for Linux 2.6: - % ./configure --with-l26=/usr/src/linux-2.6.23 - - Or compiling for Linux 2.4: - % ./configure --with-l24=/usr/src/linux-2.4.35 +Other than the Linux kernel module, the software in the OpenFlow +distribution should compile under Unix-like environments such as +Linux, FreeBSD, Mac OS X, and Solaris. Our primary test environment +is Debian GNU/Linux. Please contact us with portability-related bug +reports or patches. - 2. Run make: - - % make - - The following binaries should be built. - - Datapath kernel module: - ./datapath/linux-2.6/openflow_mod.ko (If compiling for Linux 2.6) - ./datapath/linux-2.4/openflow_mod.o (If compiling for Linux 2.4) - - Secure channel executable: - ./secchan/secchan - - Controller executable: - ./controller/controller - - dpctl utility: - ./utility/dpctl - - 3. Optionally you can "make install" to install binaries and the - man pages (/usr/local/ is the default prefix). If you just want - access to the man pages without installing, set your MANPATH to - include the openflow/ source root. - - 4. Insert the datapath kernel module into the running Linux instance. - - (Linux 2.6) - % insmod datapath/linux-2.6/openflow_mod.ko - - (Linux 2.4) - % insmod datapath/linux-2.4/openflow_mod.o - - 5. Create datapaths by running dpctl on the Linux host (see man - dpctl(8)). Start the controller on a remote host with - controller (see man controller(8)). Start the Secure Channel - on the datapath host to connect the datapath to the controller - with secchan(see man secchan(8)). - - For more detailed installation instructions, refer to [3]. +The Linux kernel module is, of course, Linux-specific, and the secchan +and dpctl utilities will not be as useful without the kernel module. +The testing of the kernel module has focused on Linux 2.6.23. Linux +2.6 releases from 2.6.15 onward and Linux 2.4 releases from 2.4.20 +onward should also work. +GCC is the expected compiler. Bugs/Shortcomings ----------------- - The current flowtable does not support all statistics messages - mentioned in the Type 0 OpenFlow spec -- The secure channel and sample controller don't support SSL -- The flowtable does not support the "normal processing" action + mentioned in the Type 0 OpenFlow spec. + +- The flowtable does not support the "normal processing" action. + - Configure/build system does not support separate build directory for the datapath. ./configure must be run from the source root. + - dpctl dump-flows may freeze when large numbers of flows are in the - flow table. This has no affect on the datapath + flow table. This has no effect on the datapath. References ---------- @@ -190,10 +100,8 @@ References [2] OpenFlow Switch Specification. - [3] Installation Instructions: INSTALL - Contact ------- e-mail: info@openflowswitch.org -www: http://openflowswitch.org/alpha/ +www: http://openflowswitch.org/ diff --git a/configure.ac b/configure.ac index 4ec1972a..6abd5211 100644 --- a/configure.ac +++ b/configure.ac @@ -30,6 +30,23 @@ AC_CHECK_HEADER([linux/netlink.h], [HAVE_NETLINK=no], [#include ]) AM_CONDITIONAL([HAVE_NETLINK], [test "$HAVE_NETLINK" = yes]) +if test "$HAVE_NETLINK" = yes; then + AC_DEFINE([HAVE_NETLINK], [1], + [Define to 1 if Netlink protocol is available.]) +fi + +PKG_CHECK_MODULES([SSL], [libssl], + [HAVE_OPENSSL=yes], + [HAVE_OPENSSL=no + AC_MSG_WARN([Cannot find libssl: + +$SSL_PKG_ERRORS + +OpenFlow will not support SSL connections.])]) +AM_CONDITIONAL([HAVE_OPENSSL], [test "$HAVE_OPENSSL" = yes]) +if test "$HAVE_OPENSSL" = yes; then + AC_DEFINE([HAVE_OPENSSL], [1], [Define to 1 if OpenSSL is installed.]) +fi AC_CHECK_LIB([socket], [connect]) AC_CHECK_LIB([resolv], [gethostbyname]) diff --git a/controller/controller.c b/controller/controller.c index 8e22feab..6c89f060 100644 --- a/controller/controller.c +++ b/controller/controller.c @@ -133,9 +133,11 @@ main(int argc, char *argv[]) } while (n_switches > 0) { + size_t n_ready; int retval; /* Wait until there's something to do. */ + n_ready = 0; for (i = 0; i < n_switches; i++) { struct switch_ *this = switches[i]; int want; @@ -152,16 +154,17 @@ main(int argc, char *argv[]) this->pollfd = &pollfds[i]; this->pollfd->fd = -1; this->pollfd->events = 0; - vconn_prepoll(this->vconn, want, this->pollfd); + n_ready += vconn_prepoll(this->vconn, want, this->pollfd); } if (vlog_server) { pollfds[n_switches].fd = vlog_server_get_fd(vlog_server); pollfds[n_switches].events = POLLIN; } do { - retval = poll(pollfds, n_switches + (vlog_server != NULL), -1); + retval = poll(pollfds, n_switches + (vlog_server != NULL), + n_ready ? 0 : -1); } while (retval < 0 && errno == EINTR); - if (retval <= 0) { + if (retval < 0 || (retval == 0 && !n_ready)) { fatal(retval < 0 ? errno : 0, "poll"); } @@ -623,6 +626,11 @@ parse_options(int argc, char *argv[]) {"verbose", optional_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, +#ifdef HAVE_OPENSSL + {"private-key", required_argument, 0, 'p'}, + {"certificate", required_argument, 0, 'c'}, + {"ca-cert", required_argument, 0, 'C'}, +#endif {0, 0, 0, 0}, }; char *short_options = long_options_to_short_options(long_options); @@ -656,6 +664,20 @@ parse_options(int argc, char *argv[]) vlog_set_verbosity(optarg); break; +#ifdef HAVE_OPENSSL + case 'p': + vconn_ssl_set_private_key_file(optarg); + break; + + case 'c': + vconn_ssl_set_certificate_file(optarg); + break; + + case 'C': + vconn_ssl_set_ca_cert_file(optarg); + break; +#endif + case '?': exit(EXIT_FAILURE); @@ -672,16 +694,25 @@ usage(void) printf("%s: OpenFlow controller\n" "usage: %s [OPTIONS] VCONN\n" "where VCONN is one of the following:\n" + " ptcp:[PORT] listen to TCP PORT (default: %d)\n", + program_name, program_name); #ifdef HAVE_NETLINK - " nl:DP_IDX via netlink to local datapath DP_IDX\n" + printf(" nl:DP_IDX via netlink to local datapath DP_IDX\n"); +#endif +#ifdef HAVE_OPENSSL + printf(" pssl:[PORT] listen for SSL on PORT (default: %d)\n" + "\nPKI configuration (required to use SSL):\n" + " -p, --private-key=FILE file with private key\n" + " -c, --certificate=FILE file with certificate for private key\n" + " -C, --ca-cert=FILE file with peer CA certificate\n", + OFP_SSL_PORT); #endif - " ptcp:[PORT] listen to TCP PORT (default: %d)\n" - "\nOther options:\n" + printf("\nOther options:\n" " -H, --hub act as hub instead of learning switch\n" " -n, --noflow pass traffic, but don't add flows\n" " -v, --verbose set maximum verbosity level\n" " -h, --help display this help message\n" " -V, --version display version information\n", - program_name, program_name, OFP_TCP_PORT); + OFP_TCP_PORT); exit(EXIT_SUCCESS); } diff --git a/include/vconn-ssl.h b/include/vconn-ssl.h new file mode 100644 index 00000000..3eab068d --- /dev/null +++ b/include/vconn-ssl.h @@ -0,0 +1,10 @@ +#ifndef VCONN_SSL_H +#define VCONN_SSL_H 1 + +#ifdef HAVE_OPENSSL +void vconn_ssl_set_private_key_file(const char *file_name); +void vconn_ssl_set_certificate_file(const char *file_name); +void vconn_ssl_set_ca_cert_file(const char *file_name); +#endif + +#endif /* vconn-ssl.h */ diff --git a/include/vconn.h b/include/vconn.h index f6da0ad4..064947c2 100644 --- a/include/vconn.h +++ b/include/vconn.h @@ -46,7 +46,7 @@ enum { int vconn_open(const char *name, struct vconn **); void vconn_close(struct vconn *); bool vconn_is_passive(const struct vconn *); -void vconn_prepoll(struct vconn *, int want, struct pollfd *); +bool vconn_prepoll(struct vconn *, int want, struct pollfd *); void vconn_postpoll(struct vconn *, short int *revents); int vconn_accept(struct vconn *, struct vconn **); int vconn_recv(struct vconn *, struct buffer **); @@ -83,8 +83,13 @@ struct vconn_class { * initialize 'pfd->fd' and 'pfd->events' appropriately so that poll() will * wake up when the connection becomes available for the operations * specified in 'want'. The prepoll function may also set bits in 'pfd' to - * allow for internal processing. */ - void (*prepoll)(struct vconn *, int want, struct pollfd *pfd); + * allow for internal processing. + * + * Should return false normally. May return true to indicate that no + * blocking should happen in poll() because the connection is available for + * some operation specified in 'want' but that status cannot be detected + * via poll() and thus poll() could block forever otherwise. */ + bool (*prepoll)(struct vconn *, int want, struct pollfd *pfd); /* Called by the main loop after calling poll(), this function may perform * any internal processing needed by the connection. It is provided with @@ -147,6 +152,10 @@ struct vconn_class { extern struct vconn_class tcp_vconn_class; extern struct vconn_class ptcp_vconn_class; +#ifdef HAVE_OPENSSL +extern struct vconn_class ssl_vconn_class; +extern struct vconn_class pssl_vconn_class; +#endif #ifdef HAVE_NETLINK extern struct vconn_class netlink_vconn_class; #endif diff --git a/include/vlog.h b/include/vlog.h index f47324b8..93f13a14 100644 --- a/include/vlog.h +++ b/include/vlog.h @@ -60,6 +60,7 @@ enum vlog_facility vlog_get_facility_val(const char *name); VLOG_MODULE(socket_util) \ VLOG_MODULE(vconn_netlink) \ VLOG_MODULE(vconn_tcp) \ + VLOG_MODULE(vconn_ssl) \ VLOG_MODULE(vconn) \ /* VLM_ constant for each vlog module. */ diff --git a/lib/.gitignore b/lib/.gitignore index b336cc7c..b069cc18 100644 --- a/lib/.gitignore +++ b/lib/.gitignore @@ -1,2 +1,3 @@ /Makefile /Makefile.in +/dhparams.c diff --git a/lib/Makefile.am b/lib/Makefile.am index d9a490c5..1ef8bd03 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -25,3 +25,17 @@ libopenflow_la_SOURCES += \ netlink.c \ vconn-netlink.c endif + +if HAVE_OPENSSL +libopenflow_la_SOURCES += \ + vconn-ssl.c \ + dhparams.c +dhparams.c: dh512.pem dh1024.pem dh2048.pem dh4096.pem + (echo '#include "dhparams.h"' && \ + openssl dhparam -C -in $(srcdir)/dh512.pem -noout && \ + openssl dhparam -C -in $(srcdir)/dh1024.pem -noout && \ + openssl dhparam -C -in $(srcdir)/dh2048.pem -noout && \ + openssl dhparam -C -in $(srcdir)/dh4096.pem -noout) \ + | sed 's/\(get_dh[0-9]*\)()/\1(void)/' > dhparams.c.tmp + mv dhparams.c.tmp dhparams.c +endif diff --git a/lib/dh1024.pem b/lib/dh1024.pem new file mode 100644 index 00000000..6eaeca9b --- /dev/null +++ b/lib/dh1024.pem @@ -0,0 +1,10 @@ +-----BEGIN DH PARAMETERS----- +MIGHAoGBAPSI/VhOSdvNILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsY +jY67VYy4XTjTNP18F1dDox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6 +ypUM2Zafq9AKUJsCRtMIPWakXUGfnHy9iUsiGSa6q6Jew1XpL3jHAgEC +-----END DH PARAMETERS----- + +These are the 1024 bit DH parameters from "Assigned Number for SKIP Protocols" +(http://www.skip-vpn.org/spec/numbers.html). +See there for how they were generated. +Note that g is not a generator, but this is not a problem since p is a safe prime. diff --git a/lib/dh2048.pem b/lib/dh2048.pem new file mode 100644 index 00000000..dcd0b8d0 --- /dev/null +++ b/lib/dh2048.pem @@ -0,0 +1,12 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV +89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50 +T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknb +zSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdX +Q6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbT +CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg== +-----END DH PARAMETERS----- + +These are the 2048 bit DH parameters from "Assigned Number for SKIP Protocols" +(http://www.skip-vpn.org/spec/numbers.html). +See there for how they were generated. diff --git a/lib/dh4096.pem b/lib/dh4096.pem new file mode 100644 index 00000000..1b35ad8e --- /dev/null +++ b/lib/dh4096.pem @@ -0,0 +1,18 @@ +-----BEGIN DH PARAMETERS----- +MIICCAKCAgEA+hRyUsFN4VpJ1O8JLcCo/VWr19k3BCgJ4uk+d+KhehjdRqNDNyOQ +l/MOyQNQfWXPeGKmOmIig6Ev/nm6Nf9Z2B1h3R4hExf+zTiHnvVPeRBhjdQi81rt +Xeoh6TNrSBIKIHfUJWBh3va0TxxjQIs6IZOLeVNRLMqzeylWqMf49HsIXqbcokUS +Vt1BkvLdW48j8PPv5DsKRN3tloTxqDJGo9tKvj1Fuk74A+Xda1kNhB7KFlqMyN98 +VETEJ6c7KpfOo30mnK30wqw3S8OtaIR/maYX72tGOno2ehFDkq3pnPtEbD2CScxc +alJC+EL7RPk5c/tgeTvCngvc1KZn92Y//EI7G9tPZtylj2b56sHtMftIoYJ9+ODM +sccD5Piz/rejE3Ome8EOOceUSCYAhXn8b3qvxVI1ddd1pED6FHRhFvLrZxFvBEM9 +ERRMp5QqOaHJkM+Dxv8Cj6MqrCbfC4u+ZErxodzuusgDgvZiLF22uxMZbobFWyte +OvOzKGtwcTqO/1wV5gKkzu1ZVswVUQd5Gg8lJicwqRWyyNRczDDoG9jVDxmogKTH +AaqLulO7R8Ifa1SwF2DteSGVtgWEN8gDpN3RBmmPTDngyF2DHb5qmpnznwtFKdTL +KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n1Jnhub144YRBoN8CAQI= +-----END DH PARAMETERS----- + +These are the 4096 bit DH parameters from "Assigned Number for SKIP Protocols" +(http://www.skip-vpn.org/spec/numbers.html). +See there for how they were generated. +Note that g is not a generator, but this is not a problem since p is a safe prime. diff --git a/lib/dh512.pem b/lib/dh512.pem new file mode 100644 index 00000000..200d16cd --- /dev/null +++ b/lib/dh512.pem @@ -0,0 +1,9 @@ +-----BEGIN DH PARAMETERS----- +MEYCQQD1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMIPWak +XUGfnHy9iUsiGSa6q6Jew1XpKgVfAgEC +-----END DH PARAMETERS----- + +These are the 512 bit DH parameters from "Assigned Number for SKIP Protocols" +(http://www.skip-vpn.org/spec/numbers.html). +See there for how they were generated. +Note that g is not a generator, but this is not a problem since p is a safe prime. diff --git a/lib/dhparams.h b/lib/dhparams.h new file mode 100644 index 00000000..75e0ebee --- /dev/null +++ b/lib/dhparams.h @@ -0,0 +1,11 @@ +#ifndef DHPARAMS_H +#define DHPARAMS_H 1 + +#include + +DH *get_dh512(void); +DH *get_dh1024(void); +DH *get_dh2048(void); +DH *get_dh4096(void); + +#endif /* dhparams.h */ diff --git a/lib/vconn-netlink.c b/lib/vconn-netlink.c index 7ab54705..ab077398 100644 --- a/lib/vconn-netlink.c +++ b/lib/vconn-netlink.c @@ -85,7 +85,7 @@ netlink_close(struct vconn *vconn) free(netlink); } -static void +static bool netlink_prepoll(struct vconn *vconn, int want, struct pollfd *pfd) { struct netlink_vconn *netlink = netlink_vconn_cast(vconn); @@ -96,6 +96,7 @@ netlink_prepoll(struct vconn *vconn, int want, struct pollfd *pfd) if (want & WANT_SEND) { pfd->events |= POLLOUT; } + return false; } static int diff --git a/lib/vconn-ssl.c b/lib/vconn-ssl.c new file mode 100644 index 00000000..32c61f12 --- /dev/null +++ b/lib/vconn-ssl.c @@ -0,0 +1,754 @@ +/* Copyright (C) 2008 Board of Trustees, Leland Stanford Jr. University. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "vconn-ssl.h" +#include "dhparams.h" +#include +#include +#include +#include +#include +#include +#include +#include "buffer.h" +#include "socket-util.h" +#include "util.h" +#include "openflow.h" +#include "ofp-print.h" +#include "vconn.h" + +#include "vlog.h" +#define THIS_MODULE VLM_vconn_ssl + +/* Active SSL. */ + +enum ssl_state { + STATE_SSL_CONNECTING, + STATE_CONNECTED +}; + +enum session_type { + CLIENT, + SERVER +}; + +struct ssl_vconn +{ + struct vconn vconn; + enum ssl_state state; + int connect_error; + enum session_type type; + int fd; + SSL *ssl; + struct buffer *rxbuf; + struct buffer *txbuf; +}; + +/* SSL context created by ssl_init(). */ +static SSL_CTX *ctx; + +/* Required configuration. */ +static bool has_private_key, has_certificate, has_ca_cert; + +static int ssl_init(void); +static int do_ssl_init(void); +static void connect_completed(struct ssl_vconn *, int error); +static bool ssl_wants_io(int ssl_error); +static void ssl_close(struct vconn *); +static bool state_machine(struct ssl_vconn *sslv); +static DH *tmp_dh_callback(SSL *ssl, int is_export UNUSED, int keylength); + +static int +new_ssl_vconn(const char *name, int fd, enum session_type type, + struct vconn **vconnp) +{ + struct ssl_vconn *sslv; + SSL *ssl = NULL; + int on = 1; + int retval; + + /* Check for all the needful configuration. */ + if (!has_private_key) { + VLOG_ERR("Private key must be configured to use SSL"); + goto error; + } + if (!has_certificate) { + VLOG_ERR("Certificate must be configured to use SSL"); + goto error; + } + if (!has_ca_cert) { + VLOG_ERR("CA certificate must be configured to use SSL"); + goto error; + } + if (!SSL_CTX_check_private_key(ctx)) { + VLOG_ERR("Private key does not match certificate public key"); + goto error; + } + + /* Make 'fd' non-blocking and disable Nagle. */ + retval = set_nonblocking(fd); + if (retval) { + VLOG_ERR("%s: set_nonblocking: %s", name, strerror(retval)); + close(fd); + return retval; + } + retval = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof on); + if (retval) { + VLOG_ERR("%s: setsockopt(TCP_NODELAY): %s", name, strerror(errno)); + close(fd); + return errno; + } + + /* Create and configure OpenSSL stream. */ + ssl = SSL_new(ctx); + if (ssl == NULL) { + VLOG_DBG("SSL_new: %s", ERR_error_string(ERR_get_error(), NULL)); + close(fd); + return ENOPROTOOPT; + } + if (SSL_set_fd(ssl, fd) == 0) { + VLOG_DBG("SSL_set_fd: %s", ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + /* Create and return the ssl_vconn. */ + sslv = xmalloc(sizeof *sslv); + sslv->vconn.class = &ssl_vconn_class; + sslv->state = STATE_SSL_CONNECTING; + sslv->type = type; + sslv->fd = fd; + sslv->ssl = ssl; + sslv->rxbuf = NULL; + sslv->txbuf = NULL; + *vconnp = &sslv->vconn; + return 0; + +error: + if (ssl) { + SSL_free(ssl); + } + close(fd); + return ENOPROTOOPT; +} + +static struct ssl_vconn * +ssl_vconn_cast(struct vconn *vconn) +{ + assert(vconn->class == &ssl_vconn_class); + return CONTAINER_OF(vconn, struct ssl_vconn, vconn); +} + +static int +ssl_open(const char *name, char *suffix, struct vconn **vconnp) +{ + char *save_ptr, *host_name, *port_string; + struct sockaddr_in sin; + int retval; + int fd; + + retval = ssl_init(); + if (retval) { + return retval; + } + + /* Glibc 2.7 has a bug in strtok_r when compiling with optimization that + * can cause segfaults here: + * http://sources.redhat.com/bugzilla/show_bug.cgi?id=5614. + * Using "::" instead of the obvious ":" works around it. */ + host_name = strtok_r(suffix, "::", &save_ptr); + port_string = strtok_r(NULL, "::", &save_ptr); + if (!host_name) { + fatal(0, "%s: bad peer name format", name); + } + + memset(&sin, 0, sizeof sin); + sin.sin_family = AF_INET; + if (lookup_ip(host_name, &sin.sin_addr)) { + return ENOENT; + } + sin.sin_port = htons(port_string && *port_string ? atoi(port_string) + : OFP_SSL_PORT); + + /* Create socket. */ + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + VLOG_ERR("%s: socket: %s", name, strerror(errno)); + return errno; + } + + /* Connect socket (blocking). */ + retval = connect(fd, (struct sockaddr *) &sin, sizeof sin); + if (retval < 0) { + int error = errno; + VLOG_ERR("%s: connect: %s", name, strerror(error)); + close(fd); + return error; + } + + /* Make an ssl_vconn for the socket. */ + return new_ssl_vconn(name, fd, CLIENT, vconnp); +} + +static void +ssl_close(struct vconn *vconn) +{ + struct ssl_vconn *sslv = ssl_vconn_cast(vconn); + SSL_free(sslv->ssl); + close(sslv->fd); + free(sslv); +} + +static bool +ssl_want_io_to_events(SSL *ssl, short int *events) +{ + if (SSL_want_read(ssl)) { + *events |= POLLIN; + return true; + } else if (SSL_want_write(ssl)) { + *events |= POLLOUT; + return true; + } else { + return false; + } +} + +static bool +ssl_prepoll(struct vconn *vconn, int want, struct pollfd *pfd) +{ + struct ssl_vconn *sslv = ssl_vconn_cast(vconn); + pfd->fd = sslv->fd; + if (!state_machine(sslv)) { + switch (sslv->state) { + case STATE_SSL_CONNECTING: + if (!ssl_want_io_to_events(sslv->ssl, &pfd->events)) { + /* state_machine() should have transitioned us away to another + * state. */ + NOT_REACHED(); + } + break; + default: + NOT_REACHED(); + } + } else if (sslv->connect_error) { + pfd->events = 0; + return true; + } else if (!ssl_want_io_to_events(sslv->ssl, &pfd->events)) { + if (want & WANT_RECV) { + pfd->events |= POLLIN; + } + if (want & WANT_SEND || sslv->txbuf) { + pfd->events |= POLLOUT; + } + } + return false; +} + +static void +ssl_postpoll(struct vconn *vconn, short int *revents) +{ + struct ssl_vconn *sslv = ssl_vconn_cast(vconn); + if (!state_machine(sslv)) { + *revents = 0; + } else if (sslv->connect_error) { + *revents |= POLLERR; + } else if (*revents & POLLOUT && sslv->txbuf) { + ssize_t n = SSL_write(sslv->ssl, sslv->txbuf->data, sslv->txbuf->size); + if (n > 0) { + buffer_pull(sslv->txbuf, n); + if (sslv->txbuf->size == 0) { + buffer_delete(sslv->txbuf); + sslv->txbuf = NULL; + } + } + if (sslv->txbuf) { + *revents &= ~POLLOUT; + } + } +} + +static int +interpret_ssl_error(const char *function, int ret, int error) +{ + switch (error) { + case SSL_ERROR_NONE: + VLOG_ERR("%s: unexpected SSL_ERROR_NONE", function); + break; + + case SSL_ERROR_ZERO_RETURN: + VLOG_ERR("%s: unexpected SSL_ERROR_ZERO_RETURN", function); + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return EAGAIN; + + case SSL_ERROR_WANT_CONNECT: + VLOG_ERR("%s: unexpected SSL_ERROR_WANT_CONNECT", function); + break; + + case SSL_ERROR_WANT_ACCEPT: + VLOG_ERR("%s: unexpected SSL_ERROR_WANT_ACCEPT", function); + break; + + case SSL_ERROR_WANT_X509_LOOKUP: + VLOG_ERR("%s: unexpected SSL_ERROR_WANT_X509_LOOKUP", function); + break; + + case SSL_ERROR_SYSCALL: { + int queued_error = ERR_get_error(); + if (queued_error == 0) { + if (ret < 0) { + int status = errno; + VLOG_WARN("%s: system error (%s)", function, strerror(status)); + return status; + } else { + VLOG_WARN("%s: unexpected SSL connection close", function); + return EPROTO; + } + } else { + VLOG_DBG("%s: %s", function, ERR_error_string(queued_error, NULL)); + break; + } + } + + case SSL_ERROR_SSL: { + int queued_error = ERR_get_error(); + if (queued_error != 0) { + VLOG_DBG("%s: %s", function, ERR_error_string(queued_error, NULL)); + } else { + VLOG_ERR("%s: SSL_ERROR_SSL without queued error", function); + } + break; + } + + default: + VLOG_ERR("%s: bad SSL error code %d", function, error); + break; + } + return EIO; +} + +static int +ssl_recv(struct vconn *vconn, struct buffer **bufferp) +{ + struct ssl_vconn *sslv = ssl_vconn_cast(vconn); + struct buffer *rx; + size_t want_bytes; + ssize_t ret; + + if (!state_machine(sslv)) { + return EAGAIN; + } else if (sslv->connect_error) { + return sslv->connect_error; + } + + if (sslv->rxbuf == NULL) { + sslv->rxbuf = buffer_new(1564); + } + rx = sslv->rxbuf; + +again: + if (sizeof(struct ofp_header) > rx->size) { + want_bytes = sizeof(struct ofp_header) - rx->size; + } else { + struct ofp_header *oh = rx->data; + size_t length = ntohs(oh->length); + if (length < sizeof(struct ofp_header)) { + VLOG_ERR("received too-short ofp_header (%zu bytes)", length); + return EPROTO; + } + want_bytes = length - rx->size; + } + buffer_reserve_tailroom(rx, want_bytes); + + /* Behavior of zero-byte SSL_read is poorly defined. */ + assert(want_bytes > 0); + + ret = SSL_read(sslv->ssl, buffer_tail(rx), want_bytes); + if (ret > 0) { + rx->size += ret; + if (ret == want_bytes) { + if (rx->size > sizeof(struct ofp_header)) { + *bufferp = rx; + sslv->rxbuf = NULL; + return 0; + } else { + goto again; + } + } + return EAGAIN; + } else { + int error = SSL_get_error(sslv->ssl, ret); + if (error == SSL_ERROR_ZERO_RETURN) { + /* Connection closed (EOF). */ + if (rx->size) { + VLOG_WARN("SSL_read: unexpected connection close"); + return EPROTO; + } else { + return EOF; + } + } else { + return interpret_ssl_error("SSL_read", ret, error); + } + } +} + +static int +ssl_send(struct vconn *vconn, struct buffer *buffer) +{ + struct ssl_vconn *sslv = ssl_vconn_cast(vconn); + ssize_t ret; + + if (!state_machine(sslv)) { + return EAGAIN; + } else if (sslv->connect_error) { + return sslv->connect_error; + } + + if (sslv->txbuf) { + return EAGAIN; + } + + ret = SSL_write(sslv->ssl, buffer->data, buffer->size); + if (ret > 0) { + if (ret == buffer->size) { + buffer_delete(buffer); + } else { + sslv->txbuf = buffer; + buffer_pull(buffer, ret); + } + return 0; + } else { + int error = SSL_get_error(sslv->ssl, ret); + if (error == SSL_ERROR_ZERO_RETURN) { + /* Connection closed (EOF). */ + VLOG_WARN("SSL_write: connection close"); + return EPIPE; + } else { + return interpret_ssl_error("SSL_write", ret, error); + } + } +} + +struct vconn_class ssl_vconn_class = { + .name = "ssl", + .open = ssl_open, + .close = ssl_close, + .prepoll = ssl_prepoll, + .postpoll = ssl_postpoll, + .recv = ssl_recv, + .send = ssl_send, +}; + +/* Passive SSL. */ + +struct pssl_vconn +{ + struct vconn vconn; + int fd; +}; + +static struct pssl_vconn * +pssl_vconn_cast(struct vconn *vconn) +{ + assert(vconn->class == &pssl_vconn_class); + return CONTAINER_OF(vconn, struct pssl_vconn, vconn); +} + +static int +pssl_open(const char *name, char *suffix, struct vconn **vconnp) +{ + struct sockaddr_in sin; + struct pssl_vconn *pssl; + int retval; + int fd; + unsigned int yes = 1; + + retval = ssl_init(); + if (retval) { + return retval; + } + + /* Create socket. */ + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + int error = errno; + VLOG_ERR("%s: socket: %s", name, strerror(error)); + return error; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0) { + int error = errno; + VLOG_ERR("%s: setsockopt(SO_REUSEADDR): %s", name, strerror(errno)); + return error; + } + + memset(&sin, 0, sizeof sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(atoi(suffix) ? atoi(suffix) : OFP_SSL_PORT); + retval = bind(fd, (struct sockaddr *) &sin, sizeof sin); + if (retval < 0) { + int error = errno; + VLOG_ERR("%s: bind: %s", name, strerror(error)); + close(fd); + return error; + } + + retval = listen(fd, 10); + if (retval < 0) { + int error = errno; + VLOG_ERR("%s: listen: %s", name, strerror(error)); + close(fd); + return error; + } + + retval = set_nonblocking(fd); + if (retval) { + VLOG_ERR("%s: set_nonblocking: %s", name, strerror(retval)); + close(fd); + return retval; + } + + pssl = xmalloc(sizeof *pssl); + pssl->vconn.class = &pssl_vconn_class; + pssl->fd = fd; + *vconnp = &pssl->vconn; + return 0; +} + +static void +pssl_close(struct vconn *vconn) +{ + struct pssl_vconn *pssl = pssl_vconn_cast(vconn); + close(pssl->fd); + free(pssl); +} + +static bool +pssl_prepoll(struct vconn *vconn, int want, struct pollfd *pfd) +{ + struct pssl_vconn *pssl = pssl_vconn_cast(vconn); + pfd->fd = pssl->fd; + if (want & WANT_ACCEPT) { + pfd->events |= POLLIN; + } + return false; +} + +static int +pssl_accept(struct vconn *vconn, struct vconn **new_vconnp) +{ + struct pssl_vconn *pssl = pssl_vconn_cast(vconn); + int new_fd; + + new_fd = accept(pssl->fd, NULL, NULL); + if (new_fd < 0) { + int error = errno; + if (error != EAGAIN) { + VLOG_DBG("pssl: accept: %s", strerror(error)); + } + return error; + } + + return new_ssl_vconn("ssl" /* FIXME */, new_fd, SERVER, new_vconnp); +} + +struct vconn_class pssl_vconn_class = { + .name = "pssl", + .open = pssl_open, + .close = pssl_close, + .prepoll = pssl_prepoll, + .accept = pssl_accept, +}; + +/* + * Returns true if OpenSSL error is WANT_READ or WANT_WRITE, indicating that + * OpenSSL is requesting that we call it back when the socket is ready for read + * or writing (respectively. + */ +static bool +ssl_wants_io(int ssl_error) +{ + return (ssl_error == SSL_ERROR_WANT_WRITE + || ssl_error == SSL_ERROR_WANT_READ); +} + +static int +ssl_init(void) +{ + static int init_status = -1; + if (init_status < 0) { + init_status = do_ssl_init(); + assert(init_status >= 0); + } + return init_status; +} + +static int +do_ssl_init(void) +{ + SSL_METHOD *method; + + SSL_library_init(); + SSL_load_error_strings(); + + method = TLSv1_method(); + if (method == NULL) { + VLOG_ERR("TLSv1_method: %s", ERR_error_string(ERR_get_error(), NULL)); + return ENOPROTOOPT; + } + + ctx = SSL_CTX_new(method); + if (ctx == NULL) { + VLOG_ERR("SSL_CTX_new: %s", ERR_error_string(ERR_get_error(), NULL)); + return ENOPROTOOPT; + } + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + SSL_CTX_set_tmp_dh_callback(ctx, tmp_dh_callback); + SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + NULL); + + return 0; +} + +static bool +state_machine(struct ssl_vconn *sslv) +{ + if (sslv->state == STATE_SSL_CONNECTING) { + int ret = (sslv->type == CLIENT + ? SSL_connect(sslv->ssl) : SSL_accept(sslv->ssl)); + if (ret != 1) { + int error = SSL_get_error(sslv->ssl, ret); + if (ret < 0 && ssl_wants_io(error)) { + /* Stay in this state to repeat the SSL_connect later. */ + return false; + } else { + interpret_ssl_error((sslv->type == CLIENT ? "SSL_connect" + : "SSL_accept"), ret, error); + shutdown(sslv->fd, SHUT_RDWR); + connect_completed(sslv, EPROTO); + } + } else { + connect_completed(sslv, 0); + } + } + return sslv->state == STATE_CONNECTED; +} + +static void +connect_completed(struct ssl_vconn *sslv, int error) +{ + sslv->state = STATE_CONNECTED; + sslv->connect_error = error; +} + +static DH * +tmp_dh_callback(SSL *ssl, int is_export UNUSED, int keylength) +{ + struct dh { + int keylength; + DH *dh; + DH *(*constructor)(void); + }; + + static struct dh dh_table[] = { + {512, NULL, get_dh512}, + {1024, NULL, get_dh1024}, + {2048, NULL, get_dh2048}, + {4096, NULL, get_dh4096}, + }; + + struct dh *dh; + + for (dh = dh_table; dh < &dh[ARRAY_SIZE(dh_table)]; dh++) { + if (dh->keylength == keylength) { + if (!dh->dh) { + dh->dh = dh->constructor(); + if (!dh->dh) { + fatal(ENOMEM, "out of memory constructing " + "Diffie-Hellman parameters"); + } + } + return dh->dh; + } + } + VLOG_ERR("no Diffie-Hellman parameters for key length %d", keylength); + return NULL; +} + +void +vconn_ssl_set_private_key_file(const char *file_name) +{ + if (ssl_init()) { + return; + } + if (SSL_CTX_use_PrivateKey_file(ctx, file_name, SSL_FILETYPE_PEM) != 1) { + VLOG_ERR("SSL_use_PrivateKey_file: %s", + ERR_error_string(ERR_get_error(), NULL)); + return; + } + has_private_key = true; +} + +void +vconn_ssl_set_certificate_file(const char *file_name) +{ + if (ssl_init()) { + return; + } + if (SSL_CTX_use_certificate_chain_file(ctx, file_name) != 1) { + VLOG_ERR("SSL_use_certificate_file: %s", + ERR_error_string(ERR_get_error(), NULL)); + return; + } + has_certificate = true; +} + +void +vconn_ssl_set_ca_cert_file(const char *file_name) +{ + STACK_OF(X509_NAME) *ca_list; + + if (ssl_init()) { + return; + } + + /* Set up list of CAs that the server will accept from the client. */ + ca_list = SSL_load_client_CA_file(file_name); + if (ca_list == NULL) { + VLOG_ERR("SSL_load_client_CA_file: %s", + ERR_error_string(ERR_get_error(), NULL)); + return; + } + SSL_CTX_set_client_CA_list(ctx, ca_list); + + /* Set up CAs for OpenSSL to trust in verifying the peer's certificate. */ + if (SSL_CTX_load_verify_locations(ctx, file_name, NULL) != 1) { + VLOG_ERR("SSL_load_verify_locations: %s", + ERR_error_string(ERR_get_error(), NULL)); + return; + } + + has_ca_cert = true; +} diff --git a/lib/vconn-tcp.c b/lib/vconn-tcp.c index 1878d2db..f97761fe 100644 --- a/lib/vconn-tcp.c +++ b/lib/vconn-tcp.c @@ -139,7 +139,7 @@ tcp_close(struct vconn *vconn) free(tcp); } -static void +static bool tcp_prepoll(struct vconn *vconn, int want, struct pollfd *pfd) { struct tcp_vconn *tcp = tcp_vconn_cast(vconn); @@ -150,6 +150,7 @@ tcp_prepoll(struct vconn *vconn, int want, struct pollfd *pfd) if (want & WANT_SEND || tcp->txbuf) { pfd->events |= POLLOUT; } + return false; } static void @@ -336,7 +337,7 @@ ptcp_close(struct vconn *vconn) free(ptcp); } -static void +static bool ptcp_prepoll(struct vconn *vconn, int want, struct pollfd *pfd) { struct ptcp_vconn *ptcp = ptcp_vconn_cast(vconn); @@ -344,6 +345,7 @@ ptcp_prepoll(struct vconn *vconn, int want, struct pollfd *pfd) if (want & WANT_ACCEPT) { pfd->events |= POLLIN; } + return false; } static int diff --git a/lib/vconn.c b/lib/vconn.c index 2fedc2eb..be96ef9a 100644 --- a/lib/vconn.c +++ b/lib/vconn.c @@ -37,6 +37,10 @@ static struct vconn_class *vconn_classes[] = { #ifdef HAVE_NETLINK &netlink_vconn_class, #endif +#ifdef HAVE_OPENSSL + &ssl_vconn_class, + &pssl_vconn_class, +#endif }; /* Check the validity of the vconn class structures. */ @@ -116,11 +120,16 @@ vconn_is_passive(const struct vconn *vconn) /* Initializes 'pfd->fd' and 'pfd->events' appropriately so that poll() will * wake up when the connection becomes available for the operations specified - * in 'want', or for performing the vconn's needed internal processing. */ -void + * in 'want', or for performing the vconn's needed internal processing. + * + * Normally returns false. Returns true to indicate that no blocking should + * happen in poll() because the connection is available for some operation + * specified in 'want' but that status cannot be detected via poll() and thus + * poll() could block forever otherwise. */ +bool vconn_prepoll(struct vconn *vconn, int want, struct pollfd *pollfd) { - (vconn->class->prepoll)(vconn, want, pollfd); + return (vconn->class->prepoll)(vconn, want, pollfd); } /* Perform any internal processing needed by the connections. The vconn file diff --git a/man/man1/.gitignore b/man/man1/.gitignore deleted file mode 100644 index b336cc7c..00000000 --- a/man/man1/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/Makefile -/Makefile.in diff --git a/man/man8/ofp-pki.8 b/man/man8/ofp-pki.8 new file mode 100644 index 00000000..47c164ad --- /dev/null +++ b/man/man8/ofp-pki.8 @@ -0,0 +1,144 @@ +.TH ofp-pki 8 "March 2008" "OpenFlow" "OpenFlow Manual" + +.SH NAME +ofp\-pki \- OpenFlow public key infrastructure management utility + +.SH SYNOPSIS +\fBofp\-pki\fR [\fIOPTIONS\fR] \fICOMMAND\fR [\fIARGS\fR] +.sp +Commands with their arguments: +.br +\fBofp\-pki\fR \fBnew-pki\fR +.br +\fBofp\-pki\fR \fBreq\fR \fINAME\fR +.br +\fBofp\-pki\fR \fBsign\fR \fINAME\fR \fITYPE\fR +.br +\fBofp\-pki\fR \fBreq+sign\fR \fINAME\fR \fITYPE\fR +.br +\fBofp\-pki\fR \fBverify\fR \fINAME\fR \fITYPE\fR +.sp +The available options are: +.br +[\fB-d\fR \fIDIR\fR | \fB--dir=\fR\fIDIR\fR] [\fB-f\fR | \fB--force\fR] [\fB-b\fR | \fB--batch\fR] [\fB-l\fR \fIFILE\fR | \fB--log=\fIFILE\fR] [\fB-h\fR | \fB--help\fR] + +.SH DESCRIPTION +The \fBofp-pki\fR program sets up and manages a public key +infrastructure for use with OpenFlow. It is intended to be a simple +interface for organizations that do not have an established public key +infrastructure. Other PKI tools can substitute for or supplement the +use of \fBofp-pki\fR. + +\fBofp-pki\fR uses \fBopenssl\fR(1) for certificate management and key +generation. + +The commands supported by \fBofp-pki\fR are: + +.TP +\fBnew-pki\fR +Creates a new PKI directory (by default named \fBpki\fR) and populates +it with a pair of certificate authorities for controllers and +switches. + +This command should ideally be run on a high-security machine separate +from any OpenFlow controller or switch, called the CA machine. The +files \fBpki/controllerca/cacert.pem\fR and +\fBpki/switchca/cacert.pem\fR that it produces will need to be copied +over to the OpenFlow switches and controllers, respectively. Their +contents may safely be made public. + +Other files generated by \fBnew-pki\fR may remain on the CA machine. +The files \fBpki/controllerca/private/cakey.pem\fR and +\fBpki/switchca/private/cakey.pem\fR have particularly sensitive +contents that should not be exposed. + +.TP +\fBofp\-pki\fR \fBreq\fR \fINAME\fR +Generates a new private key named \fINAME\fR\fB-privkey.pem\fR and +corresponding certificate request named \fINAME\fR\fB-req.pem\fR. +The private key can be intended for use by a switch or a controller. + +This command should ideally be run on the switch or controller that +will use the private key to identify itself. The file +\fINAME\fR\fB-req.pem\fR must be copied to the CA machine for signing +with the \fBsign\fR command (below). + +This command will output a fingerprint to stdout as its final step. +Write down the fingerprint and take it to the CA machine before +continuing with the \fBsign\fR step. + +\fINAME\fR\fB-privkey.pem\fR has sensitive contents that should not be +exposed. \fINAME\fR\fB-req.pem\fR may be safely made public. + +.TP +\fBofp\-pki\fR \fBsign\fR \fINAME\fR \fITYPE\fR +Signs the certificate request named \fINAME\fR\fB-req.pem\fR that was +produced in the previous step, producing a certificate named +\fINAME\fR\fB-cert.pem\fR. \fITYPE\fR, which must be \fBswitch\fR or +\fBcontroller\fR, indicates the use for which the key is being +certified. + +This command must be run on the CA machine. + +The command will output a fingerprint to stdout and request that you +verify that it is the same fingerprint output by the \fBreq\fR +command. This ensures that the request being signed is the same one +produced by \fBreq\fR. + +The file \fINAME\fR\fB-cert.pem\fR will need to be copied back to the +switch or controller for which it is intended. Its contents may +safely be made public. + +.TP +\fBofp\-pki\fR \fBreq+sign\fR \fINAME\fR \fITYPE\fR +Combines the \fBreq\fR and \fBsign\fR commands into a single step, +outputting all the files produced by each. The +\fINAME\fR\fB-privkey.pem\fR and \fINAME\fR\fB-cert.pem\fR files must +be copied securely to the switch or controller. +\fINAME\fR\fB-privkey.pem\fR has sensitive contents and must not be +exposed in transit. Afterward, it should be deleted from the CA +machine. + +This combined method is, theoretically, less secure than the +individual steps performed separately on two different machines, +because there is additional potential for exposure of the private +key. However, it is also more convenient. + +.TP +\fBofp\-pki\fR \fBverify\fR \fINAME\fR \fITYPE\fR +Verifies that \fINAME\fR\fB-cert.pem\fR is a valid certificate for the +given \fITYPE\fR of use (either \fBswitch\fR or \fBcontroller\fR). If +the certificate is valid for this use, it prints the message +``\fINAME\fR\fB-cert.pem\fR: OK''; otherwise, it prints an error +message. + +.SH OPTIONS +.TP +[\fB-d\fR \fIDIR\fR | \fB--dir=\fR\fIDIR\fR] +Specifies the location of the PKI hierarchy to be used or created by +the command (default: \fBpki\fR under the current directory). The +\fBreq\fR command does not need access to a PKI hierarchy. + +.TP +[\fB-f\fR | \fB--force\fR] +By default, \fBofp-pki\fR will not overwrite existing files or +directories. This option overrides this behavior. + +.TP +[\fB-b\fR | \fB--batch\fR] +Suppresses the interactive verification of fingerprints that the +\fBsign\fR command by default requires. + +.TP +[\fB-l\fR \fIFILE\fR | \fB--log=\fIFILE\fR] +Sets the log file to \fIFILE\fR (default: ofp-pki.log). + +.TP +[\fB-h\fR | \fB--help\fR] +Prints a help usage message and exits. + +.SH "SEE ALSO" + +.BR dpctl (8), +.BR secchan (8), +.BR controller (8) diff --git a/secchan/secchan.c b/secchan/secchan.c index e9df46d2..9418b41a 100644 --- a/secchan/secchan.c +++ b/secchan/secchan.c @@ -88,7 +88,10 @@ main(int argc, char *argv[]) reconnect(&halves[i]); } for (;;) { + size_t n_ready; + /* Wait until there's something to do. */ + n_ready = 0; for (i = 0; i < 2; i++) { struct half *this = &halves[i]; struct half *peer = &halves[!i]; @@ -101,16 +104,17 @@ main(int argc, char *argv[]) } this->pollfd->fd = -1; this->pollfd->events = 0; - vconn_prepoll(this->vconn, want, this->pollfd); + n_ready += vconn_prepoll(this->vconn, want, this->pollfd); } if (vlog_server) { pollfds[2].fd = vlog_server_get_fd(vlog_server); pollfds[2].events = POLLIN; } do { - retval = poll(pollfds, 2 + (vlog_server != NULL), -1); + retval = poll(pollfds, 2 + (vlog_server != NULL), + n_ready ? 0 : -1); } while (retval < 0 && errno == EINTR); - if (retval <= 0) { + if (retval < 0 || (retval == 0 && !n_ready)) { fatal(retval < 0 ? errno : 0, "poll"); } @@ -217,6 +221,11 @@ parse_options(int argc, char *argv[]) {"verbose", optional_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, +#ifdef HAVE_OPENSSL + {"private-key", required_argument, 0, 'p'}, + {"certificate", required_argument, 0, 'c'}, + {"ca-cert", required_argument, 0, 'C'}, +#endif {0, 0, 0, 0}, }; char *short_options = long_options_to_short_options(long_options); @@ -246,6 +255,20 @@ parse_options(int argc, char *argv[]) vlog_set_verbosity(optarg); break; +#ifdef HAVE_OPENSSL + case 'p': + vconn_ssl_set_private_key_file(optarg); + break; + + case 'c': + vconn_ssl_set_certificate_file(optarg); + break; + + case 'C': + vconn_ssl_set_ca_cert_file(optarg); + break; +#endif + case '?': exit(EXIT_FAILURE); @@ -260,15 +283,28 @@ static void usage(void) { printf("%s: Secure Channel\n" - "usage: %s [OPTIONS] nl:DP_ID tcp:HOST:[PORT]\n" - "\nConnects to local datapath DP_ID via Netlink and \n" - "controller on HOST via TCP to PORT (default: %d).\n" - "\nNetworking options:\n" + "usage: %s [OPTIONS] LOCAL REMOTE\n" + "\nRelays OpenFlow message between LOCAL and REMOTE datapaths.\n" + "LOCAL and REMOTE must each be one of the following:\n" + " tcp:HOST[:PORT] PORT (default: %d) on remote TCP HOST\n", + program_name, program_name); +#ifdef HAVE_NETLINK + printf(" nl:DP_IDX local datapath DP_IDX\n"); +#endif +#ifdef HAVE_OPENSSL + printf(" ssl:HOST[:PORT] SSL PORT (default: %d) on remote HOST\n" + "\nPKI configuration (required to use SSL):\n" + " -p, --private-key=FILE file with private key\n" + " -c, --certificate=FILE file with certificate for private key\n" + " -C, --ca-cert=FILE file with peer CA certificate\n", + OFP_SSL_PORT); +#endif + printf("\nNetworking options:\n" " -u, --unreliable do not reconnect after connections drop\n" "\nOther options:\n" " -v, --verbose set maximum verbosity level\n" " -h, --help display this help message\n" " -V, --version display version information\n", - program_name, program_name, OFP_TCP_PORT); + OFP_TCP_PORT); exit(EXIT_SUCCESS); } diff --git a/utilities/ofp-pki b/utilities/ofp-pki new file mode 100755 index 00000000..46a6a89d --- /dev/null +++ b/utilities/ofp-pki @@ -0,0 +1,308 @@ +#! /bin/sh -e + +DIR=pki +command= +arg1= +arg2= +prev= +force=no +batch=no +log=ofp-pki.log +for option; do + # This option-parsing mechanism borrowed from a Autoconf-generated + # configure script under the following license: + + # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, + # 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. + # This configure script is free software; the Free Software Foundation + # gives unlimited permission to copy, distribute and modify it. + + # If the previous option needs an argument, assign it. + if test -n "$prev"; then + eval $prev=\$option + prev= + continue + fi + case $option in + *=*) optarg=`expr "X$option" : '[^=]*=\(.*\)'` ;; + *) optarg=yes ;; + esac + + case $dashdash$option in + --) + dashdash=yes ;; + -h|--help) + cat <&2 + { (exit 1); exit 1; }; } +fi +if test -z "$command"; then + echo "$0: missing command name; use --help for help" + exit 1 +fi +exec 3>>$log + +if test "$command" = "new-pki"; then + if test -e "$DIR" && test "$force" != "yes"; then + echo "$0: $DIR already exists" + exit 1 + fi + + if test ! -d "$DIR"; then + mkdir "$DIR" + fi + cd "$DIR" + + if test ! -e dsaparam.pem; then + echo "Generating DSA parameters, please wait..." + openssl dsaparam -out dsaparam.pem 2048 1>&3 2>&3 + fi + + # Create the request configuration. + if test ! -e req.cnf; then + cat > req.cnf < crlnumber + test -e serial || echo 01 > serial + + # Put DSA parameters in directory. + if test ! -e dsaparam.pem; then + cp ../dsaparam.pem . + fi + + # Write CA configuration file. + if test ! -e ca.cnf; then + cat > ca.cnf <<'EOF' +[ req ] +prompt = no +distinguished_name = req_distinguished_name + +[ req_distinguished_name ] +C = US +ST = CA +L = Palo Alto +O = OpenFlow +OU = OpenFlow +CN = OpenFlow + +[ ca ] +default_ca = the_ca + +[ the_ca ] +dir = . # top dir +database = $dir/index.txt # index file. +new_certs_dir = $dir/newcerts # new certs dir +certificate = $dir/cacert.pem # The CA cert +serial = $dir/serial # serial no file +private_key = $dir/private/cakey.pem# CA private key +RANDFILE = $dir/private/.rand # random number file +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = md5 # md to use +policy = policy # default policy +email_in_dn = no # Don't add the email into cert DN +name_opt = ca_default # Subject name display option +cert_opt = ca_default # Certificate display option +copy_extensions = none # Don't copy extensions from request + +# For the CA policy +[ policy ] +countryName = optional +stateOrProvinceName = optional +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional +EOF + fi + + # Create certificate authority. + openssl req -config ca.cnf -nodes \ + -newkey dsa:dsaparam.pem -keyout private/cakey.pem -out careq.pem \ + 1>&3 2>&3 + openssl ca -config ca.cnf -create_serial -out cacert.pem \ + -days 1095 -batch -keyfile private/cakey.pem -selfsign \ + -infiles careq.pem 1>&3 2>&3 + + cd "$oldpwd" + done + exit 0 +fi + +one_arg() { + if test -z "$arg1" || test -n "$arg2"; then + echo "$0: $command must have exactly one argument; use --help for help" + exit 1 + fi +} + +two_args() { + if test -z "$arg1" || test -z "$arg2"; then + echo "$0: $command must have exactly two arguments; use --help for help" + exit 1 + fi +} + +must_not_exist() { + if test -e "$1" && test "$force" != "yes"; then + echo "$0: $1 already exists and --force not supplied" + exit 1 + fi +} + +fingerprint() { + printf "$1-req.pem fingerprint is " + sha1sum "$1-req.pem" | awk '{print $1}' +} + +check_type() { + if test "$1" != switch && test "$1" != controller; then + echo "$0: type argument must be 'switch' or 'controller'" + exit 1 + fi +} + +must_exist() { + if test ! -e "$1"; then + echo "$0: $1 does not exist" + exit 1 + fi +} + +DIR_must_exist() { + if test ! -e "$DIR"; then + echo "$0: $DIR does not exist (need to use --dir or new-pki?)" + exit 1 + elif test ! -d "$DIR"; then + echo "$0: $DIR is not a directory" + exit 1 + fi +} + +make_request() { + must_not_exist "$arg1-privkey.pem" + must_not_exist "$arg1-req.pem" + DIR_must_exist + openssl req -config "$DIR/req.cnf" -text -nodes \ + -newkey "dsa:$DIR/dsaparam.pem" -keyout "$1-privkey.pem" \ + -out "$1-req.pem" 1>&3 2>&3 +} + +sign_request() { + must_exist "$1-req.pem" + must_not_exist "$1-cert.pem" + check_type "$2" + DIR_must_exist + (cd "$DIR/$2ca" && openssl ca -config ca.cnf -batch -in /dev/stdin) \ + < "$1-req.pem" > "$1-cert.pem.tmp" 2>&3 + mv "$1-cert.pem.tmp" "$1-cert.pem" +} + +if test "$command" = req; then + one_arg + make_request "$arg1" + fingerprint "$arg1" +elif test "$command" = sign; then + two_args + fingerprint "$arg1" + if test $batch != yes; then + echo "Does fingerprint match? (yes/no)" + read answer + if test "$answer" != yes; then + echo "Match failure, aborting" + exit 1 + fi + fi + sign_request "$arg1" "$arg2" +elif test "$command" = req+sign; then + two_args + make_request "$arg1" + sign_request "$arg1" "$arg2" + fingerprint "$arg1" +elif test "$command" = verify; then + two_args + must_exist "$arg1-cert.pem" + check_type "$arg2" + DIR_must_exist + openssl verify -CAfile "$DIR/${arg2}ca/cacert.pem" "$arg1-cert.pem" +else + echo "$0: $command command unknown; use --help for help" + exit 1 +fi