Support SSL in secchan and controller.

This commit is contained in:
Ben Pfaff committed 2008-03-13 15:17:45 -07:00
1 parent 9df2b7bb71
commit cf6207b610
23 files changed
+1640 -216

No files matched your search

+171 -49
View File
@@ -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:
<info@openflowswitch.org>
Setting up the Kernel Build Environment
---------------------------------------
Prerequisites
-------------
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.
To compile the userspace programs in the OpenFlow reference
distribution, you will need the following software:
For example, if compiling on Debian or Ubuntu, the Linux headers
and image packages must be installed (apt-get install
linux-headers-<version> linux-image-<version>).
- A make program, e.g. GNU make
(http://www.gnu.org/software/make/). BSD make should also work.
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.
- 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 correct version of GCC for the kernel that you are building
the module against:
* 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).
* 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
-------------
+2 -3
View File
@@ -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
+46 -138
View File
@@ -1,4 +1,4 @@
OpenFlow Reference Release v0.1.5 <http://openflowswitch.org>
OpenFlow Reference Release <http://openflowswitch.org>
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.
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.
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:
<info@openflowswitch.org>
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
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.
<http://openflowswitch.org/alpha/openflow-spec-v0.2.pdf>
[3] Installation Instructions: INSTALL
Contact
-------
e-mail: info@openflowswitch.org
www: http://openflowswitch.org/alpha/
www: http://openflowswitch.org/
+17
View File
@@ -30,6 +30,23 @@ AC_CHECK_HEADER([linux/netlink.h],
[HAVE_NETLINK=no],
[#include <sys/socket.h>])
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])
+38 -7
View File
@@ -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
" ptcp:[PORT] listen to TCP PORT (default: %d)\n"
"\nOther options:\n"
#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
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);
}
+10
View File
@@ -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 */
+12 -3
View File
@@ -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
+1
View File
@@ -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. */
+1
View File
@@ -1,2 +1,3 @@
/Makefile
/Makefile.in
/dhparams.c
+14
View File
@@ -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
+10
View File
@@ -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.
+12
View File
@@ -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.
+18
View File
@@ -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.
+9
View File
@@ -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.
+11
View File
@@ -0,0 +1,11 @@
#ifndef DHPARAMS_H
#define DHPARAMS_H 1
#include <openssl/dh.h>
DH *get_dh512(void);
DH *get_dh1024(void);
DH *get_dh2048(void);
DH *get_dh4096(void);
#endif /* dhparams.h */
+2 -1
View File
@@ -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
+754
View File
@@ -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 <assert.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <poll.h>
#include <unistd.h>
#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;
}
+4 -2
View File
@@ -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
+12 -3
View File
@@ -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
-2
View File
@@ -1,2 +0,0 @@
/Makefile
/Makefile.in
+144
View File
@@ -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)
+44 -8
View File
@@ -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);
}
+308
View File
@@ -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 <<EOF
ofp-pki, for managing a simple OpenFlow public key infrastructure
usage: $0 [OPTION...] COMMAND [ARG...]
where the valid commands and their arguments are:
new-pki Create a new PKI
req NAME Create new private key and certificate request
named NAME-privkey.pem and NAME-req.pem, resp.
TYPE is a certificate type: 'switch' or 'controller'
sign NAME TYPE Sign switch certificate request NAME-req.pem,
producing certificate NAME-cert.pem
TYPE is a certificate type: 'switch' or 'controller'
req+sign NAME TYPE Combine the above two steps, producing all three files.
verify NAME TYPE Checks that NAME-cert.pem is a valid TYPE certificate
TYPE is a certificate type: 'switch' or 'controller'
The valid OPTIONS are:
-d, --dir=DIR Directory where the PKI is located (default: pki)
-f, --force Continue even if file or directory already exists
-b, --batch Skip fingerprint verification
-l, --log=FILE Log openssl output to FILE (default: ofp-log.log)
-h, --help Print this usage message.
EOF
exit 0
;;
--d*=*)
DIR=$optarg
;;
--d*|-d)
prev=DIR
;;
--force|-f)
force=yes
;;
--batch|-b)
batch=yes
;;
-*)
echo "unrecognized option $option"
exit 1
;;
*)
if test -z "$command"; then
command=$option
elif test -z "$arg1"; then
arg1=$option
elif test -z "$arg2"; then
arg2=$option
else
echo "only two arguments may be specified"
exit 1
fi
;;
esac
shift
done
if test -n "$prev"; then
option=--`echo $prev | sed 's/_/-/g'`
{ echo "$as_me: error: missing argument to $option" >&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 <<EOF
[ req ]
prompt = no
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
C = US
ST = CA
L = Palo Alto
O = OpenFlow
OU = OpenFlow certifier
CN = OpenFlow certificate
EOF
fi
# Create the CAs.
for ca in controllerca switchca; do
echo "Creating $ca..."
oldpwd=$PWD
mkdir -p $ca
cd $ca
mkdir -p certs crl newcerts private
touch index.txt
test -e crlnumber || echo 01 > 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