Support SSL in secchan and controller.
This commit is contained in:
1 parent
9df2b7bb71
commit
cf6207b610
23 files changed
+1640
-216
No files matched your search
@@ -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
|
||||
-------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,2 +1,3 @@
|
||||
/Makefile
|
||||
/Makefile.in
|
||||
/dhparams.c
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
/Makefile
|
||||
/Makefile.in
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
Executable
+308
@@ -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
|
||||
Reference in new issue
Block a user